vunor 0.0.2
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 +33 -0
- package/dist/theme.d.ts +133 -0
- package/dist/theme.mjs +1149 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.mjs +13 -0
- package/package.json +83 -0
- package/src/components/AppLayout/AppLayout.vue +114 -0
- package/src/components/Button/Button.vue +49 -0
- package/src/components/Button/ButtonBase.vue +43 -0
- package/src/components/Button/index.ts +1 -0
- package/src/components/Button/shortcuts.ts +29 -0
- package/src/components/Card/Card.vue +47 -0
- package/src/components/Card/CardHeader.vue +26 -0
- package/src/components/Card/CardInner.vue +5 -0
- package/src/components/Card/index.ts +3 -0
- package/src/components/Card/pi.ts +46 -0
- package/src/components/Card/shortcuts.ts +19 -0
- package/src/components/Checkbox/Checkbox.vue +59 -0
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Checkbox/shortcuts.ts +27 -0
- package/src/components/Combobox/Combobox.vue +502 -0
- package/src/components/Combobox/index.ts +2 -0
- package/src/components/Combobox/shortcuts.ts +12 -0
- package/src/components/Combobox/types.ts +33 -0
- package/src/components/Icon/Icon.vue +9 -0
- package/src/components/Icon/index.ts +1 -0
- package/src/components/Input/Input.vue +109 -0
- package/src/components/Input/InputShell.vue +133 -0
- package/src/components/Input/index.ts +4 -0
- package/src/components/Input/pi.ts +18 -0
- package/src/components/Input/types.ts +52 -0
- package/src/components/Input/utils.ts +108 -0
- package/src/components/Label/Label.vue +6 -0
- package/src/components/Label/index.ts +1 -0
- package/src/components/Loading/LoadingIndicator.vue +23 -0
- package/src/components/Loading/index.ts +1 -0
- package/src/components/Loading/shortcuts.ts +9 -0
- package/src/components/Menu/Menu.vue +100 -0
- package/src/components/Menu/MenuItem.vue +20 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/Menu/shortcuts.ts +6 -0
- package/src/components/OverflowContainer/OverflowContainer.vue +120 -0
- package/src/components/OverflowContainer/index.ts +1 -0
- package/src/components/Pagination/Pagination.vue +71 -0
- package/src/components/Popover/Popover.vue +58 -0
- package/src/components/Popover/index.ts +1 -0
- package/src/components/RadioGroup/RadioGroup.vue +83 -0
- package/src/components/RadioGroup/index.ts +1 -0
- package/src/components/RadioGroup/shortcuts.ts +34 -0
- package/src/components/Select/Select.vue +93 -0
- package/src/components/Select/SelectBase.vue +148 -0
- package/src/components/Select/index.ts +3 -0
- package/src/components/Select/shortcuts.ts +30 -0
- package/src/components/Select/types.ts +30 -0
- package/src/components/Slider/Slider.vue +73 -0
- package/src/components/Slider/index.ts +1 -0
- package/src/components/Slider/shortcuts.ts +23 -0
- package/src/components/shortcuts.ts +21 -0
- package/src/components/utils/index.ts +1 -0
- package/src/components/utils/provide-inject.ts +39 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
total: number
|
|
4
|
+
defaultPage?: number
|
|
5
|
+
showEdges?: boolean
|
|
6
|
+
siblingCount?: number
|
|
7
|
+
itemsPerPage?: number
|
|
8
|
+
showArrows?: boolean
|
|
9
|
+
}>()
|
|
10
|
+
const modelValue = defineModel<number>()
|
|
11
|
+
const emit = defineEmits<{
|
|
12
|
+
(name: 'update:page', v: number): void
|
|
13
|
+
}>()
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<PaginationRoot
|
|
18
|
+
:total
|
|
19
|
+
:itemsPerPage
|
|
20
|
+
:sibling-count
|
|
21
|
+
:show-edges
|
|
22
|
+
:default-page
|
|
23
|
+
v-model:page="modelValue"
|
|
24
|
+
@update:page="emit('update:page', $event)"
|
|
25
|
+
>
|
|
26
|
+
<PaginationList v-slot="{ items }" class="flex items-center gap-1">
|
|
27
|
+
<PaginationFirst
|
|
28
|
+
v-if="!showEdges && showArrows"
|
|
29
|
+
class="size-fingertip flex items-center justify-center btn btn-square c8-flat disabled:opacity-30"
|
|
30
|
+
>
|
|
31
|
+
<VuIcon name="i--chevron-left-2" class="btn-icon btn-icon-left" />
|
|
32
|
+
</PaginationFirst>
|
|
33
|
+
<PaginationPrev
|
|
34
|
+
v-if="showArrows"
|
|
35
|
+
class="size-fingertip flex items-center justify-center mr-4 btn btn-square c8-flat disabled:opacity-30"
|
|
36
|
+
>
|
|
37
|
+
<VuIcon name="i--chevron-left" class="btn-icon btn-icon-left" />
|
|
38
|
+
</PaginationPrev>
|
|
39
|
+
<template v-for="(page, index) in items">
|
|
40
|
+
<PaginationListItem
|
|
41
|
+
v-if="page.type === 'page'"
|
|
42
|
+
:key="index"
|
|
43
|
+
class="size-fingertip btn btn-square c8-flat"
|
|
44
|
+
:value="page.value"
|
|
45
|
+
>
|
|
46
|
+
{{ page.value }}
|
|
47
|
+
</PaginationListItem>
|
|
48
|
+
<PaginationEllipsis
|
|
49
|
+
v-else
|
|
50
|
+
:key="page.type"
|
|
51
|
+
:index="index"
|
|
52
|
+
class="size-fingertip flex items-center justify-center"
|
|
53
|
+
>
|
|
54
|
+
…
|
|
55
|
+
</PaginationEllipsis>
|
|
56
|
+
</template>
|
|
57
|
+
<PaginationNext
|
|
58
|
+
v-if="showArrows"
|
|
59
|
+
class="size-fingertip flex items-center justify-center ml-4 btn btn-square c8-flat disabled:opacity-30"
|
|
60
|
+
>
|
|
61
|
+
<VuIcon name="i--chevron-right" class="btn-icon btn-icon-left" />
|
|
62
|
+
</PaginationNext>
|
|
63
|
+
<PaginationLast
|
|
64
|
+
v-if="!showEdges && showArrows"
|
|
65
|
+
class="size-fingertip flex items-center justify-center btn btn-square c8-flat disabled:opacity-30"
|
|
66
|
+
>
|
|
67
|
+
<VuIcon name="i--chevron-right-2" class="btn-icon btn-icon-left" />
|
|
68
|
+
</PaginationLast>
|
|
69
|
+
</PaginationList>
|
|
70
|
+
</PaginationRoot>
|
|
71
|
+
</template>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PopoverContentProps } from 'radix-vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<
|
|
5
|
+
PopoverContentProps & {
|
|
6
|
+
class?: string | Record<string, boolean>
|
|
7
|
+
icon?: string
|
|
8
|
+
label?: string
|
|
9
|
+
asLink?: boolean
|
|
10
|
+
}
|
|
11
|
+
>()
|
|
12
|
+
|
|
13
|
+
const popupContentProps = computed(() => {
|
|
14
|
+
const obj = {} as PopoverContentProps
|
|
15
|
+
for (const [key, value] of Object.entries(props)) {
|
|
16
|
+
if (!['class', 'label', 'icon'].includes(key)) {
|
|
17
|
+
obj[key as keyof PopoverContentProps] = value as undefined
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return obj
|
|
21
|
+
})
|
|
22
|
+
const modelValue = defineModel<boolean>()
|
|
23
|
+
function open() {
|
|
24
|
+
modelValue.value = true
|
|
25
|
+
}
|
|
26
|
+
function close() {
|
|
27
|
+
modelValue.value = false
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<PopoverRoot as-child v-model:open="modelValue">
|
|
33
|
+
<PopoverTrigger as-child>
|
|
34
|
+
<slot :isOpen="modelValue" :close :open
|
|
35
|
+
><VuButton
|
|
36
|
+
:as-link
|
|
37
|
+
:icon="icon || 'i--more-vert'"
|
|
38
|
+
:class
|
|
39
|
+
:label
|
|
40
|
+
:data-active="modelValue ? '' : undefined"
|
|
41
|
+
/></slot>
|
|
42
|
+
</PopoverTrigger>
|
|
43
|
+
<PopoverPortal>
|
|
44
|
+
<PopoverContent
|
|
45
|
+
v-bind="popupContentProps"
|
|
46
|
+
:style="{
|
|
47
|
+
'max-height': 'var(--radix-popper-available-height)',
|
|
48
|
+
'overflow': 'auto',
|
|
49
|
+
}"
|
|
50
|
+
as-child
|
|
51
|
+
>
|
|
52
|
+
<slot name="content" :isOpen="modelValue" :close :open>
|
|
53
|
+
<div></div>
|
|
54
|
+
</slot>
|
|
55
|
+
</PopoverContent>
|
|
56
|
+
</PopoverPortal>
|
|
57
|
+
</PopoverRoot>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as VuPopover } from './Popover.vue'
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends { value: string; label: string; disabled?: boolean }">
|
|
2
|
+
import { useId } from 'radix-vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
items: (string | T)[]
|
|
6
|
+
defaultValue?: string
|
|
7
|
+
label?: string
|
|
8
|
+
labelVisible?: boolean
|
|
9
|
+
row?: boolean
|
|
10
|
+
disabledValues?: string[]
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
class?: string | Record<string, boolean>
|
|
13
|
+
error?: string | boolean
|
|
14
|
+
verticalMiddle?: boolean
|
|
15
|
+
reverse?: boolean
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const _items = computed<T[]>(() => {
|
|
19
|
+
return props.items.map(item =>
|
|
20
|
+
typeof item === 'string' ? ({ value: item, label: item } as T) : item
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const id = useId()
|
|
25
|
+
|
|
26
|
+
const modelValue = defineModel<string>()
|
|
27
|
+
|
|
28
|
+
function isDisabled(val: string) {
|
|
29
|
+
return props.disabledValues?.includes(val)
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
<template>
|
|
33
|
+
<div class="rb-container" :class>
|
|
34
|
+
<label v-if="!!label && labelVisible" class="rb-label">{{ label }}</label>
|
|
35
|
+
<RadioGroupRoot
|
|
36
|
+
v-model="modelValue"
|
|
37
|
+
class="rb-root"
|
|
38
|
+
:class="{ 'rb-row': row }"
|
|
39
|
+
:default-value
|
|
40
|
+
:aria-label="label"
|
|
41
|
+
:disabled
|
|
42
|
+
:data-error="!!error"
|
|
43
|
+
:aria-disabled="disabled"
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
class="rb-item-wrapper"
|
|
47
|
+
:class="{
|
|
48
|
+
'items-center': verticalMiddle,
|
|
49
|
+
'items-start': !verticalMiddle,
|
|
50
|
+
'flex-row-reverse': reverse,
|
|
51
|
+
}"
|
|
52
|
+
v-for="item of _items"
|
|
53
|
+
>
|
|
54
|
+
<RadioGroupItem
|
|
55
|
+
:id="id + '-' + item.value"
|
|
56
|
+
class="rb-item"
|
|
57
|
+
:data-error="!!error"
|
|
58
|
+
:value="item.value"
|
|
59
|
+
:disabled="disabled || item.disabled || isDisabled(item.value)"
|
|
60
|
+
:aria-disabled="disabled || item.disabled || isDisabled(item.value)"
|
|
61
|
+
>
|
|
62
|
+
<RadioGroupIndicator class="rb-item-indicator" />
|
|
63
|
+
</RadioGroupItem>
|
|
64
|
+
<label
|
|
65
|
+
class="rb-item-label"
|
|
66
|
+
:for="id + '-' + item.value"
|
|
67
|
+
:aria-disabled="disabled || item.disabled || isDisabled(item.value)"
|
|
68
|
+
>
|
|
69
|
+
<slot v-bind="item">
|
|
70
|
+
{{ item.label }}
|
|
71
|
+
</slot>
|
|
72
|
+
</label>
|
|
73
|
+
</div>
|
|
74
|
+
</RadioGroupRoot>
|
|
75
|
+
<div
|
|
76
|
+
v-if="!!error && typeof error === 'string'"
|
|
77
|
+
class="text-caption text-error-500 text-mt-0"
|
|
78
|
+
:class="{ 'text-right': reverse }"
|
|
79
|
+
>
|
|
80
|
+
{{ error }}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as VuRadioGroup } from './RadioGroup.vue'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { scFromObject } from '../../theme/utils/shortcut-obj'
|
|
2
|
+
|
|
3
|
+
export const radioShortcuts = {
|
|
4
|
+
'rb-container': scFromObject({
|
|
5
|
+
'': 'flex flex-col gap-$s text-body',
|
|
6
|
+
}),
|
|
7
|
+
'rb-label': scFromObject({
|
|
8
|
+
'': 'text-label text-grey-400',
|
|
9
|
+
}),
|
|
10
|
+
'rb-root': scFromObject({
|
|
11
|
+
'': 'flex gap-x-$l gap-y-$m',
|
|
12
|
+
'[&.rb-row]:': 'flex-wrap',
|
|
13
|
+
'not-[.rb-row]:': 'flex-col',
|
|
14
|
+
}),
|
|
15
|
+
'rb-item-wrapper': scFromObject({
|
|
16
|
+
'': 'flex',
|
|
17
|
+
}),
|
|
18
|
+
'rb-item': scFromObject({
|
|
19
|
+
'': 'select-none shrink-0 current-bg-scope-color-500 bg-current/0 size-1.25em rounded-full cursor-default current-border-grey-500 border-current/40 border-[0.16em] transition-none backdrop-blur-sm',
|
|
20
|
+
"data-[state=checked]:not-[[data-error='true']]:":
|
|
21
|
+
'current-border-scope-color-500 border-current',
|
|
22
|
+
'data-[state=checked]:': 'bg-current',
|
|
23
|
+
'active:enabled:': 'bg-current/20',
|
|
24
|
+
'aria-[disabled=true]:': 'scope-grey opacity-50 cursor-not-allowed',
|
|
25
|
+
'data-[error=true]:': 'current-border-error-500 current-bg-error-500',
|
|
26
|
+
}),
|
|
27
|
+
'rb-item-indicator': scFromObject({
|
|
28
|
+
'': "flex items-center justify-center w-full h-full rounded-full relative after:content-[''] after:block after:size-[0.5em] after:rounded-[50%] after:bg-white animate-zoom-in animate-duration-100",
|
|
29
|
+
}),
|
|
30
|
+
'rb-item-label': scFromObject({
|
|
31
|
+
'': 'select-none px-$s text-body leading-none lh-1.25em',
|
|
32
|
+
'aria-[disabled=true]:': 'scope-grey opacity-50 cursor-not-allowed',
|
|
33
|
+
}),
|
|
34
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends TSelectItem">
|
|
2
|
+
import type { TInputProps, TInputShellProps, TInputEmits } from '../Input/types'
|
|
3
|
+
import { useInputShellProps, useInputProps } from '../Input/utils'
|
|
4
|
+
import type { TSelectBaseProps, TSelectItem } from './types'
|
|
5
|
+
|
|
6
|
+
type Props = Omit<TInputProps & TInputShellProps, 'active'> & TSelectBaseProps<T>
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
9
|
+
sideOffset: 2,
|
|
10
|
+
})
|
|
11
|
+
defineEmits<TInputEmits>()
|
|
12
|
+
|
|
13
|
+
const forwardProps = computed(() => {
|
|
14
|
+
if (props.groupItem) {
|
|
15
|
+
return useInputShellProps()?.value
|
|
16
|
+
}
|
|
17
|
+
return useInputProps()?.value
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const modelValue = defineModel<string>()
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<VuSelectBase
|
|
25
|
+
v-model="modelValue"
|
|
26
|
+
:disabled-values
|
|
27
|
+
:popup-class
|
|
28
|
+
:popup-round
|
|
29
|
+
:value-class
|
|
30
|
+
:icon-class
|
|
31
|
+
:class
|
|
32
|
+
:items
|
|
33
|
+
:required
|
|
34
|
+
:disabled
|
|
35
|
+
:placeholder
|
|
36
|
+
:defaultValue
|
|
37
|
+
:popupPosition
|
|
38
|
+
:side
|
|
39
|
+
:sideOffset
|
|
40
|
+
:sticky
|
|
41
|
+
:updatePositionStrategy
|
|
42
|
+
v-slot="s"
|
|
43
|
+
>
|
|
44
|
+
<SelectTrigger as-child v-if="groupItem">
|
|
45
|
+
<!-- prettier-ignore-attribute v-model -->
|
|
46
|
+
<VuInputShell
|
|
47
|
+
class="cursor-pointer select-none"
|
|
48
|
+
v-bind="forwardProps"
|
|
49
|
+
:icon-append="typeof iconAppend === 'string' ? iconAppend : s.icon"
|
|
50
|
+
:model-value="(s.displayItem?.label || s.displayItem?.value) as string"
|
|
51
|
+
:active="s.open"
|
|
52
|
+
readonly
|
|
53
|
+
@click="s.openPopup"
|
|
54
|
+
tabindex="-1"
|
|
55
|
+
@focus="$event.target.querySelector('input')?.focus()"
|
|
56
|
+
>
|
|
57
|
+
<template #overlay>
|
|
58
|
+
<SelectValue class="absolute left-0 right-0 h-0 invisible" />
|
|
59
|
+
</template>
|
|
60
|
+
</VuInputShell>
|
|
61
|
+
</SelectTrigger>
|
|
62
|
+
<!-- prettier-ignore-attribute v-model -->
|
|
63
|
+
<VuInput
|
|
64
|
+
v-else
|
|
65
|
+
class="select-none"
|
|
66
|
+
v-bind="forwardProps"
|
|
67
|
+
:icon-append="typeof iconAppend === 'string' ? iconAppend : s.icon"
|
|
68
|
+
:model-value="(s.displayItem?.label || s.displayItem?.value) as string"
|
|
69
|
+
:active="s.open"
|
|
70
|
+
:stack-label
|
|
71
|
+
readonly
|
|
72
|
+
@click="s.openPopup"
|
|
73
|
+
v-slot="shellProps"
|
|
74
|
+
>
|
|
75
|
+
<SelectTrigger as-child>
|
|
76
|
+
<!-- prettier-ignore-attribute v-model -->
|
|
77
|
+
<VuInputShell
|
|
78
|
+
class="cursor-pointer select-none"
|
|
79
|
+
v-bind="shellProps"
|
|
80
|
+
:model-value="(s.displayItem?.label || s.displayItem?.value) as string"
|
|
81
|
+
readonly
|
|
82
|
+
type="text"
|
|
83
|
+
tabindex="-1"
|
|
84
|
+
@focus="$event.target.querySelector('input')?.focus()"
|
|
85
|
+
>
|
|
86
|
+
<template #overlay>
|
|
87
|
+
<SelectValue class="absolute left-[-1px] right-0 h-0 invisible" />
|
|
88
|
+
</template>
|
|
89
|
+
</VuInputShell>
|
|
90
|
+
</SelectTrigger>
|
|
91
|
+
</VuInput>
|
|
92
|
+
</VuSelectBase>
|
|
93
|
+
</template>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends TSelectItem">
|
|
2
|
+
import type { TSelectBaseProps, TSelectItem } from './types'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<TSelectBaseProps<T>>()
|
|
5
|
+
|
|
6
|
+
const open = defineModel<boolean>('open')
|
|
7
|
+
const modelValue = defineModel<string>()
|
|
8
|
+
|
|
9
|
+
const groups = computed(() => {
|
|
10
|
+
if (Array.isArray(props.items)) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
grp: '',
|
|
14
|
+
items: props.items.map(item =>
|
|
15
|
+
typeof item === 'string' ? ({ value: item, label: item } as T) : item
|
|
16
|
+
),
|
|
17
|
+
},
|
|
18
|
+
]
|
|
19
|
+
} else {
|
|
20
|
+
const r = [] as { grp: string; items: T[] }[]
|
|
21
|
+
for (const [key, val] of Object.entries(props.items)) {
|
|
22
|
+
r.push({
|
|
23
|
+
grp: key,
|
|
24
|
+
items: val.map(item =>
|
|
25
|
+
typeof item === 'string' ? ({ value: item, label: item } as T) : item
|
|
26
|
+
),
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
return r
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const flatItems = computed(() => {
|
|
34
|
+
const r = new Map<string | null | undefined, T>()
|
|
35
|
+
for (const grp of groups.value) {
|
|
36
|
+
for (const item of grp.items) {
|
|
37
|
+
r.set(item.value, item)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return r
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
function isItemDisabled(val: string | null | undefined) {
|
|
44
|
+
return props.disabledValues?.includes(val)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function openPopup() {
|
|
48
|
+
if (!props.disabled) {
|
|
49
|
+
open.value = !open.value
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const displayItem = computed(() => flatItems.value.get(modelValue.value))
|
|
54
|
+
|
|
55
|
+
function getSearchValue(item: T) {
|
|
56
|
+
return item.search || item.label?.replace(/[^\p{L}\p{N}\p{P}\p{M}\p{Zs}]/gu, '').trim()
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<!-- prettier-ignore-attribute default-value -->
|
|
62
|
+
<SelectRoot
|
|
63
|
+
v-model="modelValue"
|
|
64
|
+
v-model:open="open"
|
|
65
|
+
:disabled
|
|
66
|
+
:required
|
|
67
|
+
:default-value="defaultValue as string"
|
|
68
|
+
>
|
|
69
|
+
<slot :displayItem="displayItem" :value="modelValue" :openPopup :open :icon="'i--chevron-down'">
|
|
70
|
+
<SelectTrigger :class>
|
|
71
|
+
<VuIcon
|
|
72
|
+
v-if="!!displayItem?.icon"
|
|
73
|
+
:name="displayItem.icon"
|
|
74
|
+
class="inline-block vertical-middle mr-$xs"
|
|
75
|
+
style="color: currentColor"
|
|
76
|
+
/>
|
|
77
|
+
<SelectValue :placeholder :class="valueClass">
|
|
78
|
+
{{ displayItem?.label }}
|
|
79
|
+
</SelectValue>
|
|
80
|
+
<VuIcon name="i--chevron-down" :class="iconClass" />
|
|
81
|
+
</SelectTrigger>
|
|
82
|
+
</slot>
|
|
83
|
+
|
|
84
|
+
<SelectPortal>
|
|
85
|
+
<SelectContent
|
|
86
|
+
class="select-content group"
|
|
87
|
+
:style="{
|
|
88
|
+
'min-width': 'var(--radix-popper-anchor-width)',
|
|
89
|
+
'max-height': popupPosition === 'popper' ? 'var(--radix-popper-available-height)' : '',
|
|
90
|
+
'overflow': popupPosition === 'popper' ? 'auto' : '',
|
|
91
|
+
}"
|
|
92
|
+
:data-design="popupRound ? 'round' : undefined"
|
|
93
|
+
:class="popupClass"
|
|
94
|
+
:position="popupPosition"
|
|
95
|
+
:side
|
|
96
|
+
:side-offset
|
|
97
|
+
:sticky
|
|
98
|
+
:update-position-strategy
|
|
99
|
+
>
|
|
100
|
+
<SelectScrollUpButton class="select-scroll-btn">
|
|
101
|
+
<VuIcon name="i--chevron-up" class="size-1em" />
|
|
102
|
+
</SelectScrollUpButton>
|
|
103
|
+
|
|
104
|
+
<SelectViewport>
|
|
105
|
+
<template v-for="(g, grpIndex) of groups" :key="grpIndex">
|
|
106
|
+
<SelectSeparator v-if="grpIndex > 0" class="select-separator" />
|
|
107
|
+
<SelectLabel class="select-grp-label" v-if="!!g.grp">
|
|
108
|
+
<slot name="group" v-bind="g">
|
|
109
|
+
<span>{{ g.grp }}</span>
|
|
110
|
+
</slot>
|
|
111
|
+
</SelectLabel>
|
|
112
|
+
<SelectGroup>
|
|
113
|
+
<SelectItem
|
|
114
|
+
v-for="(item, index) in g.items"
|
|
115
|
+
:key="index"
|
|
116
|
+
class="select-item relative"
|
|
117
|
+
:value="String(item.value)"
|
|
118
|
+
:disabled="disabled || item.disabled || isItemDisabled(item.value)"
|
|
119
|
+
:aria-disabled="disabled || item.disabled || isItemDisabled(item.value)"
|
|
120
|
+
>
|
|
121
|
+
<SelectItemText class="absolute">
|
|
122
|
+
<span class="hidden">
|
|
123
|
+
{{ getSearchValue(item) }}
|
|
124
|
+
</span>
|
|
125
|
+
</SelectItemText>
|
|
126
|
+
<span>
|
|
127
|
+
<slot name="item" v-bind="item">
|
|
128
|
+
<VuIcon
|
|
129
|
+
v-if="!!item.icon"
|
|
130
|
+
:name="item.icon"
|
|
131
|
+
class="inline-block vertical-middle mr-$xs"
|
|
132
|
+
style="color: currentColor"
|
|
133
|
+
/>
|
|
134
|
+
{{ item.label }}
|
|
135
|
+
</slot>
|
|
136
|
+
</span>
|
|
137
|
+
</SelectItem>
|
|
138
|
+
</SelectGroup>
|
|
139
|
+
</template>
|
|
140
|
+
</SelectViewport>
|
|
141
|
+
|
|
142
|
+
<SelectScrollDownButton class="select-scroll-btn">
|
|
143
|
+
<VuIcon name="i--chevron-down" class="size-1em" />
|
|
144
|
+
</SelectScrollDownButton>
|
|
145
|
+
</SelectContent>
|
|
146
|
+
</SelectPortal>
|
|
147
|
+
</SelectRoot>
|
|
148
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { scFromObject } from '../../theme/utils/shortcut-obj'
|
|
2
|
+
|
|
3
|
+
export const selectShortcuts = {
|
|
4
|
+
'select-content': scFromObject({
|
|
5
|
+
'': 'min-w-[60px] rounded-base surface-0 bg-current/70 backdrop-blur-xl overflow-hidden shadow-xl z-[100] current-border-grey-400 border-current/20 ',
|
|
6
|
+
'data-[design=round]:': 'rounded-fingertip-half',
|
|
7
|
+
'[&>div[data-radix-combobox-viewport]]:':
|
|
8
|
+
'max-h-[var(--radix-popper-available-height)] [scrollbar-width:auto]',
|
|
9
|
+
'[&>div[data-radix-combobox-viewport]::-webkit-scrollbar]:': 'block',
|
|
10
|
+
// 'data-[side=top]:': 'animate-slide-down-and-fade',
|
|
11
|
+
// 'data-[side=right]:': 'animate-slide-left-and-fade',
|
|
12
|
+
// 'data-[side=bottom]:': 'animate-slide-up-and-fade',
|
|
13
|
+
// 'data-[side=left]:': 'animate-slide-right-and-fade',
|
|
14
|
+
}),
|
|
15
|
+
'select-scroll-btn': 'flex items-center justify-center h-fingertip cursor-default',
|
|
16
|
+
'select-grp-label': scFromObject({
|
|
17
|
+
'': 'px-$m h-fingertip flex items-center ',
|
|
18
|
+
'group-data-[design=round]:': 'px-fingertip-half',
|
|
19
|
+
'[&>span]:': 'text-label text-grey-400',
|
|
20
|
+
}),
|
|
21
|
+
'select-item': scFromObject({
|
|
22
|
+
'': 'text-body leading-none flex items-center h-fingertip relative select-none relative',
|
|
23
|
+
'data-[disabled]:': 'opacity-40 pointer-events-none',
|
|
24
|
+
'data-[highlighted]:': 'outline-none bg-scope-color-500/15',
|
|
25
|
+
'[&>span]:': 'px-$m',
|
|
26
|
+
'group-data-[design=round]:[&>span]:': 'px-fingertip-half',
|
|
27
|
+
'[&>span]:data-[state=checked]:': 'text-scope-color-500 fw-700!',
|
|
28
|
+
}),
|
|
29
|
+
'select-separator': 'h-[1px] bg-grey-500/10 mx-$s',
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface TSelectItem {
|
|
2
|
+
icon?: string
|
|
3
|
+
search?: string
|
|
4
|
+
value: string | null | undefined
|
|
5
|
+
label?: string
|
|
6
|
+
disabled?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type TSelectItems<T extends TSelectItem = TSelectItem> =
|
|
10
|
+
| Array<T | string>
|
|
11
|
+
| Record<string, Array<T | string>>
|
|
12
|
+
|
|
13
|
+
export interface TSelectBaseProps<T extends TSelectItem> {
|
|
14
|
+
required?: boolean
|
|
15
|
+
disabledValues?: Array<string | null | undefined>
|
|
16
|
+
defaultValue?: string | null | undefined
|
|
17
|
+
popupClass?: string | Record<string, boolean>
|
|
18
|
+
popupRound?: boolean
|
|
19
|
+
valueClass?: string | Record<string, boolean>
|
|
20
|
+
iconClass?: string | Record<string, boolean>
|
|
21
|
+
class?: string | Record<string, boolean>
|
|
22
|
+
items: TSelectItems<T>
|
|
23
|
+
disabled?: boolean
|
|
24
|
+
placeholder?: string
|
|
25
|
+
popupPosition?: 'item-aligned' | 'popper'
|
|
26
|
+
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
27
|
+
sideOffset?: number
|
|
28
|
+
sticky?: 'partial' | 'always'
|
|
29
|
+
updatePositionStrategy?: 'always' | 'optimized'
|
|
30
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SliderRootEmits, SliderRootProps } from 'radix-vue'
|
|
3
|
+
import { useForwardPropsEmits } from 'radix-vue'
|
|
4
|
+
|
|
5
|
+
type TClass = string | Record<string, boolean>
|
|
6
|
+
|
|
7
|
+
const props = defineProps<
|
|
8
|
+
SliderRootProps & {
|
|
9
|
+
class?: TClass
|
|
10
|
+
rootClass?: TClass
|
|
11
|
+
trackClass?: TClass
|
|
12
|
+
rangeClass?: TClass
|
|
13
|
+
hideRange?: boolean
|
|
14
|
+
thumbs?: number
|
|
15
|
+
labels?: string[]
|
|
16
|
+
sliderClass?: TClass | TClass[]
|
|
17
|
+
label?: string
|
|
18
|
+
displayValue?: boolean
|
|
19
|
+
}
|
|
20
|
+
>()
|
|
21
|
+
const emits = defineEmits<SliderRootEmits>()
|
|
22
|
+
|
|
23
|
+
const delegatedProps = computed(() => {
|
|
24
|
+
const {
|
|
25
|
+
class: _,
|
|
26
|
+
rootClass,
|
|
27
|
+
hideRange,
|
|
28
|
+
trackClass,
|
|
29
|
+
rangeClass,
|
|
30
|
+
thumbs,
|
|
31
|
+
labels,
|
|
32
|
+
sliderClass,
|
|
33
|
+
label,
|
|
34
|
+
displayValue,
|
|
35
|
+
...delegated
|
|
36
|
+
} = props
|
|
37
|
+
|
|
38
|
+
return delegated
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
42
|
+
const modelValue = defineModel<number[]>()
|
|
43
|
+
function getSliderClass(n: number) {
|
|
44
|
+
if (Array.isArray(props.sliderClass)) {
|
|
45
|
+
return props.sliderClass[n]
|
|
46
|
+
} else if (props.sliderClass !== undefined) {
|
|
47
|
+
return props.sliderClass
|
|
48
|
+
}
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<div :class>
|
|
55
|
+
<div class="flex justify-between" v-if="!!label || displayValue">
|
|
56
|
+
<VuLabel>{{ label || '' }}</VuLabel>
|
|
57
|
+
<span class="text-label text-grey-400">{{ modelValue?.join(' | ') }}</span>
|
|
58
|
+
</div>
|
|
59
|
+
<SliderRoot v-bind="forwarded" v-model="modelValue" class="slider" :class="rootClass">
|
|
60
|
+
<SliderTrack :class="trackClass || 'slider-track'">
|
|
61
|
+
<SliderRange v-if="!hideRange" :class="rangeClass || 'slider-range'" />
|
|
62
|
+
</SliderTrack>
|
|
63
|
+
|
|
64
|
+
<slot>
|
|
65
|
+
<SliderThumb
|
|
66
|
+
v-for="n in thumbs || 1"
|
|
67
|
+
:class="getSliderClass(n) || 'slider-thumb'"
|
|
68
|
+
:aria-label="labels?.[n]"
|
|
69
|
+
/>
|
|
70
|
+
</slot>
|
|
71
|
+
</SliderRoot>
|
|
72
|
+
</div>
|
|
73
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as VuSlider } from './Slider.vue'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { scFromObject } from '../../theme/utils/shortcut-obj'
|
|
2
|
+
|
|
3
|
+
export const sliderShortcuts = {
|
|
4
|
+
'slider': scFromObject({
|
|
5
|
+
'': 'relative flex items-center select-none touch-none min-w-2em min-h-2em',
|
|
6
|
+
}),
|
|
7
|
+
'slider-track': scFromObject({
|
|
8
|
+
'': 'bg-grey-500/20 relative grow rounded-full h-[0.25em]',
|
|
9
|
+
}),
|
|
10
|
+
'slider-range': scFromObject({
|
|
11
|
+
'': 'absolute bg-scope-color-500 rounded-full h-full',
|
|
12
|
+
}),
|
|
13
|
+
'slider-thumb': scFromObject({
|
|
14
|
+
'': 'block w-[1.5em] h-[1.5em] bg-scope-color-500 rounded-full border-scope-light-0 border-[3px] outline-scope-color-500/10 outline-0px outline-solid',
|
|
15
|
+
'dark:': 'border-scope-dark-0',
|
|
16
|
+
'not-[[disabled]]:': scFromObject({
|
|
17
|
+
'': 'cursor-grab',
|
|
18
|
+
'hover:': 'shadow-md',
|
|
19
|
+
'active:': 'cursor-grabbing',
|
|
20
|
+
'focus:': 'outline-[0.5em]',
|
|
21
|
+
}),
|
|
22
|
+
}),
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { buttonShortcuts } from './Button/shortcuts'
|
|
2
|
+
import { cardShortcuts } from './Card/shortcuts'
|
|
3
|
+
import { checkboxShortcuts } from './Checkbox/shortcuts'
|
|
4
|
+
import { comboboxShortcuts } from './Combobox/shortcuts'
|
|
5
|
+
import { loadingShortcuts } from './Loading/shortcuts'
|
|
6
|
+
import { menuShortcuts } from './Menu/shortcuts'
|
|
7
|
+
import { radioShortcuts } from './RadioGroup/shortcuts'
|
|
8
|
+
import { selectShortcuts } from './Select/shortcuts'
|
|
9
|
+
import { sliderShortcuts } from './Slider/shortcuts'
|
|
10
|
+
|
|
11
|
+
export const shortcuts = [
|
|
12
|
+
cardShortcuts,
|
|
13
|
+
menuShortcuts,
|
|
14
|
+
buttonShortcuts,
|
|
15
|
+
checkboxShortcuts,
|
|
16
|
+
radioShortcuts,
|
|
17
|
+
selectShortcuts,
|
|
18
|
+
comboboxShortcuts,
|
|
19
|
+
sliderShortcuts,
|
|
20
|
+
loadingShortcuts,
|
|
21
|
+
]
|