@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.
- package/README.md +102 -0
- package/package.json +27 -0
- package/src/components/UdsAccordion.vue +82 -0
- package/src/components/UdsAlert.vue +61 -0
- package/src/components/UdsAvatar.vue +59 -0
- package/src/components/UdsBadge.vue +48 -0
- package/src/components/UdsBreadcrumb.vue +73 -0
- package/src/components/UdsButton.vue +43 -0
- package/src/components/UdsCard.vue +49 -0
- package/src/components/UdsCheckbox.vue +56 -0
- package/src/components/UdsCodeBlock.vue +86 -0
- package/src/components/UdsCommandPalette.vue +144 -0
- package/src/components/UdsDataTable.vue +142 -0
- package/src/components/UdsDatePicker.vue +69 -0
- package/src/components/UdsDropdown.vue +132 -0
- package/src/components/UdsFileUpload.vue +148 -0
- package/src/components/UdsFooter.vue +39 -0
- package/src/components/UdsHero.vue +44 -0
- package/src/components/UdsInput.vue +95 -0
- package/src/components/UdsModal.vue +114 -0
- package/src/components/UdsNavbar.vue +57 -0
- package/src/components/UdsPagination.vue +96 -0
- package/src/components/UdsPricing.vue +58 -0
- package/src/components/UdsProgress.vue +92 -0
- package/src/components/UdsRadio.vue +56 -0
- package/src/components/UdsSelect.vue +84 -0
- package/src/components/UdsSideNav.vue +102 -0
- package/src/components/UdsSkeleton.vue +51 -0
- package/src/components/UdsSocialProof.vue +58 -0
- package/src/components/UdsTabs.vue +106 -0
- package/src/components/UdsTestimonial.vue +57 -0
- package/src/components/UdsToast.vue +70 -0
- package/src/components/UdsToggle.vue +60 -0
- package/src/components/UdsTooltip.vue +62 -0
- package/src/index.ts +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @mkatogui/uds-vue
|
|
2
|
+
|
|
3
|
+
Vue 3 Composition API components for the Universal Design System. 32 accessible, themeable components built with `<script setup lang="ts">` and `defineProps`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mkatogui/uds-vue
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Vue 3.3+ is required as a peer dependency.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Import individual components:
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<script setup>
|
|
19
|
+
import { UdsButton, UdsInput, UdsModal } from '@mkatogui/uds-vue'
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<UdsButton variant="primary" size="md" @click="handleClick">
|
|
24
|
+
Get Started
|
|
25
|
+
</UdsButton>
|
|
26
|
+
</template>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Theming
|
|
30
|
+
|
|
31
|
+
Components use UDS design tokens via CSS custom properties and BEM class naming (`uds-{component}--{variant}`). Apply a palette with the `data-theme` attribute on a parent element:
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<div data-theme="ai-futuristic">
|
|
35
|
+
<UdsButton variant="gradient">Launch</UdsButton>
|
|
36
|
+
</div>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Available palettes: `minimal-saas`, `ai-futuristic`, `gradient-startup`, `corporate`, `apple-minimal`, `illustration`, `dashboard`, `bold-lifestyle`, `minimal-corporate`.
|
|
40
|
+
|
|
41
|
+
## Components
|
|
42
|
+
|
|
43
|
+
| Component | File | Variants |
|
|
44
|
+
|-----------|------|----------|
|
|
45
|
+
| Button | `UdsButton.vue` | primary, secondary, ghost, gradient, destructive, icon-only |
|
|
46
|
+
| Navigation Bar | `UdsNavbar.vue` | standard, minimal, dark, transparent |
|
|
47
|
+
| Hero Section | `UdsHero.vue` | centered, product-screenshot, video-bg, gradient-mesh, search-forward, split |
|
|
48
|
+
| Feature Card | `UdsCard.vue` | icon-top, image-top, horizontal, stat-card, dashboard-preview |
|
|
49
|
+
| Pricing Table | `UdsPricing.vue` | 2-column, 3-column, 4-column, toggle |
|
|
50
|
+
| Social Proof Bar | `UdsSocialProof.vue` | logo-strip, stats-counter, testimonial-mini, combined |
|
|
51
|
+
| Testimonial Card | `UdsTestimonial.vue` | quote-card, video, metric, carousel |
|
|
52
|
+
| Footer | `UdsFooter.vue` | simple, multi-column, newsletter, mega-footer |
|
|
53
|
+
| Code Block | `UdsCodeBlock.vue` | syntax-highlighted, terminal, multi-tab |
|
|
54
|
+
| Modal | `UdsModal.vue` | confirmation, task, alert |
|
|
55
|
+
| Form Input | `UdsInput.vue` | text, email, password, number, search, textarea |
|
|
56
|
+
| Select | `UdsSelect.vue` | native, custom |
|
|
57
|
+
| Checkbox | `UdsCheckbox.vue` | standard, indeterminate |
|
|
58
|
+
| Radio | `UdsRadio.vue` | standard, card |
|
|
59
|
+
| Toggle Switch | `UdsToggle.vue` | standard, with-label |
|
|
60
|
+
| Alert | `UdsAlert.vue` | success, warning, error, info |
|
|
61
|
+
| Badge | `UdsBadge.vue` | status, count, tag |
|
|
62
|
+
| Tabs | `UdsTabs.vue` | line, pill, segmented |
|
|
63
|
+
| Accordion | `UdsAccordion.vue` | single, multi, flush |
|
|
64
|
+
| Breadcrumb | `UdsBreadcrumb.vue` | standard, truncated |
|
|
65
|
+
| Tooltip | `UdsTooltip.vue` | simple, rich |
|
|
66
|
+
| Dropdown Menu | `UdsDropdown.vue` | action, context, nav-sub |
|
|
67
|
+
| Avatar | `UdsAvatar.vue` | image, initials, icon, group |
|
|
68
|
+
| Skeleton | `UdsSkeleton.vue` | text, card, avatar, table |
|
|
69
|
+
| Toast | `UdsToast.vue` | success, error, warning, info, neutral |
|
|
70
|
+
| Pagination | `UdsPagination.vue` | numbered, simple, load-more, infinite-scroll |
|
|
71
|
+
| Data Table | `UdsDataTable.vue` | basic, sortable, selectable, expandable |
|
|
72
|
+
| Date Picker | `UdsDatePicker.vue` | single, range, with-time |
|
|
73
|
+
| Command Palette | `UdsCommandPalette.vue` | standard |
|
|
74
|
+
| Progress Indicator | `UdsProgress.vue` | bar, circular, stepper |
|
|
75
|
+
| Side Navigation | `UdsSideNav.vue` | default, collapsed, with-sections |
|
|
76
|
+
| File Upload | `UdsFileUpload.vue` | dropzone, button, avatar-upload |
|
|
77
|
+
|
|
78
|
+
## Accessibility
|
|
79
|
+
|
|
80
|
+
All components follow WCAG 2.1 AA guidelines:
|
|
81
|
+
|
|
82
|
+
- Proper ARIA roles and attributes (`role`, `aria-label`, `aria-expanded`, `aria-selected`, etc.)
|
|
83
|
+
- Keyboard navigation support (arrow keys, Enter, Space, Escape, Tab)
|
|
84
|
+
- Focus management (focus traps in Modal, focus restoration)
|
|
85
|
+
- Semantic HTML elements (`nav`, `button`, `figure`, `blockquote`, `table`)
|
|
86
|
+
- Screen reader announcements via `role="alert"` and `role="status"`
|
|
87
|
+
- Form label associations via `for`/`id` pairs
|
|
88
|
+
|
|
89
|
+
## v-model Support
|
|
90
|
+
|
|
91
|
+
Form components support Vue's `v-model` directive:
|
|
92
|
+
|
|
93
|
+
```vue
|
|
94
|
+
<UdsInput v-model="email" variant="email" label="Email" />
|
|
95
|
+
<UdsSelect v-model="country" :options="countries" label="Country" />
|
|
96
|
+
<UdsCheckbox v-model="agreed" label="I agree to the terms" />
|
|
97
|
+
<UdsToggle v-model="darkMode" label="Dark mode" />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mkatogui/uds-vue",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Vue 3 components for Universal Design System — 31 accessible, themeable components",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist/", "src/"],
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"vue": ">=3.3.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["vue", "design-system", "components", "accessibility", "wcag"],
|
|
20
|
+
"author": "Marcelo Katogui",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/mkatogui/universal-design-system.git",
|
|
25
|
+
"directory": "packages/vue"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface AccordionItem {
|
|
5
|
+
title: string
|
|
6
|
+
content?: string
|
|
7
|
+
defaultExpanded?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
variant?: 'single' | 'multi' | 'flush'
|
|
12
|
+
items?: AccordionItem[]
|
|
13
|
+
allowMultiple?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
variant: 'single',
|
|
18
|
+
items: () => [],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const expandedItems = ref<Set<number>>(
|
|
22
|
+
new Set(props.items.map((item, i) => (item.defaultExpanded ? i : -1)).filter((i) => i >= 0))
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const classes = computed(() =>
|
|
26
|
+
[
|
|
27
|
+
'uds-accordion',
|
|
28
|
+
`uds-accordion--${props.variant}`,
|
|
29
|
+
]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join(' ')
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
function toggle(index: number) {
|
|
35
|
+
const next = new Set(expandedItems.value)
|
|
36
|
+
if (next.has(index)) {
|
|
37
|
+
next.delete(index)
|
|
38
|
+
} else {
|
|
39
|
+
if (!props.allowMultiple && props.variant === 'single') {
|
|
40
|
+
next.clear()
|
|
41
|
+
}
|
|
42
|
+
next.add(index)
|
|
43
|
+
}
|
|
44
|
+
expandedItems.value = next
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleKeydown(e: KeyboardEvent, index: number) {
|
|
48
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
toggle(index)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<div :class="classes">
|
|
57
|
+
<div v-for="(item, i) in items" :key="i" class="uds-accordion__item">
|
|
58
|
+
<button
|
|
59
|
+
class="uds-accordion__trigger"
|
|
60
|
+
:aria-expanded="expandedItems.has(i)"
|
|
61
|
+
:aria-controls="`accordion-panel-${i}`"
|
|
62
|
+
@click="toggle(i)"
|
|
63
|
+
@keydown="handleKeydown($event, i)"
|
|
64
|
+
>
|
|
65
|
+
<span class="uds-accordion__title">{{ item.title }}</span>
|
|
66
|
+
<span class="uds-accordion__icon" aria-hidden="true" />
|
|
67
|
+
</button>
|
|
68
|
+
<div
|
|
69
|
+
:id="`accordion-panel-${i}`"
|
|
70
|
+
class="uds-accordion__panel"
|
|
71
|
+
role="region"
|
|
72
|
+
:hidden="!expandedItems.has(i)"
|
|
73
|
+
>
|
|
74
|
+
<div class="uds-accordion__content">
|
|
75
|
+
<slot :name="`item-${i}`">
|
|
76
|
+
{{ item.content }}
|
|
77
|
+
</slot>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'success' | 'warning' | 'error' | 'info'
|
|
6
|
+
size?: 'sm' | 'md' | 'lg'
|
|
7
|
+
title?: string
|
|
8
|
+
message?: string
|
|
9
|
+
dismissible?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
13
|
+
variant: 'info',
|
|
14
|
+
size: 'md',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
dismiss: []
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const dismissed = ref(false)
|
|
22
|
+
|
|
23
|
+
const classes = computed(() =>
|
|
24
|
+
[
|
|
25
|
+
'uds-alert',
|
|
26
|
+
`uds-alert--${props.variant}`,
|
|
27
|
+
`uds-alert--${props.size}`,
|
|
28
|
+
]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join(' ')
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const alertRole = computed(() =>
|
|
34
|
+
props.variant === 'error' || props.variant === 'warning' ? 'alert' : 'status'
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
function handleDismiss() {
|
|
38
|
+
dismissed.value = true
|
|
39
|
+
emit('dismiss')
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<div v-if="!dismissed" :class="classes" :role="alertRole">
|
|
45
|
+
<div class="uds-alert__content">
|
|
46
|
+
<strong v-if="title" class="uds-alert__title">{{ title }}</strong>
|
|
47
|
+
<p v-if="message" class="uds-alert__message">{{ message }}</p>
|
|
48
|
+
<slot />
|
|
49
|
+
</div>
|
|
50
|
+
<button
|
|
51
|
+
v-if="dismissible"
|
|
52
|
+
class="uds-alert__dismiss"
|
|
53
|
+
aria-label="Dismiss alert"
|
|
54
|
+
@click="handleDismiss"
|
|
55
|
+
>
|
|
56
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
57
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'image' | 'initials' | 'icon' | 'group'
|
|
6
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
7
|
+
src?: string
|
|
8
|
+
alt?: string
|
|
9
|
+
initials?: string
|
|
10
|
+
status?: 'online' | 'offline' | 'busy'
|
|
11
|
+
fallback?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
variant: 'image',
|
|
16
|
+
size: 'md',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const imgError = ref(false)
|
|
20
|
+
|
|
21
|
+
const classes = computed(() =>
|
|
22
|
+
[
|
|
23
|
+
'uds-avatar',
|
|
24
|
+
`uds-avatar--${props.variant}`,
|
|
25
|
+
`uds-avatar--${props.size}`,
|
|
26
|
+
props.status && `uds-avatar--${props.status}`,
|
|
27
|
+
]
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join(' ')
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
function handleError() {
|
|
33
|
+
imgError.value = true
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<div :class="classes">
|
|
39
|
+
<img
|
|
40
|
+
v-if="variant === 'image' && src && !imgError"
|
|
41
|
+
:src="src"
|
|
42
|
+
:alt="alt || ''"
|
|
43
|
+
class="uds-avatar__image"
|
|
44
|
+
@error="handleError"
|
|
45
|
+
/>
|
|
46
|
+
<span v-else-if="variant === 'initials' || imgError" class="uds-avatar__initials" :aria-label="alt">
|
|
47
|
+
{{ initials || fallback || '?' }}
|
|
48
|
+
</span>
|
|
49
|
+
<span v-else class="uds-avatar__icon" :aria-label="alt">
|
|
50
|
+
<slot />
|
|
51
|
+
</span>
|
|
52
|
+
<span
|
|
53
|
+
v-if="status"
|
|
54
|
+
class="uds-avatar__status"
|
|
55
|
+
:class="`uds-avatar__status--${status}`"
|
|
56
|
+
:aria-label="status"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'status' | 'count' | 'tag'
|
|
6
|
+
size?: 'sm' | 'md'
|
|
7
|
+
label?: string
|
|
8
|
+
color?: string
|
|
9
|
+
removable?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
13
|
+
variant: 'status',
|
|
14
|
+
size: 'md',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
remove: []
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const classes = computed(() =>
|
|
22
|
+
[
|
|
23
|
+
'uds-badge',
|
|
24
|
+
`uds-badge--${props.variant}`,
|
|
25
|
+
`uds-badge--${props.size}`,
|
|
26
|
+
props.color && `uds-badge--${props.color}`,
|
|
27
|
+
props.removable && 'uds-badge--removable',
|
|
28
|
+
]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join(' ')
|
|
31
|
+
)
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<span :class="classes" :aria-label="label">
|
|
36
|
+
<slot>{{ label }}</slot>
|
|
37
|
+
<button
|
|
38
|
+
v-if="removable"
|
|
39
|
+
class="uds-badge__remove"
|
|
40
|
+
:aria-label="`Remove ${label || ''}`"
|
|
41
|
+
@click="emit('remove')"
|
|
42
|
+
>
|
|
43
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
44
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
45
|
+
</svg>
|
|
46
|
+
</button>
|
|
47
|
+
</span>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface BreadcrumbItem {
|
|
5
|
+
label: string
|
|
6
|
+
href?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
variant?: 'standard' | 'truncated'
|
|
11
|
+
items?: BreadcrumbItem[]
|
|
12
|
+
separator?: string
|
|
13
|
+
maxItems?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
variant: 'standard',
|
|
18
|
+
separator: '/',
|
|
19
|
+
items: () => [],
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const classes = computed(() =>
|
|
23
|
+
[
|
|
24
|
+
'uds-breadcrumb',
|
|
25
|
+
`uds-breadcrumb--${props.variant}`,
|
|
26
|
+
]
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.join(' ')
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const displayItems = computed(() => {
|
|
32
|
+
if (props.variant === 'truncated' && props.maxItems && props.items.length > props.maxItems) {
|
|
33
|
+
const first = props.items.slice(0, 1)
|
|
34
|
+
const last = props.items.slice(-(props.maxItems - 1))
|
|
35
|
+
return [...first, { label: '...', href: undefined } as BreadcrumbItem, ...last]
|
|
36
|
+
}
|
|
37
|
+
return props.items
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<nav :class="classes" aria-label="Breadcrumb">
|
|
43
|
+
<ol class="uds-breadcrumb__list">
|
|
44
|
+
<li
|
|
45
|
+
v-for="(item, i) in displayItems"
|
|
46
|
+
:key="i"
|
|
47
|
+
class="uds-breadcrumb__item"
|
|
48
|
+
>
|
|
49
|
+
<a
|
|
50
|
+
v-if="item.href && i < displayItems.length - 1"
|
|
51
|
+
:href="item.href"
|
|
52
|
+
class="uds-breadcrumb__link"
|
|
53
|
+
>
|
|
54
|
+
{{ item.label }}
|
|
55
|
+
</a>
|
|
56
|
+
<span
|
|
57
|
+
v-else
|
|
58
|
+
class="uds-breadcrumb__current"
|
|
59
|
+
:aria-current="i === displayItems.length - 1 ? 'page' : undefined"
|
|
60
|
+
>
|
|
61
|
+
{{ item.label }}
|
|
62
|
+
</span>
|
|
63
|
+
<span
|
|
64
|
+
v-if="i < displayItems.length - 1"
|
|
65
|
+
class="uds-breadcrumb__separator"
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
>
|
|
68
|
+
{{ separator }}
|
|
69
|
+
</span>
|
|
70
|
+
</li>
|
|
71
|
+
</ol>
|
|
72
|
+
</nav>
|
|
73
|
+
</template>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'gradient' | 'destructive' | 'icon-only'
|
|
6
|
+
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
7
|
+
loading?: boolean
|
|
8
|
+
fullWidth?: boolean
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
iconLeft?: boolean
|
|
11
|
+
iconRight?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
variant: 'primary',
|
|
16
|
+
size: 'md',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const classes = computed(() =>
|
|
20
|
+
[
|
|
21
|
+
'uds-btn',
|
|
22
|
+
`uds-btn--${props.variant}`,
|
|
23
|
+
`uds-btn--${props.size}`,
|
|
24
|
+
props.fullWidth && 'uds-btn--full-width',
|
|
25
|
+
props.loading && 'uds-btn--loading',
|
|
26
|
+
]
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.join(' ')
|
|
29
|
+
)
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<button
|
|
34
|
+
:class="classes"
|
|
35
|
+
:disabled="disabled || loading"
|
|
36
|
+
:aria-busy="loading || undefined"
|
|
37
|
+
:aria-disabled="disabled || undefined"
|
|
38
|
+
role="button"
|
|
39
|
+
>
|
|
40
|
+
<span v-if="loading" class="uds-btn__spinner" aria-hidden="true" />
|
|
41
|
+
<slot />
|
|
42
|
+
</button>
|
|
43
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'icon-top' | 'image-top' | 'horizontal' | 'stat-card' | 'dashboard-preview'
|
|
6
|
+
size?: 'sm' | 'md' | 'lg'
|
|
7
|
+
title?: string
|
|
8
|
+
description?: string
|
|
9
|
+
link?: string
|
|
10
|
+
image?: string
|
|
11
|
+
imageAlt?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
variant: 'icon-top',
|
|
16
|
+
size: 'md',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const classes = computed(() =>
|
|
20
|
+
[
|
|
21
|
+
'uds-card',
|
|
22
|
+
`uds-card--${props.variant}`,
|
|
23
|
+
`uds-card--${props.size}`,
|
|
24
|
+
]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')
|
|
27
|
+
)
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<div :class="classes">
|
|
32
|
+
<div v-if="image" class="uds-card__image">
|
|
33
|
+
<img :src="image" :alt="imageAlt || ''" />
|
|
34
|
+
</div>
|
|
35
|
+
<div class="uds-card__icon">
|
|
36
|
+
<slot name="icon" />
|
|
37
|
+
</div>
|
|
38
|
+
<div class="uds-card__body">
|
|
39
|
+
<h3 v-if="title" class="uds-card__title">{{ title }}</h3>
|
|
40
|
+
<p v-if="description" class="uds-card__description">{{ description }}</p>
|
|
41
|
+
<slot />
|
|
42
|
+
</div>
|
|
43
|
+
<div v-if="link" class="uds-card__link">
|
|
44
|
+
<a :href="link">
|
|
45
|
+
<slot name="link-text">Learn more</slot>
|
|
46
|
+
</a>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useId } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'standard' | 'indeterminate'
|
|
6
|
+
checked?: boolean
|
|
7
|
+
indeterminate?: boolean
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
label?: string
|
|
10
|
+
name?: string
|
|
11
|
+
modelValue?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
variant: 'standard',
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
'update:modelValue': [value: boolean]
|
|
20
|
+
}>()
|
|
21
|
+
|
|
22
|
+
const checkboxId = useId()
|
|
23
|
+
|
|
24
|
+
const classes = computed(() =>
|
|
25
|
+
[
|
|
26
|
+
'uds-checkbox',
|
|
27
|
+
`uds-checkbox--${props.variant}`,
|
|
28
|
+
props.disabled && 'uds-checkbox--disabled',
|
|
29
|
+
props.indeterminate && 'uds-checkbox--indeterminate',
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(' ')
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
function handleChange(e: Event) {
|
|
36
|
+
const target = e.target as HTMLInputElement
|
|
37
|
+
emit('update:modelValue', target.checked)
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div :class="classes">
|
|
43
|
+
<input
|
|
44
|
+
:id="checkboxId"
|
|
45
|
+
type="checkbox"
|
|
46
|
+
class="uds-checkbox__input"
|
|
47
|
+
:checked="modelValue ?? checked"
|
|
48
|
+
:indeterminate="indeterminate"
|
|
49
|
+
:disabled="disabled"
|
|
50
|
+
:name="name"
|
|
51
|
+
:aria-checked="indeterminate ? 'mixed' : undefined"
|
|
52
|
+
@change="handleChange"
|
|
53
|
+
/>
|
|
54
|
+
<label v-if="label" :for="checkboxId" class="uds-checkbox__label">{{ label }}</label>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Tab {
|
|
5
|
+
label: string
|
|
6
|
+
language: string
|
|
7
|
+
code: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
variant?: 'syntax-highlighted' | 'terminal' | 'multi-tab'
|
|
12
|
+
size?: 'sm' | 'md' | 'lg'
|
|
13
|
+
language?: string
|
|
14
|
+
code?: string
|
|
15
|
+
showLineNumbers?: boolean
|
|
16
|
+
showCopy?: boolean
|
|
17
|
+
tabs?: Tab[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
21
|
+
variant: 'syntax-highlighted',
|
|
22
|
+
size: 'md',
|
|
23
|
+
showCopy: true,
|
|
24
|
+
tabs: () => [],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const copied = ref(false)
|
|
28
|
+
const activeTab = ref(0)
|
|
29
|
+
|
|
30
|
+
const classes = computed(() =>
|
|
31
|
+
[
|
|
32
|
+
'uds-code-block',
|
|
33
|
+
`uds-code-block--${props.variant}`,
|
|
34
|
+
`uds-code-block--${props.size}`,
|
|
35
|
+
props.showLineNumbers && 'uds-code-block--line-numbers',
|
|
36
|
+
copied.value && 'uds-code-block--copied',
|
|
37
|
+
]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(' ')
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const displayCode = computed(() => {
|
|
43
|
+
if (props.variant === 'multi-tab' && props.tabs.length) {
|
|
44
|
+
return props.tabs[activeTab.value]?.code || ''
|
|
45
|
+
}
|
|
46
|
+
return props.code || ''
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
async function copyToClipboard() {
|
|
50
|
+
try {
|
|
51
|
+
await navigator.clipboard.writeText(displayCode.value)
|
|
52
|
+
copied.value = true
|
|
53
|
+
setTimeout(() => { copied.value = false }, 2000)
|
|
54
|
+
} catch {
|
|
55
|
+
// Clipboard API not available
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<div :class="classes">
|
|
62
|
+
<div v-if="variant === 'multi-tab' && tabs.length" class="uds-code-block__tabs" role="tablist">
|
|
63
|
+
<button
|
|
64
|
+
v-for="(tab, i) in tabs"
|
|
65
|
+
:key="i"
|
|
66
|
+
role="tab"
|
|
67
|
+
:aria-selected="activeTab === i"
|
|
68
|
+
class="uds-code-block__tab"
|
|
69
|
+
@click="activeTab = i"
|
|
70
|
+
>
|
|
71
|
+
{{ tab.label }}
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="uds-code-block__container">
|
|
75
|
+
<button
|
|
76
|
+
v-if="showCopy"
|
|
77
|
+
class="uds-code-block__copy"
|
|
78
|
+
aria-label="Copy code to clipboard"
|
|
79
|
+
@click="copyToClipboard"
|
|
80
|
+
>
|
|
81
|
+
{{ copied ? 'Copied' : 'Copy' }}
|
|
82
|
+
</button>
|
|
83
|
+
<pre><code :class="language ? `language-${language}` : undefined">{{ displayCode }}</code></pre>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|