@byyuurin/ui 0.0.8 → 0.0.10

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 (151) hide show
  1. package/README.md +0 -3
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +4 -3
  4. package/dist/module.mjs.map +1 -1
  5. package/dist/runtime/app/injections.d.ts +9299 -8
  6. package/dist/runtime/app/injections.js +35 -5
  7. package/dist/runtime/components/Accordion.vue +17 -19
  8. package/dist/runtime/components/Alert.vue +6 -9
  9. package/dist/runtime/components/App.vue +14 -19
  10. package/dist/runtime/components/Avatar.vue +5 -8
  11. package/dist/runtime/components/AvatarGroup.vue +2 -5
  12. package/dist/runtime/components/Badge.vue +3 -6
  13. package/dist/runtime/components/Breadcrumb.vue +22 -23
  14. package/dist/runtime/components/Button.vue +2 -3
  15. package/dist/runtime/components/ButtonGroup.vue +2 -5
  16. package/dist/runtime/components/Calendar.vue +185 -0
  17. package/dist/runtime/components/Card.vue +2 -5
  18. package/dist/runtime/components/Carousel.vue +11 -16
  19. package/dist/runtime/components/Checkbox.vue +13 -11
  20. package/dist/runtime/components/Chip.vue +6 -9
  21. package/dist/runtime/components/Collapsible.vue +2 -5
  22. package/dist/runtime/components/Drawer.vue +88 -45
  23. package/dist/runtime/components/DropdownMenu.vue +143 -0
  24. package/dist/runtime/components/DropdownMenuContent.vue +188 -0
  25. package/dist/runtime/components/Form.vue +311 -0
  26. package/dist/runtime/components/FormItem.vue +129 -0
  27. package/dist/runtime/components/Input.vue +34 -23
  28. package/dist/runtime/components/InputNumber.vue +23 -18
  29. package/dist/runtime/components/Kbd.vue +2 -6
  30. package/dist/runtime/components/Link.vue +25 -7
  31. package/dist/runtime/components/Modal.vue +31 -22
  32. package/dist/runtime/components/OverlayProvider.vue +29 -0
  33. package/dist/runtime/components/Pagination.vue +34 -27
  34. package/dist/runtime/components/PinInput.vue +23 -17
  35. package/dist/runtime/components/Popover.vue +5 -8
  36. package/dist/runtime/components/Progress.vue +2 -5
  37. package/dist/runtime/components/RadioGroup.vue +52 -49
  38. package/dist/runtime/components/ScrollArea.vue +2 -6
  39. package/dist/runtime/components/Select.vue +96 -89
  40. package/dist/runtime/components/Separator.vue +2 -6
  41. package/dist/runtime/components/Skeleton.vue +2 -5
  42. package/dist/runtime/components/Slider.vue +13 -11
  43. package/dist/runtime/components/Switch.vue +18 -11
  44. package/dist/runtime/components/Table.vue +23 -13
  45. package/dist/runtime/components/Tabs.vue +14 -16
  46. package/dist/runtime/components/Textarea.vue +20 -17
  47. package/dist/runtime/components/Toast.vue +12 -13
  48. package/dist/runtime/components/{Toaster.vue → ToastProvider.vue} +18 -16
  49. package/dist/runtime/components/Tooltip.vue +5 -8
  50. package/dist/runtime/composables/useFormItem.d.ts +27 -0
  51. package/dist/runtime/composables/useFormItem.js +64 -0
  52. package/dist/runtime/composables/useKbd.d.ts +1 -1
  53. package/dist/runtime/composables/useOverlay.d.ts +29 -0
  54. package/dist/runtime/composables/useOverlay.js +69 -0
  55. package/dist/runtime/composables/useTheme.d.ts +6 -2
  56. package/dist/runtime/composables/useTheme.js +10 -3
  57. package/dist/runtime/composables/useToast.d.ts +4 -20
  58. package/dist/runtime/composables/useToast.js +6 -5
  59. package/dist/runtime/index.d.ts +6 -2
  60. package/dist/runtime/index.js +6 -2
  61. package/dist/runtime/locale/en.js +6 -0
  62. package/dist/runtime/locale/zh-tw.js +6 -0
  63. package/dist/runtime/theme/accordion.js +3 -3
  64. package/dist/runtime/theme/alert.js +3 -3
  65. package/dist/runtime/theme/app.d.ts +1 -0
  66. package/dist/runtime/theme/app.js +2 -1
  67. package/dist/runtime/theme/avatar.js +2 -2
  68. package/dist/runtime/theme/badge.d.ts +21 -45
  69. package/dist/runtime/theme/breadcrumb.d.ts +3 -3
  70. package/dist/runtime/theme/breadcrumb.js +5 -5
  71. package/dist/runtime/theme/button.d.ts +111 -57
  72. package/dist/runtime/theme/button.js +13 -13
  73. package/dist/runtime/theme/calendar.d.ts +56 -0
  74. package/dist/runtime/theme/calendar.js +69 -0
  75. package/dist/runtime/theme/card.js +6 -6
  76. package/dist/runtime/theme/carousel.js +1 -1
  77. package/dist/runtime/theme/checkbox.js +5 -5
  78. package/dist/runtime/theme/chip.d.ts +11 -44
  79. package/dist/runtime/theme/chip.js +3 -3
  80. package/dist/runtime/theme/drawer.d.ts +85 -47
  81. package/dist/runtime/theme/drawer.js +46 -19
  82. package/dist/runtime/theme/dropdown-menu.d.ts +71 -0
  83. package/dist/runtime/theme/dropdown-menu.js +83 -0
  84. package/dist/runtime/theme/form-item.d.ts +76 -0
  85. package/dist/runtime/theme/form-item.js +34 -0
  86. package/dist/runtime/theme/form.d.ts +8 -0
  87. package/dist/runtime/theme/form.js +7 -0
  88. package/dist/runtime/theme/index.d.ts +5 -1
  89. package/dist/runtime/theme/index.js +5 -1
  90. package/dist/runtime/theme/input-number.d.ts +41 -61
  91. package/dist/runtime/theme/input-number.js +1 -1
  92. package/dist/runtime/theme/input.d.ts +99 -71
  93. package/dist/runtime/theme/input.js +15 -15
  94. package/dist/runtime/theme/kbd.d.ts +2 -2
  95. package/dist/runtime/theme/kbd.js +1 -1
  96. package/dist/runtime/theme/link.d.ts +1 -1
  97. package/dist/runtime/theme/link.js +3 -3
  98. package/dist/runtime/theme/modal.d.ts +5 -33
  99. package/dist/runtime/theme/modal.js +4 -4
  100. package/dist/runtime/theme/pagination.d.ts +27 -3
  101. package/dist/runtime/theme/pagination.js +6 -2
  102. package/dist/runtime/theme/pinInput.d.ts +42 -42
  103. package/dist/runtime/theme/pinInput.js +13 -13
  104. package/dist/runtime/theme/progress.d.ts +117 -53
  105. package/dist/runtime/theme/progress.js +4 -4
  106. package/dist/runtime/theme/radio-group.d.ts +2 -2
  107. package/dist/runtime/theme/radio-group.js +7 -7
  108. package/dist/runtime/theme/select.d.ts +100 -84
  109. package/dist/runtime/theme/select.js +21 -20
  110. package/dist/runtime/theme/separator.d.ts +13 -28
  111. package/dist/runtime/theme/separator.js +1 -1
  112. package/dist/runtime/theme/skeleton.d.ts +1 -1
  113. package/dist/runtime/theme/skeleton.js +1 -1
  114. package/dist/runtime/theme/slider.js +1 -1
  115. package/dist/runtime/theme/switch.js +5 -5
  116. package/dist/runtime/theme/table.d.ts +3 -0
  117. package/dist/runtime/theme/table.js +8 -7
  118. package/dist/runtime/theme/tabs.d.ts +51 -68
  119. package/dist/runtime/theme/tabs.js +10 -10
  120. package/dist/runtime/theme/textarea.d.ts +37 -43
  121. package/dist/runtime/theme/textarea.js +13 -13
  122. package/dist/runtime/theme/{toaster.d.ts → toast-provider.d.ts} +26 -41
  123. package/dist/runtime/theme/toast.js +5 -5
  124. package/dist/runtime/theme/tooltip.js +1 -1
  125. package/dist/runtime/types/components.d.ts +6 -1
  126. package/dist/runtime/types/form.d.ts +45 -0
  127. package/dist/runtime/types/form.js +0 -0
  128. package/dist/runtime/types/index.d.ts +5 -2
  129. package/dist/runtime/types/index.js +1 -0
  130. package/dist/runtime/types/locale.d.ts +6 -0
  131. package/dist/runtime/types/utils.d.ts +35 -12
  132. package/dist/runtime/utils/extend-theme.js +15 -4
  133. package/dist/runtime/utils/form.d.ts +5 -0
  134. package/dist/runtime/utils/form.js +24 -0
  135. package/dist/runtime/utils/index.d.ts +2 -0
  136. package/dist/runtime/utils/index.js +4 -0
  137. package/dist/runtime/utils/link.d.ts +4 -26
  138. package/dist/runtime/utils/link.js +10 -3
  139. package/dist/shared/ui.3e7fad19.mjs +5 -0
  140. package/dist/shared/ui.3e7fad19.mjs.map +1 -0
  141. package/dist/unocss.mjs +21 -16
  142. package/dist/unocss.mjs.map +1 -1
  143. package/dist/unplugin.mjs +1 -1
  144. package/dist/vite.mjs +1 -1
  145. package/package.json +20 -18
  146. package/dist/runtime/components/ModalProvider.vue +0 -11
  147. package/dist/runtime/composables/useModal.d.ts +0 -10
  148. package/dist/runtime/composables/useModal.js +0 -47
  149. package/dist/shared/ui.ba24b380.mjs +0 -4
  150. package/dist/shared/ui.ba24b380.mjs.map +0 -1
  151. /package/dist/runtime/theme/{toaster.js → toast-provider.js} +0 -0
