@mkatogui/uds-vue 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +102 -0
  2. package/package.json +27 -0
  3. package/src/components/UdsAccordion.vue +82 -0
  4. package/src/components/UdsAlert.vue +61 -0
  5. package/src/components/UdsAvatar.vue +59 -0
  6. package/src/components/UdsBadge.vue +48 -0
  7. package/src/components/UdsBreadcrumb.vue +73 -0
  8. package/src/components/UdsButton.vue +43 -0
  9. package/src/components/UdsCard.vue +49 -0
  10. package/src/components/UdsCheckbox.vue +56 -0
  11. package/src/components/UdsCodeBlock.vue +86 -0
  12. package/src/components/UdsCommandPalette.vue +144 -0
  13. package/src/components/UdsDataTable.vue +142 -0
  14. package/src/components/UdsDatePicker.vue +69 -0
  15. package/src/components/UdsDropdown.vue +132 -0
  16. package/src/components/UdsFileUpload.vue +148 -0
  17. package/src/components/UdsFooter.vue +39 -0
  18. package/src/components/UdsHero.vue +44 -0
  19. package/src/components/UdsInput.vue +95 -0
  20. package/src/components/UdsModal.vue +114 -0
  21. package/src/components/UdsNavbar.vue +57 -0
  22. package/src/components/UdsPagination.vue +96 -0
  23. package/src/components/UdsPricing.vue +58 -0
  24. package/src/components/UdsProgress.vue +92 -0
  25. package/src/components/UdsRadio.vue +56 -0
  26. package/src/components/UdsSelect.vue +84 -0
  27. package/src/components/UdsSideNav.vue +102 -0
  28. package/src/components/UdsSkeleton.vue +51 -0
  29. package/src/components/UdsSocialProof.vue +58 -0
  30. package/src/components/UdsTabs.vue +106 -0
  31. package/src/components/UdsTestimonial.vue +57 -0
  32. package/src/components/UdsToast.vue +70 -0
  33. package/src/components/UdsToggle.vue +60 -0
  34. package/src/components/UdsTooltip.vue +62 -0
  35. package/src/index.ts +32 -0
