Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
db8da7f
adds components list
dChiamp Jun 20, 2025
bd57d86
Update COMPONENTS.md
Sveb Jun 20, 2025
4fa76ad
Update COMPONENTS.md
Sveb Jun 20, 2025
b4dc86d
Update COMPONENTS.md
Sveb Jun 23, 2025
285e079
Update COMPONENTS.md
Sveb Jun 23, 2025
213a640
adds notes
dChiamp Jun 23, 2025
3004c74
Update COMPONENTS.md
Sveb Jun 24, 2025
7a1e0aa
Merge branch 'main' of https://github.com/funkhaus/ucla-library-websi…
dChiamp Jun 24, 2025
e955637
claimed component
austinblanchard Jun 24, 2025
d5e45d1
Update COMPONENTS.md
Sveb Jun 25, 2025
d49eaf5
claim a component
lux-v Jun 25, 2025
4b6cccf
claim a component
lux-v Jun 25, 2025
bfa79d4
Update COMPONENTS.md
Sveb Jun 25, 2025
4852133
Update COMPONENTS.md
Sveb Jun 25, 2025
2120974
Update COMPONENTS.md
Sveb Jun 25, 2025
c5d0581
take a component
lux-v Jun 25, 2025
af0a6b3
take a component
lux-v Jun 25, 2025
2e73be7
take a component
lux-v Jun 25, 2025
88bfe5e
Update COMPONENTS.md
austinblanchard Jun 25, 2025
22b88d7
Update COMPONENTS.md
Sveb Jun 26, 2025
498ab73
Adjust the component name - already exists
Sveb Jun 30, 2025
95a1121
UCLA - NotesAccordion
Sveb Jun 30, 2025
09c2fd0
take a component
lux-v Jul 1, 2025
0c75f77
take a component
lux-v Jul 1, 2025
a7bf5fa
Update COMPONENTS.md
Sveb Jul 1, 2025
069c123
Update COMPONENTS.md
Sveb Jul 7, 2025
cdb4a86
Update COMPONENTS.md
Sveb Jul 7, 2025
3bb30e3
ExcerptPod
Sveb Jul 7, 2025
8a29837
Update COMPONENTS.md
austinblanchard Jul 7, 2025
7bdfa7a
Update COMPONENTS.md - marking BentoPod
Sveb Jul 8, 2025
bc0154d
add component
lux-v Jul 8, 2025
597ef57
add component
lux-v Jul 8, 2025
02aafc7
Update COMPONENTS.md
austinblanchard Jul 8, 2025
e675c1b
Update COMPONENTS.md
austinblanchard Jul 8, 2025
2933c6d
take a component
lux-v Jul 9, 2025
d5c665a
take a component
lux-v Jul 9, 2025
e539e14
fix component description
lux-v Jul 9, 2025
4808e2d
Update COMPONENTS.md
Sveb Jul 10, 2025
e372e04
Update COMPONENTS.md
Sveb Jul 10, 2025
7d5d656
change component's name
lux-v Jul 11, 2025
ed656cf
Update COMPONENTS.md
Sveb Jul 14, 2025
877fb65
take a component
lux-v Jul 14, 2025
741d21e
Update NotesAccordion.vue
Sveb Jul 15, 2025
348494b
take a component
lux-v Jul 15, 2025
edc6600
clean up
Sveb Jul 15, 2025
0ffe143
Clean up props
Sveb Jul 15, 2025
45e6cf7
take a component
lux-v Jul 16, 2025
183375c
remove AssetFeaturedImage definition
lux-v Jul 16, 2025
ee9dd92
take a component
lux-v Jul 16, 2025
62919e6
take a components
lux-v Jul 16, 2025
51430d6
take a components
lux-v Jul 16, 2025
f6de890
take a component
lux-v Jul 16, 2025
76226de
take a component
lux-v Jul 16, 2025
d78dda6
add a new line
lux-v Jul 16, 2025
7388620
add detailed component description
lux-v Jul 16, 2025
63b619b
add detailed component description
lux-v Jul 16, 2025
e277dbe
take a component
lux-v Jul 16, 2025
08e03ec
update component description
lux-v Jul 16, 2025
0ee907a
Adds a button component
Sveb Jul 17, 2025
864171d
Update COMPONENTS.md
Sveb Jul 17, 2025
1bb618a
Update COMPONENTS.md
Sveb Jul 17, 2025
54df91d
Update COMPONENTS.md
Sveb Jul 17, 2025
0ed4fbb
Clean up components.md
Sveb Jul 17, 2025
b06efac
Taking the block-collection component
Sveb Jul 21, 2025
d31c91f
Taking the grid-collections component as well.
Sveb Jul 21, 2025
94304d5
Update COMPONENTS.md
Sveb Jul 21, 2025
e626674
move the component styles to the theme files
Sveb Jul 23, 2025
279744a
Clean up component list
Sveb Jul 23, 2025
82e0566
add spacing
lux-v Jul 23, 2025
d24c553
add spacing
lux-v Jul 23, 2025
c69bfac
Merge branch 'UCLALibrary:main' into main
lux-v Aug 5, 2025
0f9c0aa
update component name
lux-v Aug 6, 2025
a10aa20
Update Button definition
lux-v Aug 11, 2025
7f9084e
Update COMPONENTS.md
lux-v Aug 11, 2025
4b9de5d
Update COMPONENTS.md
lux-v Aug 11, 2025
f3b329f
Remove duplicates
lux-v Aug 11, 2025
2f4300d
remove duplicates
lux-v Aug 11, 2025
2cde0d3
remove duplicates
lux-v Aug 11, 2025
df42ca4
Remove already existing components
lux-v Aug 11, 2025
3db154c
Merge branch 'main' into main
lux-v Aug 12, 2025
9287f6d
take a component
lux-v Aug 12, 2025
33787b8
update package
Sveb Aug 14, 2025
fe7041b
add a component
lux-v Aug 15, 2025
05911af
Take a component
Sveb Aug 15, 2025
92f95f4
adjust prop for block anchor nav
lux-v Aug 15, 2025
b876c86
take a component
lux-v Aug 15, 2025
1d0497d
Update COMPONENTS.md
lux-v Aug 18, 2025
2e670a6
Merge remote-tracking branch 'upstream/main'
Sveb Aug 18, 2025
eec9a91
Merge branch 'main' into main
lux-v Aug 18, 2025
09043d2
Merge remote-tracking branch 'upstream/main'
Sveb Aug 18, 2025
4a3040f
Taking the DropdownSingleSelect
Sveb Aug 19, 2025
99240ef
Merge remote-tracking branch 'upstream/main'
Sveb Aug 20, 2025
f72144c
Take the ButtonDropdownSearch component
Sveb Aug 20, 2025
8301316
Merge remote-tracking branch 'upstream/main'
Sveb Aug 22, 2025
3b3b82a
update PR with new spec.js files, uses the font mixins, adds the dlc …
Sveb Aug 22, 2025
69b3c6e
runs eslin:fix
Sveb Aug 22, 2025
9beaa8f
Merge branch 'UCLALibrary:main' into main
lux-v Aug 26, 2025
b1385a3
Adds the default theme to the styles
Sveb Aug 27, 2025
99d8a56
Update ExcerptPod.spec.js
Sveb Aug 27, 2025
b87f27e
Merge remote-tracking branch 'upstream/main'
Sveb Aug 27, 2025
95d50e6
adding the correct packageMenager version for netlify to see.
Sveb Aug 27, 2025
3aeb5a6
Merge pull request #30 from Sveb/installing-the-correct-pnpnm
Sveb Aug 27, 2025
16e863a
Merge remote-tracking branch 'upstream/main'
Sveb Aug 27, 2025
b40378b
Merge branch 'main' into NotesAccordion
Sveb Aug 27, 2025
9943c53
Update ExcerptPod.vue
Sveb Aug 27, 2025
8dbaec4
Adjust the position of the caret-icon when the accordion is opned.
Sveb Sep 2, 2025
3676437
removes the packageManager note for netlify
Sveb Sep 2, 2025
dfc38ff
remove redundant files
Sveb Sep 2, 2025
73a286e
Merge branch 'UCLALibrary:main' into main
lux-v Sep 2, 2025
64208d8
Merge remote-tracking branch 'upstream/main'
Sveb Sep 2, 2025
e461358
Merge branch 'main' into NotesAccordion
Sveb Sep 2, 2025
d677934
Merge branch 'main' into NotesAccordion
Sveb Sep 8, 2025
651c182
Merge branch 'main' into NotesAccordion
lux-v Oct 20, 2025
499326c
Merge branch 'main' into NotesAccordion
pghorpade Oct 20, 2025
028d02e
Merge branch 'main' into NotesAccordion
Sveb Oct 24, 2025
2d5206e
Add realistic text content
Sveb Oct 24, 2025
add5c2e
Merge branch 'main' into NotesAccordion
pghorpade Oct 27, 2025
ddeb410
Adjusts the component as per review notes
Sveb Oct 28, 2025
3a5f680
Merge branch 'main' into NotesAccordion
pghorpade Oct 31, 2025
794f739
Update ExcerptPod.spec.js
Sveb Nov 3, 2025
663db2a
Merge branch 'main' into NotesAccordion
pghorpade Nov 6, 2025
e0db569
Merge branch 'main' into NotesAccordion
pghorpade Nov 6, 2025
0be89e2
Merge branch 'main' into NotesAccordion
pghorpade Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/vue-component-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@
"engines": {
"pnpm": "^9.12.1"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'

export interface Props {
duration?: number
easing?: string
opened?: boolean
preventClose?: boolean
}

const props = withDefaults(defineProps<Props>(), {
duration: 400,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
opened: false,
preventClose: false
})

const emit = defineEmits<{
opening: []
opened: []
closing: []
closed: []
}>()

const isOpened = ref<boolean>(props.opened)
const isClosing = ref<boolean>(false)
const isExpanding = ref<boolean>(false)
const animation = ref<Animation | null>(null)
const height = ref<string>('auto')
const detailsRef = ref<HTMLDetailsElement | null>(null)
const summaryRef = ref<HTMLElement | null>(null)
const contentRef = ref<HTMLElement | null>(null)

const classes = computed(() => [
'effect-slide-toggle',
{ 'is-opened': isOpened.value },
{ 'is-closed': !isOpened.value },
{ 'is-closing': isClosing.value },
{ 'is-opening': isExpanding.value }
])

watch(() => props.opened, (newVal: boolean) => {
if (newVal)
open()

else
close()
})

function onClick(): void {
if (isClosing.value || !isOpened.value) {
open()
}
else if (isExpanding.value || isOpened.value) {
if (!props.preventClose)
close()
}
}

function close(): void {
if (!detailsRef.value || !summaryRef.value)
return

isClosing.value = true
emit('closing')

const startHeight = `${detailsRef.value.offsetHeight}px`
const endHeight = `${summaryRef.value.offsetHeight}px`

if (animation.value)
animation.value.cancel()

animation.value = detailsRef.value.animate(
{ height: [startHeight, endHeight] },
{ duration: props.duration, easing: props.easing }
)

animation.value.onfinish = () => onAnimationFinish(false)
animation.value.oncancel = () => (isClosing.value = false)
}

async function open(): Promise<void> {
if (!detailsRef.value)
return

height.value = `${detailsRef.value.offsetHeight}px`
isOpened.value = true
emit('opening')

await nextTick()
expand()
}

function expand(): void {
if (!detailsRef.value || !summaryRef.value || !contentRef.value)
return

isExpanding.value = true
const startHeight = `${detailsRef.value.offsetHeight}px`
const endHeight = `${summaryRef.value.offsetHeight + contentRef.value.offsetHeight}px`

if (animation.value)
animation.value.cancel()

animation.value = detailsRef.value.animate(
{ height: [startHeight, endHeight] },
{ duration: props.duration, easing: props.easing }
)

animation.value.onfinish = () => onAnimationFinish(true)
animation.value.oncancel = () => (isExpanding.value = false)
}

function onAnimationFinish(open: boolean): void {
isOpened.value = open
animation.value = null
isClosing.value = false
isExpanding.value = false
height.value = 'auto'
if (open)
emit('opened')
else
emit('closed')
}

// Animate to a specific height (useful for content changes)
function animateToHeight(targetHeight: number, startHeight?: number): void {
if (!detailsRef.value || !summaryRef.value)
return

const startHeightPx = startHeight ? `${startHeight}px` : `${detailsRef.value.offsetHeight}px`
const endHeight = `${targetHeight}px`

if (animation.value)
animation.value.cancel()

animation.value = detailsRef.value.animate(
{ height: [startHeightPx, endHeight] },
{ duration: props.duration, easing: props.easing }
)

animation.value.onfinish = () => {
height.value = endHeight
animation.value = null
}
animation.value.oncancel = () => {
animation.value = null
}
}

// Expose methods for parent components
defineExpose({
open,
close,
animateToHeight,
isOpened: computed(() => isOpened.value)
})
</script>

<template>
<details
ref="detailsRef"
:class="classes"
:open="isOpened"
>
<summary
ref="summaryRef"
class="summary"
@click.prevent="onClick"
>
<slot name="summary" />
</summary>
<div
ref="contentRef"
class="content"
>
<slot />
</div>
</details>
</template>

<style scoped>
.effect-slide-toggle {
overflow: hidden;
height: v-bind('height');

.summary {
display: block;
cursor: pointer;
user-select: none;

&::marker,
&::-webkit-details-marker {
display: none;
}
}
}
</style>
134 changes: 134 additions & 0 deletions packages/vue-component-library/src/lib-components/ExcerptPod.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<script setup lang="ts">
// Imports
import { computed, defineProps, ref } from 'vue'
import SvgArrowDown from 'ucla-library-design-tokens/assets/svgs/icon-caret-down.svg'
import { useTheme } from '@/composables/useTheme'
import EffectSlideToggle from '@/lib-components/EffectSlideToggle.vue'

// Props
interface ExcerptPodProps {
title: string
subtitle: string
text: string
labelOpen: string
labelClose: string
sentenceSplitCount: number
}
const props = withDefaults(defineProps<ExcerptPodProps>(), {
title: '',
subtitle: '',
text: '',
labelOpen: '',
labelClose: '',
sentenceSplitCount: 2,
})

const theme = useTheme()

// Data
const isOpen = ref(false)

// Computed
const classes = computed(() => [
'excerpt-pod',
theme?.value || '',
{ 'is-open': isOpen.value },
])

const dynamicLabel = computed(() =>
isOpen.value ? props.labelOpen : props.labelClose
)

// Split text into first two sentences and the rest
const textParts = computed(() => {
if (!props.text)
return { truncatedText: '', remainingText: '', hasMore: false }

// Simple sentence splitting - splits on period followed by space and capital letter
const sentenceRegex = /\.\s+(?=[A-Z])/g
const sentences = props.text.split(sentenceRegex)

if (sentences.length <= props.sentenceSplitCount) {
return {
truncatedText: props.text,
remainingText: '',
hasMore: false
}
}

// First X sentences
let truncatedText = sentences.slice(0, props.sentenceSplitCount).join('. ')

// Add period if not present
if (!truncatedText.endsWith('.'))
truncatedText += '.'

// Remaining text - rejoin with periods
const remainingText = ` ${sentences.slice(props.sentenceSplitCount).join('.')}`

return {
truncatedText,
remainingText,
hasMore: true,
}
})

// Methods
function toggle() {
isOpen.value = !isOpen.value
}
</script>

<template>
<div :class="classes">
<!-- Title -->
<h5
class="title"
v-html="title"
/>
<!-- Info -->
<div class="info">
<h6
class="subtitle"
v-html="subtitle"
/>
<div class="text-excerpt">
<EffectSlideToggle
:opened="isOpen"
@click.stop="toggle"
>
<!-- summary -->
<template #summary>
<div class="summary-content">
<span v-html="textParts.truncatedText" />
</div>
</template>
<!-- content -->
<div v-html="textParts.remainingText" />
</EffectSlideToggle>
<button
v-if="textParts.hasMore"
class="btn"
:aria-expanded="isOpen"
@click.stop="toggle"
>
<transition
name="fade-label"
mode="out-in"
>
<span
:key="dynamicLabel"
class="label"
v-html="dynamicLabel"
/>
</transition>
<SvgArrowDown class="caret-icon" />
</button>
</div>
</div>
</div>
</template>

<style lang="scss" scoped>
@import "@/styles/dlc/_excerpt-pod.scss";
</style>
9 changes: 9 additions & 0 deletions packages/vue-component-library/src/stories/ExcerptPod.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
describe('Funkhaus / ExcerptPod', () => {
it('Default', () => {
cy.visit(
'/iframe.html?id=funkhaus-excerptpod--default&args=&viewMode=story'
)
cy.get('.excerpt-pod').should('exist')
cy.percySnapshot('Funkhaus / ExcerptPod: Default')
})
})
Loading