@@ -7,9 +7,9 @@ import type { input } from '../theme'
7
7
  import type { ComponentAttrs } from '../types'
8
8
 
9
9
  export interface InputEmits {
10
- (e: 'update:modelValue', payload: string | number): void
11
- (e: 'blur', event: FocusEvent): void
12
- (e: 'change', event: Event): void
10
+ 'update:modelValue': [payload: string | number]
11
+ 'blur': [event: FocusEvent]
12
+ 'change': [event: Event]
13
13
  }
14
14
 
15
15
  export interface InputSlots {
@@ -48,6 +48,7 @@ import { Primitive } from 'reka-ui'
48
48
  import { computed, onMounted, ref } from 'vue'
49
49
  import { useButtonGroup } from '../composables/useButtonGroup'
50
50
  import { useComponentIcons } from '../composables/useComponentIcons'
51
+ import { useFormItem } from '../composables/useFormItem'
51
52
  import { useTheme } from '../composables/useTheme'
52
53
  import { looseToNumber } from '../utils'
53
54
 
@@ -68,22 +69,31 @@ const [modelValue, modelModifiers] = defineModel<string | number>()
68
69
 
69
70
  const inputRef = ref<HTMLInputElement | null>(null)
70
71
 
71
- const { size, orientation } = useButtonGroup(props)
72
+ const {
73
+ size: formItemSize,
74
+ id,
75
+ name,
76
+ highlight,
77
+ disabled,
78
+ ariaAttrs,
79
+ emitFormBlur,
80
+ emitFormInput,
81
+ emitFormChange,
82
+ emitFormFocus,
83
+ } = useFormItem<InputProps>(props, { deferInputValidation: true })
84
+ const { size: buttonGroupSize, orientation } = useButtonGroup(props)
72
85
  const { isLeading, leadingIconName, isTrailing, trailingIconName } = useComponentIcons(props)
73
86
 
74
- const { theme, createStyler } = useTheme()
75
- const style = computed(() => {
76
- const styler = createStyler(theme.value.input)
77
- return styler({
78
- ...props,
79
- // @ts-expect-error ignore type
80
- type: props.type,
81
- size: size.value,
82
- groupOrientation: orientation.value,
83
- leading: isLeading.value || !!slots.leading,
84
- trailing: isTrailing.value || !!slots.trailing,
85
- })
86
- })
87
+ const { generateStyle } = useTheme()
88
+ const style = computed(() => generateStyle('input', {
89
+ ...props,
90
+ type: props.type as InputVariants['type'],
91
+ size: buttonGroupSize.value || formItemSize.value,
92
+ highlight: highlight.value,
93
+ groupOrientation: orientation.value,
94
+ leading: isLeading.value || !!slots.leading,
95
+ trailing: isTrailing.value || !!slots.trailing,
96
+ }))
87
97
 
88
98
  function autoFocus() {
89
99
  if (props.autofocus)
@@ -98,6 +108,7 @@ function updateInput(value: string) {
98
108
  value = looseToNumber(value)
99
109
 
100
110
  modelValue.value = value
111
+ emitFormInput()
101
112
  }
102
113
 
103
114
  function onInput(event: Event) {
@@ -115,10 +126,12 @@ function onChange(event: Event) {
115
126
  (event.target as HTMLInputElement).value = value.trim()
116
127
 
117
128
  emit('change', event)
129
+ emitFormChange()
118
130
  }
119
131
 
120
132
  function onBlur(event: FocusEvent) {
121
133
  emit('blur', event)
134
+ emitFormBlur()
122
135
  }
123
136
 
124
137
  defineExpose({
@@ -136,7 +149,7 @@ onMounted(() => {
136
149
  <Primitive
137
150
  :as="as"
138
151
  :class="style.base({ class: [props.class, props.ui?.base] })"
139
- :aria-disabled="props.disabled ? true : undefined"
152
+ :aria-disabled="disabled ? true : undefined"
140
153
  >
141
154
  <span v-if="isLeading || slots.leading" :class="style.leading({ class: props.ui?.leading })">
142
155
  <slot name="leading">
@@ -148,20 +161,18 @@ onMounted(() => {
148
161
  </span>
149
162
 
150
163
  <input
151
- :id="id"
152
164
  ref="inputRef"
165
+ :class="style.input({ class: props.ui?.input })"
153
166
  :type="props.type"
154
167
  :value="modelValue"
155
- :name="props.name"
156
168
  :placeholder="props.placeholder"
157
- :class="style.input({ class: props.ui?.input })"
158
- :disabled="props.disabled"
159
169
  :required="props.required"
160
170
  :autocomplete="props.autocomplete"
161
- v-bind="$attrs"
171
+ v-bind="{ ...$attrs, ...ariaAttrs, id, name, disabled }"
162
172
  @input="onInput"
163
173
  @blur="onBlur"
164
174
  @change="onChange"
175
+ @focus="emitFormFocus"
165
176
  />
166
177
 
167
178
  <slot></slot>
@@ -5,9 +5,9 @@ import type { inputNumber } from '../theme'
5
5
  import type { ButtonProps, ComponentAttrs } from '../types'
6
6
 
7
7
  export interface InputNumberEmits {
8
- (e: 'update:modelValue', payload: number): void
9
- (e: 'blur', event: FocusEvent): void
10
- (e: 'change', payload: Event): void
8
+ 'update:modelValue': [payload: number]
9
+ 'blur': [event: FocusEvent]
10
+ 'change': [payload: Event]
11
11
  }
12
12
 
13
13
  export interface InputNumberSlots {
@@ -63,6 +63,7 @@ export interface InputNumberProps extends ComponentAttrs<typeof inputNumber>, Pi
63
63
  import { reactivePick } from '@vueuse/core'
64
64
  import { NumberFieldDecrement, NumberFieldIncrement, NumberFieldInput, NumberFieldRoot, useForwardPropsEmits } from 'reka-ui'
65
65
  import { computed, onMounted, ref } from 'vue'
66
+ import { useFormItem } from '../composables/useFormItem'
66
67
  import { useLocale } from '../composables/useLocale'
67
68
  import { useTheme } from '../composables/useTheme'
68
69
  import Button from './Button.vue'
@@ -82,15 +83,18 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', '
82
83
 
83
84
  const inputRef = ref<InstanceType<typeof NumberFieldInput> | null>(null)
84
85
 
85
- const { t } = useLocale()
86
- const { theme, createStyler } = useTheme()
86
+ const { t, code: codeLocale } = useLocale()
87
+ const locale = computed(() => props.locale || codeLocale.value)
88
+ const { id, name, size, highlight, disabled, ariaAttrs, emitFormBlur, emitFormFocus, emitFormInput, emitFormChange } = useFormItem<InputNumberProps>(props)
89
+ const { theme, generateStyle } = useTheme()
87
90
  const incrementIcon = computed(() => props.incrementIcon || (props.orientation === 'horizontal' ? theme.value.app.icons.plus : theme.value.app.icons.chevronUp))
88
91
  const decrementIcon = computed(() => props.decrementIcon || (props.orientation === 'horizontal' ? theme.value.app.icons.minus : theme.value.app.icons.chevronDown))
89
92
 
90
- const style = computed(() => {
91
- const styler = createStyler(theme.value.inputNumber)
92
- return styler(props)
93
- })
93
+ const style = computed(() => generateStyle('inputNumber', {
94
+ ...props,
95
+ size: size.value,
96
+ highlight: highlight.value,
97
+ }))
94
98
 
95
99
  onMounted(() => {
96
100
  setTimeout(() => {
@@ -111,35 +115,36 @@ function onUpdate(value: number) {
111
115
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
112
116
  const event = new Event('change', { target: { value } })
113
117
  emit('change', event)
118
+ emitFormChange()
119
+ emitFormInput()
114
120
  }
115
121
 
116
122
  function onBlur(event: FocusEvent) {
117
123
  emit('blur', event)
124
+ emitFormBlur()
118
125
  }
119
126
  </script>
120
127
 
121
128
  <template>
122
129
  <NumberFieldRoot
123
- v-bind="rootProps"
124
- :id="props.id"
125
- :name="props.name"
126
- :disabled="props.disabled"
127
- :locale="props.locale"
130
+ v-bind="{ ...rootProps, id, name, disabled }"
128
131
  :class="style.base({ class: [props.class, props.ui?.base] })"
129
- :aria-disabled="props.disabled ? true : undefined"
132
+ :locale="locale"
133
+ :aria-disabled="disabled ? true : undefined"
130
134
  @update:model-value="onUpdate"
131
135
  >
132
136
  <NumberFieldInput
133
- v-bind="$attrs"
137
+ v-bind="{ ...$attrs, ...ariaAttrs }"
134
138
  ref="inputRef"
135
139
  :placeholder="props.placeholder"
136
140
  :required="props.required"
137
141
  :class="style.input({ class: props.ui?.input })"
138
142
  @blur="onBlur"
143
+ @focus="emitFormFocus"
139
144
  />
140
145
 
141
146
  <div :class="style.increment({ class: props.ui?.increment })">
142
- <NumberFieldIncrement as-child :disabled="props.disabled">
147
+ <NumberFieldIncrement as-child :disabled="disabled">
143
148
  <slot name="increment">
144
149
  <Button
145
150
  :icon="incrementIcon"
@@ -153,7 +158,7 @@ function onBlur(event: FocusEvent) {
153
158
  </div>
154
159
 
155
160
  <div :class="style.decrement({ class: props.ui?.decrement })">
156
- <NumberFieldDecrement as-child :disabled="props.disabled">
161
+ <NumberFieldDecrement as-child :disabled="disabled">
157
162
  <slot name="decrement">
158
163
  <Button
159
164
  :icon="decrementIcon"
@@ -36,12 +36,8 @@ const props = withDefaults(defineProps<KbdProps>(), {
36
36
  defineSlots<KbdSlots>()
37
37
 
38
38
  const { getKbdKey } = useKbd()
39
-
40
- const { theme, createStyler } = useTheme()
41
- const style = computed(() => {
42
- const styler = createStyler(theme.value.kbd)
43
- return styler(props)
44
- })
39
+ const { generateStyle } = useTheme()
40
+ const style = computed(() => generateStyle('kbd', props))
45
41
  </script>
46
42
 
47
43
  <template>
@@ -163,16 +163,24 @@ function isPartiallyEqual(item1: any, item2: any) {
163
163
  }
164
164
 
165
165
  const isExternalLink = computed(() => {
166
- if (!props.to)
166
+ const to = props.to || props.href
167
+
168
+ if (!to)
167
169
  return false
168
170
 
169
- return typeof props.to === 'string' && hasProtocol(props.to, { acceptRelative: true })
171
+ if (props.target === '_blank')
172
+ return true
173
+
174
+ return typeof to === 'string' && hasProtocol(to, { acceptRelative: true })
170
175
  })
171
176
 
172
177
  function isLinkActive({ route: linkRoute, isActive, isExactActive }: any) {
173
178
  if (props.active !== undefined)
174
179
  return props.active
175
180
 
181
+ if (isExternalLink.value || !props.to)
182
+ return false
183
+
176
184
  if (props.exactQuery === 'partial') {
177
185
  if (!isPartiallyEqual(linkRoute?.query, route.value?.query))
178
186
  return false
@@ -273,19 +281,28 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {
273
281
  v-else
274
282
  v-slot="{ href, navigate, route: linkRoute, isActive, isExactActive }"
275
283
  v-bind="linkProps"
276
- :to="to || '#'"
284
+ :to="isExternalLink ? '#' : to || '#'"
277
285
  custom
278
286
  >
279
287
  <template v-if="custom">
280
288
  <slot
281
289
  v-bind="{
282
290
  ...$attrs,
291
+ ...isExternalLink
292
+ ? {
293
+ href: to || props.href,
294
+ target: props.target,
295
+ }
296
+ : {
297
+ href: to ? href : undefined,
298
+ target: undefined,
299
+ },
283
300
  as,
284
301
  type,
285
302
  disabled,
286
- href: to ? href : undefined,
287
303
  navigate,
288
304
  active: isLinkActive({ route: linkRoute, isActive, isExactActive }),
305
+ isExternal: isExternalLink,
289
306
  }"
290
307
  >
291
308
  {{ props.label }}
@@ -301,6 +318,7 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {
301
318
  href: to ? href : undefined,
302
319
  navigate,
303
320
  }"
321
+ :is-external="isExternalLink"
304
322
  :class="resolveLinkClass({ route: linkRoute, isActive, isExactActive })"
305
323
  >
306
324
  <slot :active="isLinkActive({ route: linkRoute, isActive, isExactActive })">
@@ -318,7 +336,7 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {
318
336
  type,
319
337
  disabled,
320
338
  href: to || href,
321
- target: isExternalLink ? '_blank' : target || undefined,
339
+ target: target || (isExternalLink ? '_blank' : undefined),
322
340
  active: false,
323
341
  }"
324
342
  >
@@ -332,8 +350,8 @@ function resolveLinkClass({ route, isActive, isExactActive }: any = {}) {
332
350
  as,
333
351
  type,
334
352
  disabled,
335
- href: ((to || href) as string),
336
- target: isExternalLink ? '_blank' : target || undefined,
353
+ href: ((typeof to === 'string' ? to : href) as string),
354
+ target: target || (isExternalLink ? '_blank' : undefined),
337
355
  }"
338
356
  :is-external="isExternalLink"
339
357
  :class="resolveLinkClass()"
@@ -1,20 +1,22 @@
1
1
  <script lang="ts">
2
2
  import type { VariantProps } from '@byyuurin/ui-kit'
3
- import type { DialogContentProps, DialogRootEmits, DialogRootProps } from 'reka-ui'
3
+ import type { DialogContentEmits, DialogContentProps, DialogRootEmits, DialogRootProps } from 'reka-ui'
4
4
  import type { modal } from '../theme'
5
- import type { ButtonProps, ComponentAttrs } from '../types'
5
+ import type { ButtonProps, ComponentAttrs, EmitsToProps } from '../types'
6
6
 
7
- export interface ModalEmits extends DialogRootEmits {}
7
+ export interface ModalEmits extends DialogRootEmits {
8
+ 'after-leave': []
9
+ }
8
10
 
9
11
  export interface ModalSlots {
10
12
  default?: (props: { open: boolean }) => any
11
- content?: (props?: {}) => any
12
- header?: (props?: {}) => any
13
- title?: (props?: {}) => any
14
- description?: (props?: {}) => any
15
- close?: (props?: {}) => any
16
- body?: (props?: {}) => any
17
- footer?: (props?: {}) => any
13
+ content?: any
14
+ header?: any
15
+ title?: any
16
+ description?: any
17
+ close?: (props: { ui: ComponentAttrs<typeof modal>['ui'] }) => any
18
+ body?: any
19
+ footer?: any
18
20
  }
19
21
 
20
22
  type ModalVariants = VariantProps<typeof modal>
@@ -23,7 +25,7 @@ export interface ModalProps extends ComponentAttrs<typeof modal>, DialogRootProp
23
25
  title?: string
24
26
  description?: string
25
27
  size?: ModalVariants['size']
26
- content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'>
28
+ content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DialogContentEmits>>
27
29
  /** @default true */
28
30
  portal?: boolean
29
31
  /** @default true */
@@ -35,6 +37,10 @@ export interface ModalProps extends ComponentAttrs<typeof modal>, DialogRootProp
35
37
  * @default true
36
38
  */
37
39
  dismissible?: boolean
40
+ /**
41
+ * Display a close button to dismiss the modal.
42
+ * @default true
43
+ */
38
44
  close?: ButtonProps | boolean
39
45
  /** @default app.icons.close */
40
46
  closeIcon?: string
@@ -43,7 +49,7 @@ export interface ModalProps extends ComponentAttrs<typeof modal>, DialogRootProp
43
49
 
44
50
  <script setup lang="ts">
45
51
  import { reactivePick } from '@vueuse/core'
46
- import { DialogClose, DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger, useForwardPropsEmits } from 'reka-ui'
52
+ import { DialogClose, DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger, useForwardPropsEmits, VisuallyHidden } from 'reka-ui'
47
53
  import { computed, toRef } from 'vue'
48
54
  import { useLocale } from '../composables/useLocale'
49
55
  import { useTheme } from '../composables/useTheme'
@@ -60,7 +66,10 @@ const props = withDefaults(defineProps<ModalProps>(), {
60
66
  const emit = defineEmits<ModalEmits>()
61
67
  const slots = defineSlots<ModalSlots>()
62
68
  const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emit)
63
- const contentProps = toRef(() => props.content)
69
+ const contentProps = toRef(() => ({
70
+ ...props.content,
71
+ ...(slots.content || slots.header || (!props.description && !slots.description)) ? { 'aria-describedby': undefined } : {},
72
+ }))
64
73
  const contentEvents = computed(() => {
65
74
  if (props.dismissible)
66
75
  return {}
@@ -73,11 +82,8 @@ const contentEvents = computed(() => {
73
82
  })
74
83
 
75
84
  const { t } = useLocale()
76
- const { theme, createStyler } = useTheme()
77
- const style = computed(() => {
78
- const styler = createStyler(theme.value.modal)
79
- return styler(props)
80
- })
85
+ const { theme, generateStyle } = useTheme()
86
+ const style = computed(() => generateStyle('modal', props))
81
87
  </script>
82
88
 
83
89
  <template>
@@ -92,7 +98,11 @@ const style = computed(() => {
92
98
  <DialogPortal :disabled="!props.portal">
93
99
  <DialogOverlay v-if="props.overlay" :class="style.overlay({ class: props.ui?.overlay })" />
94
100
 
95
- <DialogContent :class="style.content({ class: props.ui?.content })" v-bind="contentProps" v-on="contentEvents">
101
+ <DialogContent :class="style.content({ class: props.ui?.content })" v-bind="contentProps" v-on="contentEvents" @after-leave="emit('after-leave')">
102
+ <VisuallyHidden v-if="slots.content || slots.header || (!props.title && !slots.title)">
103
+ <DialogTitle />
104
+ </VisuallyHidden>
105
+
96
106
  <slot name="content">
97
107
  <div
98
108
  v-if="slots.header || props.title || slots.title || props.description || slots.description || props.close || slots.close"
@@ -108,10 +118,9 @@ const style = computed(() => {
108
118
  </slot>
109
119
  </DialogTitle>
110
120
 
111
- <DialogClose as-child>
112
- <slot name="close">
121
+ <DialogClose v-if="props.close || slots.close" as-child>
122
+ <slot name="close" :ui="props.ui">
113
123
  <Button
114
- v-if="props.close"
115
124
  variant="ghost"
116
125
  :icon="props.closeIcon || theme.app.icons.close"
117
126
  v-bind="typeof props.close === 'boolean' ? {} : props.close"
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { OverlayComponentState } from '../composables/useOverlay'
4
+ import { useOverlay } from '../composables/useOverlay'
5
+
6
+ const { overlays, unmount, close } = useOverlay()
7
+ const mountedOverlays = computed(() => overlays.filter((overlay) => overlay.isMounted))
8
+
9
+ function onOverlayUnmount(id: OverlayComponentState['id']) {
10
+ close(id)
11
+ unmount(id)
12
+ }
13
+
14
+ function onClose(id: OverlayComponentState['id'], value: any) {
15
+ close(id, value)
16
+ }
17
+ </script>
18
+
19
+ <template>
20
+ <component
21
+ :is="overlay.component"
22
+ v-for="overlay in mountedOverlays"
23
+ :key="overlay.id"
24
+ v-bind="overlay.props"
25
+ v-model:open="overlay.modelValue"
26
+ @close="onClose(overlay.id, $event)"
27
+ @after-leave="onOverlayUnmount(overlay.id)"
28
+ />
29
+ </template>
@@ -70,6 +70,10 @@ export interface PaginationProps extends ComponentAttrs<typeof pagination>, Pick
70
70
  * @default true
71
71
  */
72
72
  showControls?: boolean
73
+ /**
74
+ * A function to render page controls as links.
75
+ */
76
+ to?: (page: number) => ButtonProps['to']
73
77
  }
74
78
  </script>
75
79
 
@@ -77,6 +81,7 @@ export interface PaginationProps extends ComponentAttrs<typeof pagination>, Pick
77
81
  import { reactivePick } from '@vueuse/core'
78
82
  import { PaginationEllipsis, PaginationFirst, PaginationLast, PaginationList, PaginationListItem, PaginationNext, PaginationPrev, PaginationRoot, useForwardPropsEmits } from 'reka-ui'
79
83
  import { computed } from 'vue'
84
+ import { useLocale } from '../composables/useLocale'
80
85
  import { useTheme } from '../composables/useTheme'
81
86
  import Button from './Button.vue'
82
87
 
@@ -95,71 +100,73 @@ const slots = defineSlots<PaginationSlots>()
95
100
 
96
101
  const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultPage', 'disabled', 'itemsPerPage', 'page', 'showEdges', 'siblingCount', 'total'), emit)
97
102
 
98
- const { theme, createStyler } = useTheme()
103
+ const { dir } = useLocale()
104
+ const { theme, generateStyle } = useTheme()
105
+ const style = computed(() => generateStyle('pagination', props))
99
106
 
100
- const firstIcon = computed(() => props.firstIcon || theme.value.app.icons.chevronDoubleLeft)
101
- const prevIcon = computed(() => props.prevIcon || theme.value.app.icons.chevronLeft)
102
- const nextIcon = computed(() => props.nextIcon || theme.value.app.icons.chevronRight)
103
- const lastIcon = computed(() => props.lastIcon || theme.value.app.icons.chevronDoubleRight)
107
+ const firstIcon = computed(() => props.firstIcon || (dir.value === 'rtl' ? theme.value.app.icons.chevronDoubleRight : theme.value.app.icons.chevronDoubleLeft))
108
+ const prevIcon = computed(() => props.prevIcon || (dir.value === 'rtl' ? theme.value.app.icons.chevronRight : theme.value.app.icons.chevronLeft))
109
+ const nextIcon = computed(() => props.nextIcon || (dir.value === 'rtl' ? theme.value.app.icons.chevronLeft : theme.value.app.icons.chevronRight))
110
+ const lastIcon = computed(() => props.lastIcon || (dir.value === 'rtl' ? theme.value.app.icons.chevronDoubleLeft : theme.value.app.icons.chevronDoubleRight))
104
111
  const ellipsisIcon = computed(() => props.ellipsisIcon || theme.value.app.icons.ellipsis)
105
-
106
- const style = computed(() => {
107
- const styler = createStyler(theme.value.pagination)
108
- return styler(props)
109
- })
110
112
  </script>
111
113
 
112
114
  <template>
113
115
  <PaginationRoot v-slot="{ page, pageCount }" v-bind="rootProps" :class="style.root({ class: [props.class, props.ui?.root] })">
114
116
  <PaginationList v-slot="{ items }" :class="style.list({ class: props.ui?.list })">
115
- <PaginationFirst v-if="props.showControls || !!slots.first" as-child>
117
+ <PaginationFirst v-if="props.showControls || !!slots.first" :class="style.first({ class: props.ui?.first })" as-child>
116
118
  <slot name="first">
117
- <Button :class="style.item({ class: props.ui?.item })" :variant="props.variant" :size="props.size" :icon="firstIcon" />
119
+ <Button :variant="props.variant" :size="props.size" :icon="firstIcon" :to="props.to?.(1)" />
118
120
  </slot>
119
121
  </PaginationFirst>
120
- <PaginationPrev v-if="props.showControls || !!slots.prev" as-child>
122
+ <PaginationPrev v-if="props.showControls || !!slots.prev" :class="style.prev({ class: props.ui?.prev })" as-child>
121
123
  <slot name="prev">
122
- <Button :class="style.item({ class: props.ui?.item })" :variant="props.variant" :size="props.size" :icon="prevIcon" />
124
+ <Button :variant="props.variant" :size="props.size" :icon="prevIcon" :to="page > 1 ? props.to?.(page - 1) : undefined" />
123
125
  </slot>
124
126
  </PaginationPrev>
125
127
 
126
128
  <template v-for="(item, index) in items">
127
- <PaginationListItem v-if="item.type === 'page'" :key="index" as-child :value="item.value">
129
+ <PaginationListItem v-if="item.type === 'page'" :key="index" :class="style.item({ class: props.ui?.item })" :value="item.value" as-child>
128
130
  <slot name="item" v-bind="{ item, index, page, pageCount }">
129
131
  <Button
130
- :class="style.item({ class: props.ui?.item })"
131
132
  :variant="props.page === item.value ? props.activeVariant : props.variant"
132
133
  :size="props.size"
133
134
  :label="String(item.value)"
134
- :ui="{ label: style.label() }"
135
+ :to="props.to?.(item.value)"
136
+ :ui="{ label: style.label({ class: props.ui?.label }) }"
135
137
  />
136
138
  </slot>
137
139
  </PaginationListItem>
138
140
 
139
- <PaginationEllipsis v-else :key="item.type" :index="index" as-child>
141
+ <PaginationEllipsis
142
+ v-else
143
+ :key="item.type"
144
+ :class="[
145
+ style.item({ class: props.ui?.item }),
146
+ style.ellipsis({ class: props.ui?.ellipsis }),
147
+ ]"
148
+ :index="index"
149
+ :disabled="props.disabled"
150
+ as-child
151
+ >
140
152
  <slot name="ellipsis">
141
153
  <Button
142
154
  :variant="props.variant"
143
155
  :size="props.size"
144
156
  :icon="ellipsisIcon"
145
- :disabled="props.disabled /* TODO: remove after reka-ui update */"
146
- :class="[
147
- style.item({ class: props.ui?.item }),
148
- style.ellipsis({ class: props.ui?.ellipsis }),
149
- ]"
150
157
  />
151
158
  </slot>
152
159
  </PaginationEllipsis>
153
160
  </template>
154
161
 
155
- <PaginationNext v-if="props.showControls || !!slots.next" as-child>
162
+ <PaginationNext v-if="props.showControls || !!slots.next" :class="style.next({ class: props.ui?.next })" as-child>
156
163
  <slot name="next">
157
- <Button :class="style.item({ class: props.ui?.item })" :variant="props.variant" :size="props.size" :icon="nextIcon" />
164
+ <Button :variant="props.variant" :size="props.size" :icon="nextIcon" :to="page < pageCount ? props.to?.(page + 1) : undefined" />
158
165
  </slot>
159
166
  </PaginationNext>
160
- <PaginationLast v-if="props.showControls || !!slots.last" as-child>
167
+ <PaginationLast v-if="props.showControls || !!slots.last" :class="style.last({ class: props.ui?.last })" as-child>
161
168
  <slot name="last">
162
- <Button :class="style.item({ class: props.ui?.item })" :variant="props.variant" :size="props.size" :icon="lastIcon" />
169
+ <Button :variant="props.variant" :size="props.size" :icon="lastIcon" :to="props.to?.(pageCount)" />
163
170
  </slot>
164
171
  </PaginationLast>
165
172
  </PaginationList>
@@ -5,10 +5,10 @@ import type { pinInput } from '../theme'
5
5
  import type { ComponentAttrs } from '../types'
6
6
 
7
7
  export interface PinInputEmits {
8
- (event: 'update:modelValue', value: string[]): void
9
- (event: 'complete', value: string[]): void
10
- (event: 'change', payload: Event): void
11
- (event: 'blur', payload: Event): void
8
+ 'update:modelValue': [value: string[]]
9
+ 'complete': [value: string[]]
10
+ 'change': [payload: Event]
11
+ 'blur': [payload: Event]
12
12
  }
13
13
 
14
14
  type PinInputVariants = VariantProps<typeof pinInput>
@@ -26,6 +26,7 @@ export interface PinInputProps extends ComponentAttrs<typeof pinInput>, Pick<Pin
26
26
  import { reactivePick } from '@vueuse/core'
27
27
  import { PinInputInput, PinInputRoot, useForwardPropsEmits } from 'reka-ui'
28
28
  import { computed, ref } from 'vue'
29
+ import { useFormItem } from '../composables/useFormItem'
29
30
  import { useTheme } from '../composables/useTheme'
30
31
  import { looseToNumber } from '../utils'
31
32
 
@@ -41,44 +42,49 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultValue', 'disa
41
42
 
42
43
  const completed = ref(false)
43
44
 
44
- const { theme, createStyler } = useTheme()
45
- const style = computed(() => {
46
- const styler = createStyler(theme.value.pinInput)
47
- return styler(props)
48
- })
45
+ const { id, name, size, highlight, disabled, ariaAttrs, emitFormInput, emitFormChange, emitFormFocus, emitFormBlur } = useFormItem<PinInputProps>(props)
46
+ const { generateStyle } = useTheme()
47
+ const style = computed(() => generateStyle('pinInput', {
48
+ ...props,
49
+ size: size.value,
50
+ highlight: highlight.value,
51
+ }))
49
52
 
50
53
  function onComplete(value: string[]) {
51
54
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
52
55
  const event = new Event('change', { target: { value } })
53
56
  emit('change', event)
57
+ emitFormChange()
54
58
  }
55
59
 
56
60
  function onBlur(event: FocusEvent) {
57
- if (!event.relatedTarget || completed.value)
61
+ if (!event.relatedTarget || completed.value) {
58
62
  emit('blur', event)
63
+ emitFormBlur()
64
+ }
59
65
  }
60
66
  </script>
61
67
 
62
68
  <template>
63
69
  <PinInputRoot
64
- v-bind="rootProps"
65
- :id="props.id"
66
- :name="props.name"
70
+ v-bind="{ ...rootProps, ...ariaAttrs, id, name }"
67
71
  :class="style.root({ class: [props.class, props.ui?.root] })"
72
+ @update:model-value="emitFormInput"
68
73
  @complete="onComplete"
69
74
  >
70
75
  <span
71
76
  v-for="(ids, index) in looseToNumber(props.length)"
72
77
  :key="ids"
73
78
  :class="style.container({ class: props.ui?.container })"
74
- :aria-disabled="props.disabled ? true : undefined"
79
+ :aria-disabled="disabled ? true : undefined"
75
80
  >
76
81
  <PinInputInput
77
- :index="index"
78
- :class="style.base({ class: props.ui?.base })"
79
82
  v-bind="$attrs"
80
- :disabled="props.disabled"
83
+ :class="style.base({ class: props.ui?.base })"
84
+ :index="index"
85
+ :disabled="disabled"
81
86
  @blur="onBlur"
87
+ @focus="emitFormFocus"
82
88
  />
83
89
  </span>
84
90
  </PinInputRoot>