@yunlefun/vue 0.1.1 → 0.1.3

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 (65) hide show
  1. package/README.md +26 -37
  2. package/components/YlfAccordion.vue +165 -0
  3. package/components/YlfAvatar.vue +70 -0
  4. package/components/YlfBadge.vue +142 -0
  5. package/components/YlfBottomMenu.vue +23 -6
  6. package/components/YlfBottomMenuItem.vue +41 -21
  7. package/components/YlfButton.vue +290 -7
  8. package/components/YlfCard.vue +74 -3
  9. package/components/YlfCheckbox.vue +84 -0
  10. package/components/YlfDialog.vue +174 -0
  11. package/components/YlfDropdownMenu.vue +98 -0
  12. package/components/YlfLogo.vue +83 -7
  13. package/components/YlfPopover.vue +64 -0
  14. package/components/YlfProgress.vue +65 -0
  15. package/components/YlfRadioGroup.vue +120 -0
  16. package/components/YlfSelect.vue +195 -0
  17. package/components/YlfSeparator.vue +68 -0
  18. package/components/YlfSlider.vue +89 -0
  19. package/components/YlfSwitch.vue +111 -0
  20. package/components/YlfTabs.vue +117 -0
  21. package/components/YlfTooltip.vue +67 -0
  22. package/components/accordion/demo.vue +11 -0
  23. package/components/accordion/index.md +19 -0
  24. package/components/avatar/demo.vue +9 -0
  25. package/components/avatar/index.md +15 -0
  26. package/components/badge/demo.vue +84 -0
  27. package/components/badge/index.md +41 -0
  28. package/components/bottom-menu/demo.vue +19 -10
  29. package/components/bottom-menu/index.md +25 -0
  30. package/components/button/demo.vue +101 -3
  31. package/components/button/index.md +41 -0
  32. package/components/card/demo.vue +76 -3
  33. package/components/card/index.md +19 -0
  34. package/components/checkbox/demo.vue +28 -0
  35. package/components/checkbox/index.md +14 -0
  36. package/components/common/demo.vue +7 -1
  37. package/components/common/index.md +10 -0
  38. package/components/dialog/demo.vue +33 -0
  39. package/components/dialog/index.md +30 -0
  40. package/components/dropdown-menu/demo.vue +27 -0
  41. package/components/dropdown-menu/index.md +27 -0
  42. package/components/popover/demo.vue +18 -0
  43. package/components/popover/index.md +21 -0
  44. package/components/progress/demo.vue +24 -0
  45. package/components/progress/index.md +17 -0
  46. package/components/radio/demo.vue +20 -0
  47. package/components/radio/index.md +23 -0
  48. package/components/select/demo.vue +20 -0
  49. package/components/select/index.md +41 -0
  50. package/components/separator/demo.vue +52 -0
  51. package/components/separator/index.md +19 -0
  52. package/components/slider/demo.vue +23 -0
  53. package/components/slider/index.md +16 -0
  54. package/components/switch/demo.vue +33 -0
  55. package/components/switch/index.md +18 -0
  56. package/components/tabs/demo.vue +30 -0
  57. package/components/tabs/index.md +18 -0
  58. package/components/theme.ts +5 -0
  59. package/components/tooltip/demo.vue +19 -0
  60. package/components/tooltip/index.md +21 -0
  61. package/dist/index.cjs +2 -1
  62. package/dist/index.d.cts +7 -1
  63. package/dist/index.d.mts +7 -1
  64. package/dist/index.d.ts +7 -1
  65. package/package.json +32 -3