@@ -0,0 +1,102 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ interface NavItem {
5
+ label: string
6
+ href?: string
7
+ icon?: string
8
+ active?: boolean
9
+ children?: NavItem[]
10
+ }
11
+
12
+ interface Section {
13
+ title: string
14
+ items: NavItem[]
15
+ }
16
+
17
+ interface Props {
18
+ variant?: 'default' | 'collapsed' | 'with-sections'
19
+ collapsed?: boolean
20
+ items?: NavItem[]
21
+ sections?: Section[]
22
+ activeItem?: string
23
+ }
24
+
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ variant: 'default',
27
+ items: () => [],
28
+ sections: () => [],
29
+ })
30
+
31
+ const emit = defineEmits<{
32
+ navigate: [href: string]
33
+ }>()
34
+
35
+ const classes = computed(() =>
36
+ [
37
+ 'uds-side-nav',
38
+ `uds-side-nav--${props.variant}`,
39
+ props.collapsed && 'uds-side-nav--collapsed',
40
+ ]
41
+ .filter(Boolean)
42
+ .join(' ')
43
+ )
44
+
45
+ function handleNavigate(href: string) {
46
+ emit('navigate', href)
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <nav :class="classes" aria-label="Side navigation">
52
+ <template v-if="variant === 'with-sections' && sections.length">
53
+ <div v-for="(section, si) in sections" :key="si" class="uds-side-nav__section">
54
+ <h3 class="uds-side-nav__section-title">{{ section.title }}</h3>
55
+ <ul class="uds-side-nav__list">
56
+ <li v-for="(item, i) in section.items" :key="i" class="uds-side-nav__item">
57
+ <a
58
+ :href="item.href || '#'"
59
+ class="uds-side-nav__link"
60
+ :class="{ 'uds-side-nav__link--active': item.active || item.href === activeItem }"
61
+ :aria-current="item.active || item.href === activeItem ? 'page' : undefined"
62
+ @click.prevent="handleNavigate(item.href || '')"
63
+ >
64
+ <span v-if="item.icon" class="uds-side-nav__icon" aria-hidden="true">{{ item.icon }}</span>
65
+ <span v-if="!collapsed" class="uds-side-nav__label">{{ item.label }}</span>
66
+ </a>
67
+ </li>
68
+ </ul>
69
+ </div>
70
+ </template>
71
+ <template v-else>
72
+ <ul class="uds-side-nav__list">
73
+ <li v-for="(item, i) in items" :key="i" class="uds-side-nav__item">
74
+ <a
75
+ :href="item.href || '#'"
76
+ class="uds-side-nav__link"
77
+ :class="{ 'uds-side-nav__link--active': item.active || item.href === activeItem }"
78
+ :aria-current="item.active || item.href === activeItem ? 'page' : undefined"
79
+ :aria-expanded="item.children?.length ? undefined : undefined"
80
+ @click.prevent="handleNavigate(item.href || '')"
81
+ >
82
+ <span v-if="item.icon" class="uds-side-nav__icon" aria-hidden="true">{{ item.icon }}</span>
83
+ <span v-if="!collapsed" class="uds-side-nav__label">{{ item.label }}</span>
84
+ </a>
85
+ <ul v-if="item.children?.length && !collapsed" class="uds-side-nav__sublist">
86
+ <li v-for="(child, ci) in item.children" :key="ci" class="uds-side-nav__subitem">
87
+ <a
88
+ :href="child.href || '#'"
89
+ class="uds-side-nav__sublink"
90
+ :class="{ 'uds-side-nav__sublink--active': child.active || child.href === activeItem }"
91
+ :aria-current="child.active || child.href === activeItem ? 'page' : undefined"
92
+ @click.prevent="handleNavigate(child.href || '')"
93
+ >
94
+ {{ child.label }}
95
+ </a>
96
+ </li>
97
+ </ul>
98
+ </li>
99
+ </ul>
100
+ </template>
101
+ </nav>
102
+ </template>
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ interface Props {
5
+ variant?: 'text' | 'card' | 'avatar' | 'table'
6
+ size?: 'sm' | 'md' | 'lg'
7
+ lines?: number
8
+ animated?: boolean
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), {
12
+ variant: 'text',
13
+ size: 'md',
14
+ lines: 3,
15
+ animated: true,
16
+ })
17
+
18
+ const classes = computed(() =>
19
+ [
20
+ 'uds-skeleton',
21
+ `uds-skeleton--${props.variant}`,
22
+ `uds-skeleton--${props.size}`,
23
+ props.animated && 'uds-skeleton--animated',
24
+ ]
25
+ .filter(Boolean)
26
+ .join(' ')
27
+ )
28
+ </script>
29
+
30
+ <template>
31
+ <div :class="classes" aria-busy="true" aria-hidden="true">
32
+ <template v-if="variant === 'text'">
33
+ <div v-for="i in lines" :key="i" class="uds-skeleton__line" />
34
+ </template>
35
+ <template v-else-if="variant === 'avatar'">
36
+ <div class="uds-skeleton__circle" />
37
+ </template>
38
+ <template v-else-if="variant === 'card'">
39
+ <div class="uds-skeleton__image" />
40
+ <div class="uds-skeleton__line" />
41
+ <div class="uds-skeleton__line uds-skeleton__line--short" />
42
+ </template>
43
+ <template v-else-if="variant === 'table'">
44
+ <div v-for="i in lines" :key="i" class="uds-skeleton__row">
45
+ <div class="uds-skeleton__cell" />
46
+ <div class="uds-skeleton__cell" />
47
+ <div class="uds-skeleton__cell" />
48
+ </div>
49
+ </template>
50
+ </div>
51
+ </template>
@@ -0,0 +1,58 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ interface Logo {
5
+ src: string
6
+ alt: string
7
+ }
8
+
9
+ interface Stat {
10
+ value: string
11
+ label: string
12
+ }
13
+
14
+ interface Props {
15
+ variant?: 'logo-strip' | 'stats-counter' | 'testimonial-mini' | 'combined'
16
+ size?: 'standard' | 'compact'
17
+ logos?: Logo[]
18
+ stats?: Stat[]
19
+ }
20
+
21
+ const props = withDefaults(defineProps<Props>(), {
22
+ variant: 'logo-strip',
23
+ size: 'standard',
24
+ logos: () => [],
25
+ stats: () => [],
26
+ })
27
+
28
+ const classes = computed(() =>
29
+ [
30
+ 'uds-social-proof',
31
+ `uds-social-proof--${props.variant}`,
32
+ `uds-social-proof--${props.size}`,
33
+ ]
34
+ .filter(Boolean)
35
+ .join(' ')
36
+ )
37
+ </script>
38
+
39
+ <template>
40
+ <div :class="classes">
41
+ <div v-if="logos.length" class="uds-social-proof__logos">
42
+ <img
43
+ v-for="(logo, i) in logos"
44
+ :key="i"
45
+ :src="logo.src"
46
+ :alt="logo.alt"
47
+ class="uds-social-proof__logo"
48
+ />
49
+ </div>
50
+ <div v-if="stats.length" class="uds-social-proof__stats">
51
+ <div v-for="(stat, i) in stats" :key="i" class="uds-social-proof__stat">
52
+ <span class="uds-social-proof__stat-value">{{ stat.value }}</span>
53
+ <span class="uds-social-proof__stat-label">{{ stat.label }}</span>
54
+ </div>
55
+ </div>
56
+ <slot />
57
+ </div>
58
+ </template>
@@ -0,0 +1,106 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, useId } from 'vue'
3
+
4
+ interface TabItem {
5
+ label: string
6
+ disabled?: boolean
7
+ }
8
+
9
+ interface Props {
10
+ variant?: 'line' | 'pill' | 'segmented'
11
+ size?: 'sm' | 'md' | 'lg'
12
+ tabs: TabItem[]
13
+ defaultActive?: number
14
+ }
15
+
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ variant: 'line',
18
+ size: 'md',
19
+ defaultActive: 0,
20
+ })
21
+
22
+ const emit = defineEmits<{
23
+ change: [index: number]
24
+ }>()
25
+
26
+ const activeTab = ref(props.defaultActive)
27
+ const baseId = useId()
28
+ const tabRefs = ref<(HTMLElement | null)[]>([])
29
+
30
+ const classes = computed(() =>
31
+ [
32
+ 'uds-tabs',
33
+ `uds-tabs--${props.variant}`,
34
+ `uds-tabs--${props.size}`,
35
+ ]
36
+ .filter(Boolean)
37
+ .join(' ')
38
+ )
39
+
40
+ function selectTab(index: number) {
41
+ if (props.tabs[index]?.disabled) return
42
+ activeTab.value = index
43
+ emit('change', index)
44
+ }
45
+
46
+ function handleKeydown(e: KeyboardEvent) {
47
+ const enabledIndices = props.tabs
48
+ .map((t, i) => (t.disabled ? -1 : i))
49
+ .filter((i) => i >= 0)
50
+ const currentPos = enabledIndices.indexOf(activeTab.value)
51
+ let nextIndex: number | undefined
52
+
53
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
54
+ e.preventDefault()
55
+ nextIndex = enabledIndices[(currentPos + 1) % enabledIndices.length]
56
+ } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
57
+ e.preventDefault()
58
+ nextIndex = enabledIndices[(currentPos - 1 + enabledIndices.length) % enabledIndices.length]
59
+ } else if (e.key === 'Home') {
60
+ e.preventDefault()
61
+ nextIndex = enabledIndices[0]
62
+ } else if (e.key === 'End') {
63
+ e.preventDefault()
64
+ nextIndex = enabledIndices[enabledIndices.length - 1]
65
+ }
66
+
67
+ if (nextIndex !== undefined) {
68
+ selectTab(nextIndex)
69
+ tabRefs.value[nextIndex]?.focus()
70
+ }
71
+ }
72
+ </script>
73
+
74
+ <template>
75
+ <div :class="classes">
76
+ <div class="uds-tabs__list" role="tablist" aria-orientation="horizontal" @keydown="handleKeydown">
77
+ <button
78
+ v-for="(tab, i) in tabs"
79
+ :key="i"
80
+ :ref="(el) => { tabRefs[i] = el as HTMLElement }"
81
+ class="uds-tabs__trigger"
82
+ role="tab"
83
+ :id="`${baseId}-tab-${i}`"
84
+ :aria-selected="activeTab === i"
85
+ :aria-controls="`${baseId}-panel-${i}`"
86
+ :tabindex="activeTab === i ? 0 : -1"
87
+ :disabled="tab.disabled"
88
+ @click="selectTab(i)"
89
+ >
90
+ {{ tab.label }}
91
+ </button>
92
+ </div>
93
+ <div
94
+ v-for="(tab, i) in tabs"
95
+ :key="i"
96
+ class="uds-tabs__panel"
97
+ role="tabpanel"
98
+ :id="`${baseId}-panel-${i}`"
99
+ :aria-labelledby="`${baseId}-tab-${i}`"
100
+ :hidden="activeTab !== i"
101
+ :tabindex="0"
102
+ >
103
+ <slot :name="`tab-${i}`" />
104
+ </div>
105
+ </div>
106
+ </template>
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ interface Props {
5
+ variant?: 'quote-card' | 'video' | 'metric' | 'carousel'
6
+ size?: 'sm' | 'md' | 'lg'
7
+ quote?: string
8
+ name?: string
9
+ title?: string
10
+ company?: string
11
+ avatar?: string
12
+ rating?: number
13
+ }
14
+
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ variant: 'quote-card',
17
+ size: 'md',
18
+ })
19
+
20
+ const classes = computed(() =>
21
+ [
22
+ 'uds-testimonial',
23
+ `uds-testimonial--${props.variant}`,
24
+ `uds-testimonial--${props.size}`,
25
+ ]
26
+ .filter(Boolean)
27
+ .join(' ')
28
+ )
29
+ </script>
30
+
31
+ <template>
32
+ <figure :class="classes">
33
+ <blockquote v-if="quote" class="uds-testimonial__quote">
34
+ <p>{{ quote }}</p>
35
+ </blockquote>
36
+ <slot />
37
+ <figcaption class="uds-testimonial__attribution">
38
+ <img
39
+ v-if="avatar"
40
+ :src="avatar"
41
+ :alt="`Photo of ${name || ''}`"
42
+ class="uds-testimonial__avatar"
43
+ />
44
+ <div class="uds-testimonial__info">
45
+ <cite v-if="name" class="uds-testimonial__name">{{ name }}</cite>
46
+ <span v-if="title || company" class="uds-testimonial__role">
47
+ {{ [title, company].filter(Boolean).join(', ') }}
48
+ </span>
49
+ </div>
50
+ <div v-if="rating" class="uds-testimonial__rating" :aria-label="`${rating} out of 5 stars`">
51
+ <span v-for="i in 5" :key="i" :class="i <= rating ? 'uds-testimonial__star--filled' : 'uds-testimonial__star'" aria-hidden="true">
52
+ &#9733;
53
+ </span>
54
+ </div>
55
+ </figcaption>
56
+ </figure>
57
+ </template>
@@ -0,0 +1,70 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref } from 'vue'
3
+
4
+ interface Props {
5
+ variant?: 'success' | 'error' | 'warning' | 'info' | 'neutral'
6
+ size?: 'sm' | 'md' | 'lg'
7
+ message?: string
8
+ duration?: number
9
+ action?: string
10
+ }
11
+
12
+ const props = withDefaults(defineProps<Props>(), {
13
+ variant: 'info',
14
+ size: 'md',
15
+ duration: 5000,
16
+ })
17
+
18
+ const emit = defineEmits<{
19
+ dismiss: []
20
+ action: []
21
+ }>()
22
+
23
+ const visible = ref(true)
24
+
25
+ const classes = computed(() =>
26
+ [
27
+ 'uds-toast',
28
+ `uds-toast--${props.variant}`,
29
+ `uds-toast--${props.size}`,
30
+ visible.value && 'uds-toast--visible',
31
+ ]
32
+ .filter(Boolean)
33
+ .join(' ')
34
+ )
35
+
36
+ const toastRole = computed(() =>
37
+ props.variant === 'error' || props.variant === 'warning' ? 'alert' : 'status'
38
+ )
39
+
40
+ function dismiss() {
41
+ visible.value = false
42
+ emit('dismiss')
43
+ }
44
+
45
+ onMounted(() => {
46
+ if (props.duration > 0) {
47
+ setTimeout(dismiss, props.duration)
48
+ }
49
+ })
50
+ </script>
51
+
52
+ <template>
53
+ <div v-if="visible" :class="classes" :role="toastRole">
54
+ <p class="uds-toast__message">
55
+ <slot>{{ message }}</slot>
56
+ </p>
57
+ <button
58
+ v-if="action"
59
+ class="uds-toast__action"
60
+ @click="emit('action')"
61
+ >
62
+ {{ action }}
63
+ </button>
64
+ <button class="uds-toast__dismiss" aria-label="Dismiss notification" @click="dismiss">
65
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
66
+ <path d="M18 6L6 18M6 6l12 12" />
67
+ </svg>
68
+ </button>
69
+ </div>
70
+ </template>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { computed, useId } from 'vue'
3
+
4
+ interface Props {
5
+ variant?: 'standard' | 'with-label'
6
+ checked?: boolean
7
+ disabled?: boolean
8
+ label?: string
9
+ modelValue?: boolean
10
+ }
11
+
12
+ const props = withDefaults(defineProps<Props>(), {
13
+ variant: 'standard',
14
+ })
15
+
16
+ const emit = defineEmits<{
17
+ 'update:modelValue': [value: boolean]
18
+ change: [value: boolean]
19
+ }>()
20
+
21
+ const toggleId = useId()
22
+
23
+ const classes = computed(() =>
24
+ [
25
+ 'uds-toggle',
26
+ `uds-toggle--${props.variant}`,
27
+ props.disabled && 'uds-toggle--disabled',
28
+ (props.modelValue ?? props.checked) && 'uds-toggle--on',
29
+ ]
30
+ .filter(Boolean)
31
+ .join(' ')
32
+ )
33
+
34
+ function handleClick() {
35
+ if (props.disabled) return
36
+ const next = !(props.modelValue ?? props.checked)
37
+ emit('update:modelValue', next)
38
+ emit('change', next)
39
+ }
40
+ </script>
41
+
42
+ <template>
43
+ <div :class="classes">
44
+ <button
45
+ :id="toggleId"
46
+ type="button"
47
+ role="switch"
48
+ class="uds-toggle__track"
49
+ :aria-checked="modelValue ?? checked ?? false"
50
+ :aria-label="label"
51
+ :disabled="disabled"
52
+ @click="handleClick"
53
+ >
54
+ <span class="uds-toggle__thumb" aria-hidden="true" />
55
+ </button>
56
+ <label v-if="variant === 'with-label' && label" :for="toggleId" class="uds-toggle__label">
57
+ {{ label }}
58
+ </label>
59
+ </div>
60
+ </template>
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, useId } from 'vue'
3
+
4
+ interface Props {
5
+ variant?: 'simple' | 'rich'
6
+ size?: 'sm' | 'md'
7
+ content?: string
8
+ position?: 'top' | 'bottom' | 'left' | 'right'
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), {
12
+ variant: 'simple',
13
+ size: 'sm',
14
+ position: 'top',
15
+ })
16
+
17
+ const visible = ref(false)
18
+ const tooltipId = useId()
19
+
20
+ const classes = computed(() =>
21
+ [
22
+ 'uds-tooltip',
23
+ `uds-tooltip--${props.variant}`,
24
+ `uds-tooltip--${props.size}`,
25
+ `uds-tooltip--${props.position}`,
26
+ visible.value && 'uds-tooltip--visible',
27
+ ]
28
+ .filter(Boolean)
29
+ .join(' ')
30
+ )
31
+
32
+ function show() {
33
+ visible.value = true
34
+ }
35
+
36
+ function hide() {
37
+ visible.value = false
38
+ }
39
+ </script>
40
+
41
+ <template>
42
+ <div :class="classes">
43
+ <div
44
+ class="uds-tooltip__trigger"
45
+ :aria-describedby="tooltipId"
46
+ @mouseenter="show"
47
+ @mouseleave="hide"
48
+ @focusin="show"
49
+ @focusout="hide"
50
+ >
51
+ <slot />
52
+ </div>
53
+ <div
54
+ v-if="visible"
55
+ :id="tooltipId"
56
+ role="tooltip"
57
+ class="uds-tooltip__content"
58
+ >
59
+ <slot name="content">{{ content }}</slot>
60
+ </div>
61
+ </div>
62
+ </template>
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ export { default as UdsButton } from './components/UdsButton.vue'
2
+ export { default as UdsNavbar } from './components/UdsNavbar.vue'
3
+ export { default as UdsHero } from './components/UdsHero.vue'
4
+ export { default as UdsCard } from './components/UdsCard.vue'
5
+ export { default as UdsPricing } from './components/UdsPricing.vue'
6
+ export { default as UdsSocialProof } from './components/UdsSocialProof.vue'
7
+ export { default as UdsTestimonial } from './components/UdsTestimonial.vue'
8
+ export { default as UdsFooter } from './components/UdsFooter.vue'
9
+ export { default as UdsCodeBlock } from './components/UdsCodeBlock.vue'
10
+ export { default as UdsModal } from './components/UdsModal.vue'
11
+ export { default as UdsInput } from './components/UdsInput.vue'
12
+ export { default as UdsSelect } from './components/UdsSelect.vue'
13
+ export { default as UdsCheckbox } from './components/UdsCheckbox.vue'
14
+ export { default as UdsRadio } from './components/UdsRadio.vue'
15
+ export { default as UdsToggle } from './components/UdsToggle.vue'
16
+ export { default as UdsAlert } from './components/UdsAlert.vue'
17
+ export { default as UdsBadge } from './components/UdsBadge.vue'
18
+ export { default as UdsTabs } from './components/UdsTabs.vue'
19
+ export { default as UdsAccordion } from './components/UdsAccordion.vue'
20
+ export { default as UdsBreadcrumb } from './components/UdsBreadcrumb.vue'
21
+ export { default as UdsTooltip } from './components/UdsTooltip.vue'
22
+ export { default as UdsDropdown } from './components/UdsDropdown.vue'
23
+ export { default as UdsAvatar } from './components/UdsAvatar.vue'
24
+ export { default as UdsSkeleton } from './components/UdsSkeleton.vue'
25
+ export { default as UdsToast } from './components/UdsToast.vue'
26
+ export { default as UdsPagination } from './components/UdsPagination.vue'
27
+ export { default as UdsDataTable } from './components/UdsDataTable.vue'
28
+ export { default as UdsDatePicker } from './components/UdsDatePicker.vue'
29
+ export { default as UdsCommandPalette } from './components/UdsCommandPalette.vue'
30
+ export { default as UdsProgress } from './components/UdsProgress.vue'
31
+ export { default as UdsSideNav } from './components/UdsSideNav.vue'
32
+ export { default as UdsFileUpload } from './components/UdsFileUpload.vue'