@@ -0,0 +1,98 @@
1
+ <script lang="ts" setup>
2
+ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuRoot, DropdownMenuSeparator, DropdownMenuTrigger } from 'reka-ui'
3
+
4
+ interface MenuItem {
5
+ label?: string
6
+ value?: string
7
+ disabled?: boolean
8
+ /** 渲染成分隔线 */
9
+ separator?: boolean
10
+ }
11
+
12
+ defineProps<{
13
+ items: MenuItem[]
14
+ }>()
15
+
16
+ const emit = defineEmits<{ select: [value: string] }>()
17
+ </script>
18
+
19
+ <template>
20
+ <DropdownMenuRoot>
21
+ <DropdownMenuTrigger as-child>
22
+ <slot name="trigger" />
23
+ </DropdownMenuTrigger>
24
+ <DropdownMenuPortal>
25
+ <DropdownMenuContent class="ylf-menu" :side-offset="6" align="start">
26
+ <template v-for="(it, i) in items" :key="i">
27
+ <DropdownMenuSeparator v-if="it.separator" class="ylf-menu__sep" />
28
+ <DropdownMenuItem
29
+ v-else
30
+ class="ylf-menu__item"
31
+ :disabled="it.disabled"
32
+ @select="it.value && emit('select', it.value)"
33
+ >
34
+ {{ it.label }}
35
+ </DropdownMenuItem>
36
+ </template>
37
+ </DropdownMenuContent>
38
+ </DropdownMenuPortal>
39
+ </DropdownMenuRoot>
40
+ </template>
41
+
42
+ <style lang="scss">
43
+ .ylf-menu {
44
+ z-index: 105;
45
+ min-width: 180px;
46
+ padding: 6px;
47
+ background: var(--ylf-c-surface, #fff);
48
+ border: 1px solid var(--ylf-c-border, #e2e8f0);
49
+ border-radius: var(--ylf-radius, 14px);
50
+ box-shadow: var(--ylf-shadow-lg, 0 22px 56px -14px rgba(15, 23, 42, 0.22));
51
+ transform-origin: var(--reka-dropdown-menu-content-transform-origin);
52
+
53
+ &[data-state='open'] {
54
+ animation: ylf-pop-in 0.16s ease;
55
+ }
56
+
57
+ &__item {
58
+ display: flex;
59
+ align-items: center;
60
+ padding: 8px 12px;
61
+ font-size: 14px;
62
+ color: var(--ylf-c-text, #0f172a);
63
+ border-radius: var(--ylf-radius-sm, 10px);
64
+ cursor: pointer;
65
+ user-select: none;
66
+ outline: none;
67
+
68
+ &[data-highlighted] {
69
+ background: var(--ylf-c-brand-soft, #eff6ff);
70
+ color: var(--ylf-c-brand, #2563eb);
71
+ }
72
+
73
+ &[data-disabled] {
74
+ opacity: 0.45;
75
+ pointer-events: none;
76
+ }
77
+ }
78
+
79
+ &__sep {
80
+ height: 1px;
81
+ margin: 6px 4px;
82
+ background: var(--ylf-c-border, #e2e8f0);
83
+ }
84
+ }
85
+
86
+ @keyframes ylf-pop-in {
87
+ from {
88
+ opacity: 0;
89
+ transform: scale(0.96);
90
+ }
91
+ }
92
+
93
+ @media (prefers-reduced-motion: reduce) {
94
+ .ylf-menu[data-state='open'] {
95
+ animation: none;
96
+ }
97
+ }
98
+ </style>
@@ -1,10 +1,86 @@
1
+ <script lang="ts" setup>
2
+ withDefaults(defineProps<{
3
+ size?: 'sm' | 'md' | 'lg'
4
+ layout?: 'vertical' | 'horizontal'
5
+ /** 是否显示「云乐坊」文字 */
6
+ wordmark?: boolean
7
+ }>(), {
8
+ size: 'md',
9
+ layout: 'vertical',
10
+ wordmark: true,
11
+ })
12
+ </script>
13
+
1
14
  <template>
2
- <div inline-flex cursor-default font-300>
3
- <div flex flex-col children:mx-auto>
4
- <div class="i-ri-cloud-line" text-8xl />
5
- <span mt--2 text-2xl text-black>
6
- 云乐坊
7
- </span>
8
- </div>
15
+ <div class="ylf-logo" :class="[`ylf-logo--${size}`, `ylf-logo--${layout}`]">
16
+ <span class="ylf-logo__mark" aria-hidden="true">
17
+ <svg viewBox="0 0 100 70" fill="currentColor">
18
+ <path d="M44 35L27.5 44.5263V25.4737L44 35Z" />
19
+ <circle cx="65" cy="35" r="9" />
20
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M85 0H15V15H0V55H15V70H85V55H100V15H85V0ZM85 15H15V55H85V15Z" />
21
+ </svg>
22
+ </span>
23
+ <span v-if="wordmark" class="ylf-logo__name">云乐坊</span>
9
24
  </div>
10
25
  </template>
26
+
27
+ <style lang="scss">
28
+ .ylf-logo {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 10px;
32
+ cursor: default;
33
+ line-height: 1;
34
+
35
+ &--vertical {
36
+ flex-direction: column;
37
+ gap: 8px;
38
+ }
39
+
40
+ &__mark {
41
+ flex: none;
42
+ display: grid;
43
+ place-items: center;
44
+ color: var(--ylf-c-brand, #2563eb);
45
+
46
+ svg {
47
+ width: 100%;
48
+ height: 100%;
49
+ }
50
+ }
51
+
52
+ &__name {
53
+ font-family: var(--ylf-font-wordmark, var(--ylf-font-display, sans-serif));
54
+ font-weight: 700;
55
+ color: var(--ylf-c-text, #0f172a);
56
+ }
57
+
58
+ // sizes
59
+ &--sm &__mark {
60
+ width: 36px;
61
+ height: 25.2px;
62
+ }
63
+
64
+ &--sm &__name {
65
+ font-size: 18px;
66
+ }
67
+
68
+ &--md &__mark {
69
+ width: 48px;
70
+ height: 33.6px;
71
+ }
72
+
73
+ &--md &__name {
74
+ font-size: 24px;
75
+ }
76
+
77
+ &--lg &__mark {
78
+ width: 72px;
79
+ height: 50.4px;
80
+ }
81
+
82
+ &--lg &__name {
83
+ font-size: 34px;
84
+ }
85
+ }
86
+ </style>
@@ -0,0 +1,64 @@
1
+ <script lang="ts" setup>
2
+ import { PopoverArrow, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
3
+
4
+ withDefaults(defineProps<{
5
+ side?: 'top' | 'right' | 'bottom' | 'left'
6
+ align?: 'start' | 'center' | 'end'
7
+ }>(), {
8
+ side: 'bottom',
9
+ align: 'center',
10
+ })
11
+
12
+ const open = defineModel<boolean>('open', { default: false })
13
+ </script>
14
+
15
+ <template>
16
+ <PopoverRoot v-model:open="open">
17
+ <PopoverTrigger as-child>
18
+ <slot name="trigger" />
19
+ </PopoverTrigger>
20
+ <PopoverPortal>
21
+ <PopoverContent :side="side" :align="align" :side-offset="8" class="ylf-popover">
22
+ <slot />
23
+ <PopoverArrow class="ylf-popover__arrow" :width="12" :height="6" />
24
+ </PopoverContent>
25
+ </PopoverPortal>
26
+ </PopoverRoot>
27
+ </template>
28
+
29
+ <style lang="scss">
30
+ .ylf-popover {
31
+ z-index: 105;
32
+ min-width: 180px;
33
+ max-width: 320px;
34
+ padding: 16px;
35
+ color: var(--ylf-c-text, #0f172a);
36
+ background: var(--ylf-c-surface, #fff);
37
+ border: 1px solid var(--ylf-c-border, #e2e8f0);
38
+ border-radius: var(--ylf-radius, 14px);
39
+ box-shadow: var(--ylf-shadow-lg, 0 22px 56px -14px rgba(15, 23, 42, 0.22));
40
+ transform-origin: var(--reka-popover-content-transform-origin);
41
+
42
+ &[data-state='open'] {
43
+ animation: ylf-pop-in 0.18s var(--ylf-ease-bounce, cubic-bezier(0.34, 1.56, 0.64, 1));
44
+ }
45
+
46
+ &__arrow {
47
+ fill: var(--ylf-c-surface, #fff);
48
+ stroke: var(--ylf-c-border, #e2e8f0);
49
+ }
50
+ }
51
+
52
+ @keyframes ylf-pop-in {
53
+ from {
54
+ opacity: 0;
55
+ transform: scale(0.96);
56
+ }
57
+ }
58
+
59
+ @media (prefers-reduced-motion: reduce) {
60
+ .ylf-popover[data-state='open'] {
61
+ animation: none;
62
+ }
63
+ }
64
+ </style>
@@ -0,0 +1,65 @@
1
+ <script lang="ts" setup>
2
+ import type { YlfAccentTone } from './theme'
3
+ import { ProgressIndicator, ProgressRoot } from 'reka-ui'
4
+ import { computed } from 'vue'
5
+
6
+ const props = withDefaults(defineProps<{
7
+ value?: number
8
+ max?: number
9
+ /** 进度条填充:brand 主色或 accent 纯色;aurora 是 accent 的旧别名 */
10
+ variant?: 'brand' | 'accent' | 'aurora'
11
+ tone?: YlfAccentTone
12
+ }>(), {
13
+ value: 0,
14
+ max: 100,
15
+ variant: 'brand',
16
+ tone: 'blue',
17
+ })
18
+
19
+ const percent = computed(() => Math.max(0, Math.min(100, (props.value / props.max) * 100)))
20
+ </script>
21
+
22
+ <template>
23
+ <ProgressRoot
24
+ :model-value="value"
25
+ :max="max"
26
+ class="ylf-progress"
27
+ :data-ylf-tone="tone"
28
+ :class="`ylf-progress--${variant}`"
29
+ >
30
+ <ProgressIndicator
31
+ class="ylf-progress__indicator"
32
+ :style="{ transform: `translateX(-${100 - percent}%)` }"
33
+ />
34
+ </ProgressRoot>
35
+ </template>
36
+
37
+ <style lang="scss">
38
+ .ylf-progress {
39
+ position: relative;
40
+ width: 100%;
41
+ height: 8px;
42
+ overflow: hidden;
43
+ border-radius: 999px;
44
+ background: var(--ylf-c-bg-mute, #e2e8f0);
45
+ }
46
+
47
+ .ylf-progress__indicator {
48
+ width: 100%;
49
+ height: 100%;
50
+ border-radius: 999px;
51
+ background: var(--ylf-c-brand, #2563eb);
52
+ transition: transform 0.4s var(--ylf-ease-bounce, cubic-bezier(0.34, 1.56, 0.64, 1));
53
+ }
54
+
55
+ .ylf-progress--accent .ylf-progress__indicator,
56
+ .ylf-progress--aurora .ylf-progress__indicator {
57
+ background: var(--ylf-accent, var(--ylf-c-brand, #2563eb));
58
+ }
59
+
60
+ @media (prefers-reduced-motion: reduce) {
61
+ .ylf-progress__indicator {
62
+ transition: none;
63
+ }
64
+ }
65
+ </style>
@@ -0,0 +1,120 @@
1
+ <script lang="ts" setup>
2
+ // 行为 / 可访问性由 reka-ui 提供(role=radiogroup、方向键 roving focus);皮肤走 token。
3
+ import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from 'reka-ui'
4
+
5
+ interface RadioOption {
6
+ label: string
7
+ value: string
8
+ disabled?: boolean
9
+ }
10
+
11
+ withDefaults(defineProps<{
12
+ options: RadioOption[]
13
+ disabled?: boolean
14
+ orientation?: 'vertical' | 'horizontal'
15
+ }>(), {
16
+ disabled: false,
17
+ orientation: 'vertical',
18
+ })
19
+
20
+ const model = defineModel<string>()
21
+ </script>
22
+
23
+ <template>
24
+ <RadioGroupRoot
25
+ v-model="model"
26
+ :disabled="disabled"
27
+ :orientation="orientation"
28
+ class="ylf-radio-group"
29
+ :class="`ylf-radio-group--${orientation}`"
30
+ >
31
+ <RadioGroupItem
32
+ v-for="opt in options"
33
+ :key="opt.value"
34
+ :value="opt.value"
35
+ :disabled="opt.disabled"
36
+ class="ylf-radio"
37
+ >
38
+ <span class="ylf-radio__control">
39
+ <RadioGroupIndicator class="ylf-radio__dot" />
40
+ </span>
41
+ <span class="ylf-radio__label">{{ opt.label }}</span>
42
+ </RadioGroupItem>
43
+ </RadioGroupRoot>
44
+ </template>
45
+
46
+ <style lang="scss">
47
+ .ylf-radio-group {
48
+ display: flex;
49
+ gap: 14px;
50
+
51
+ &--vertical {
52
+ flex-direction: column;
53
+ }
54
+
55
+ &--horizontal {
56
+ flex-direction: row;
57
+ flex-wrap: wrap;
58
+ }
59
+ }
60
+
61
+ .ylf-radio {
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: 8px;
65
+ padding: 0;
66
+ border: none;
67
+ background: transparent;
68
+ color: var(--ylf-c-text, #0f172a);
69
+ font: inherit;
70
+ cursor: pointer;
71
+ -webkit-tap-highlight-color: transparent;
72
+
73
+ &__control {
74
+ flex: none;
75
+ width: 20px;
76
+ height: 20px;
77
+ display: inline-grid;
78
+ place-items: center;
79
+ border: 2px solid var(--ylf-c-border-strong, #cbd5e1);
80
+ border-radius: 50%;
81
+ background: var(--ylf-c-surface, #fff);
82
+ transition:
83
+ border-color 0.18s ease,
84
+ box-shadow 0.2s ease;
85
+ }
86
+
87
+ &:hover:not([data-disabled]) .ylf-radio__control {
88
+ border-color: var(--ylf-c-brand, #2563eb);
89
+ }
90
+
91
+ &:focus-visible {
92
+ outline: none;
93
+ }
94
+
95
+ &:focus-visible .ylf-radio__control {
96
+ box-shadow: 0 0 0 3px var(--ylf-c-brand-soft, #eff6ff);
97
+ }
98
+
99
+ &[data-state='checked'] .ylf-radio__control {
100
+ border-color: var(--ylf-c-brand, #2563eb);
101
+ }
102
+
103
+ &[data-disabled] {
104
+ opacity: 0.5;
105
+ cursor: not-allowed;
106
+ }
107
+
108
+ &__dot {
109
+ display: block;
110
+ width: 10px;
111
+ height: 10px;
112
+ border-radius: 50%;
113
+ background: var(--ylf-c-brand, #2563eb);
114
+ }
115
+
116
+ &__label {
117
+ font-size: 15px;
118
+ }
119
+ }
120
+ </style>
@@ -0,0 +1,195 @@
1
+ <script lang="ts" setup generic="T extends string = string">
2
+ import { SelectContent, SelectIcon, SelectItem, SelectItemIndicator, SelectItemText, SelectPortal, SelectRoot, SelectTrigger, SelectValue, SelectViewport } from 'reka-ui'
3
+ import { computed } from 'vue'
4
+
5
+ defineOptions({ inheritAttrs: false })
6
+
7
+ const props = withDefaults(defineProps<{
8
+ /** color 可使用 CSS 色值或公共 token,例如 var(--ylf-accent-blue) */
9
+ options: { label: string, value: T, disabled?: boolean, color?: string }[]
10
+ placeholder?: string
11
+ disabled?: boolean
12
+ }>(), {
13
+ placeholder: '请选择',
14
+ disabled: false,
15
+ })
16
+
17
+ const model = defineModel<T>()
18
+ const selectedOption = computed(() => props.options.find(option => option.value === model.value))
19
+ </script>
20
+
21
+ <template>
22
+ <SelectRoot v-model="model" :disabled="disabled">
23
+ <SelectTrigger class="ylf-select__trigger" :aria-label="$attrs.id || $attrs['aria-labelledby'] ? undefined : placeholder" v-bind="$attrs">
24
+ <SelectValue :placeholder="placeholder" class="ylf-select__label">
25
+ <span v-if="selectedOption?.color" class="ylf-select__swatch" :style="{ backgroundColor: selectedOption.color }" aria-hidden="true" />
26
+ <span>{{ selectedOption?.label ?? placeholder }}</span>
27
+ </SelectValue>
28
+ <SelectIcon class="ylf-select__icon">
29
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
30
+ <path d="M6 9l6 6 6-6" />
31
+ </svg>
32
+ </SelectIcon>
33
+ </SelectTrigger>
34
+ <SelectPortal>
35
+ <SelectContent class="ylf-select__content" position="popper" :side-offset="6">
36
+ <SelectViewport class="ylf-select__viewport">
37
+ <SelectItem
38
+ v-for="o in options"
39
+ :key="o.value"
40
+ :value="o.value"
41
+ :disabled="o.disabled"
42
+ class="ylf-select__item"
43
+ >
44
+ <SelectItemText class="ylf-select__label">
45
+ <span v-if="o.color" class="ylf-select__swatch" :style="{ backgroundColor: o.color }" aria-hidden="true" />
46
+ <span>{{ o.label }}</span>
47
+ </SelectItemText>
48
+ <SelectItemIndicator class="ylf-select__check">
49
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
50
+ <path d="M5 12.5l4.5 4.5L19 7" />
51
+ </svg>
52
+ </SelectItemIndicator>
53
+ </SelectItem>
54
+ </SelectViewport>
55
+ </SelectContent>
56
+ </SelectPortal>
57
+ </SelectRoot>
58
+ </template>
59
+
60
+ <style lang="scss">
61
+ .ylf-select__trigger {
62
+ display: inline-flex;
63
+ align-items: center;
64
+ justify-content: space-between;
65
+ gap: 8px;
66
+ min-width: 180px;
67
+ min-height: 44px;
68
+ padding: 10px 14px;
69
+ font-family: inherit;
70
+ font-size: 15px;
71
+ line-height: 1.5;
72
+ color: var(--ylf-c-text, #0f172a);
73
+ background: var(--ylf-c-surface, #fff);
74
+ border: 1px solid var(--ylf-c-border-strong, #cbd5e1);
75
+ border-radius: var(--ylf-radius, 14px);
76
+ cursor: pointer;
77
+ transition:
78
+ border-color 0.18s ease,
79
+ box-shadow 0.2s ease;
80
+
81
+ &[data-placeholder] {
82
+ color: var(--ylf-c-text-3, #64748b);
83
+ }
84
+
85
+ &:hover:not([data-disabled]) {
86
+ border-color: var(--ylf-c-brand, #2563eb);
87
+ }
88
+
89
+ &:focus-visible {
90
+ outline: none;
91
+ border-color: var(--ylf-c-brand, #2563eb);
92
+ box-shadow: 0 0 0 3px var(--ylf-c-brand-soft, #eff6ff);
93
+ }
94
+
95
+ &[data-disabled] {
96
+ opacity: 0.5;
97
+ cursor: not-allowed;
98
+ }
99
+ }
100
+
101
+ .ylf-select__label {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ gap: 8px;
105
+ min-width: 0;
106
+ }
107
+
108
+ .ylf-select__swatch {
109
+ flex: none;
110
+ box-sizing: border-box;
111
+ width: 12px;
112
+ height: 12px;
113
+ border: 1px solid color-mix(in srgb, var(--ylf-c-text, #0f172a) 15%, transparent);
114
+ border-radius: 50%;
115
+ }
116
+
117
+ .ylf-select__icon {
118
+ flex: none;
119
+ display: inline-flex;
120
+ color: var(--ylf-c-text-3, #64748b);
121
+
122
+ svg {
123
+ width: 18px;
124
+ height: 18px;
125
+ }
126
+ }
127
+
128
+ .ylf-select__content {
129
+ z-index: 105;
130
+ box-sizing: border-box;
131
+ min-width: var(--reka-select-trigger-width);
132
+ max-width: var(--reka-select-content-available-width);
133
+ max-height: var(--reka-select-content-available-height);
134
+ padding: 6px;
135
+ background: var(--ylf-c-surface, #fff);
136
+ border: 1px solid var(--ylf-c-border, #e2e8f0);
137
+ border-radius: var(--ylf-radius, 14px);
138
+ box-shadow: var(--ylf-shadow-lg, 0 22px 56px -14px rgba(15, 23, 42, 0.22));
139
+
140
+ &[data-state='open'] {
141
+ animation: ylf-pop-in 0.16s ease;
142
+ }
143
+ }
144
+
145
+ .ylf-select__viewport {
146
+ max-height: calc(var(--reka-select-content-available-height) - 14px);
147
+ }
148
+
149
+ .ylf-select__item {
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: space-between;
153
+ gap: 8px;
154
+ padding: 8px 12px;
155
+ font-size: 14px;
156
+ color: var(--ylf-c-text, #0f172a);
157
+ border-radius: var(--ylf-radius-sm, 10px);
158
+ cursor: pointer;
159
+ user-select: none;
160
+ outline: none;
161
+
162
+ &[data-highlighted] {
163
+ background: var(--ylf-c-brand-soft, #eff6ff);
164
+ color: var(--ylf-c-brand, #2563eb);
165
+ }
166
+
167
+ &[data-disabled] {
168
+ opacity: 0.45;
169
+ pointer-events: none;
170
+ }
171
+ }
172
+
173
+ .ylf-select__check {
174
+ display: inline-flex;
175
+ color: var(--ylf-c-brand, #2563eb);
176
+
177
+ svg {
178
+ width: 15px;
179
+ height: 15px;
180
+ }
181
+ }
182
+
183
+ @keyframes ylf-pop-in {
184
+ from {
185
+ opacity: 0;
186
+ transform: scale(0.96);
187
+ }
188
+ }
189
+
190
+ @media (prefers-reduced-motion: reduce) {
191
+ .ylf-select__content[data-state='open'] {
192
+ animation: none;
193
+ }
194
+ }
195
+ </style>
@@ -0,0 +1,68 @@
1
+ <script lang="ts" setup>
2
+ import type { YlfAccentTone } from './theme'
3
+ import { Separator } from 'reka-ui'
4
+
5
+ withDefaults(defineProps<{
6
+ orientation?: 'horizontal' | 'vertical'
7
+ /** 仅装饰(不被屏幕阅读器作为分节朗读) */
8
+ decorative?: boolean
9
+ variant?: 'neutral' | 'brand' | 'accent' | 'spectrum'
10
+ tone?: YlfAccentTone
11
+ }>(), {
12
+ orientation: 'horizontal',
13
+ decorative: true,
14
+ variant: 'neutral',
15
+ tone: 'blue',
16
+ })
17
+ </script>
18
+
19
+ <template>
20
+ <Separator
21
+ class="ylf-separator"
22
+ :class="[`ylf-separator--${orientation}`, `ylf-separator--${variant}`]"
23
+ :data-ylf-tone="tone"
24
+ :orientation="orientation"
25
+ :decorative="decorative"
26
+ />
27
+ </template>
28
+
29
+ <style lang="scss">
30
+ .ylf-separator {
31
+ --_separator-size: 1px;
32
+
33
+ flex: none;
34
+ background: var(--ylf-c-border, #e2e8f0);
35
+
36
+ &--horizontal {
37
+ width: 100%;
38
+ height: var(--_separator-size);
39
+ }
40
+
41
+ &--vertical {
42
+ width: var(--_separator-size);
43
+ height: 100%;
44
+ align-self: stretch;
45
+ }
46
+
47
+ &--brand {
48
+ background: var(--ylf-c-brand, #2563eb);
49
+ }
50
+
51
+ &--accent {
52
+ --_separator-size: 2px;
53
+
54
+ background: var(--ylf-accent, var(--ylf-c-brand, #2563eb));
55
+ }
56
+
57
+ &--spectrum {
58
+ --_separator-size: 4px;
59
+
60
+ border-radius: var(--ylf-radius-pill, 999px);
61
+ background: linear-gradient(90deg, var(--ylf-spectrum-stops, #2563eb, #facc15));
62
+
63
+ &.ylf-separator--vertical {
64
+ background: linear-gradient(180deg, var(--ylf-spectrum-stops, #2563eb, #facc15));
65
+ }
66
+ }
67
+ }
68
+ </style>