@livestorm/ui-vue 2.0.5 → 3.0.0
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 +6 -3
- package/dist/UiTimelineItem.vue_vue_type_style_index_0_lang-LpdvGf5X.mjs +6093 -0
- package/dist/components.mjs +105 -96
- package/dist/index.mjs +68 -64
- package/dist/layouts.mjs +15 -15
- package/dist/types/index.d.ts +5 -1
- package/dist/types/src/components/UiActionBar/UiActionBar.vue.d.ts +3 -1
- package/dist/types/src/components/UiActionBar/UiActionBarItem.vue.d.ts +18 -3
- package/dist/types/src/components/UiActionBar/types.d.ts +3 -1
- package/dist/types/src/components/UiButton/UiButton.vue.d.ts +17 -13
- package/dist/types/src/components/UiButtonGroup/UiButtonGroupItem.vue.d.ts +3 -0
- package/dist/types/src/components/UiButtonGroup/types.d.ts +1 -1
- package/dist/types/src/components/UiDropdown/UiDropdownItem.vue.d.ts +6 -4
- package/dist/types/src/components/UiInputEmoji/UiInputEmoji.vue.d.ts +45 -0
- package/dist/types/src/components/UiInputEmoji/index.d.ts +1 -0
- package/dist/types/src/components/UiInputRadio/UiInputRadio.vue.d.ts +67 -0
- package/dist/types/src/components/UiInputRadio/UiInputRadioGroup.vue.d.ts +81 -0
- package/dist/types/src/components/UiInputRadio/index.d.ts +3 -0
- package/dist/types/src/components/UiInputRadio/types.d.ts +19 -0
- package/dist/types/src/components/UiLoader/UiLoader.vue.d.ts +6 -5
- package/dist/types/src/components/UiLogo/UiLogo.vue.d.ts +9 -3
- package/dist/types/src/components/UiPage/UiPage.vue.d.ts +34 -0
- package/dist/types/src/components/UiProgress/UiProgress.vue.d.ts +1 -1
- package/dist/types/src/components/UiSwitch/UiSwitch.vue.d.ts +109 -0
- package/dist/types/src/components/UiSwitch/index.d.ts +2 -0
- package/dist/types/src/components/UiSwitch/types.d.ts +3 -0
- package/dist/types/src/components/UiTabs/UiTabs.vue.d.ts +5 -2
- package/dist/types/src/composables/useContentMode.d.ts +25 -0
- package/dist/types/src/composables/useIconSize.d.ts +11 -0
- package/dist/types/src/constants/sizes.d.ts +1 -1
- package/dist/ui-vue.css +1 -1
- package/index.d.ts +4 -0
- package/index.js +8 -0
- package/package.json +6 -3
- package/src/components/Toast/ToastMessage.vue +6 -6
- package/src/components/UiActionBar/UiActionBar.vue +11 -2
- package/src/components/UiActionBar/UiActionBarItem.vue +56 -32
- package/src/components/UiActionBar/types.ts +3 -1
- package/src/components/UiAvatar/UiAvatar.vue +1 -1
- package/src/components/UiBanner/UiBanner.vue +7 -7
- package/src/components/UiButton/UiButton.vue +9 -9
- package/src/components/UiButtonGroup/UiButtonGroup.vue +2 -0
- package/src/components/UiButtonGroup/UiButtonGroupItem.vue +12 -0
- package/src/components/UiButtonGroup/types.ts +1 -1
- package/src/components/UiCarousel/UiCarousel.vue +2 -2
- package/src/components/UiColorPicker/UiColorPicker.vue +5 -5
- package/src/components/UiDialog/MarketplaceHeaderExample.vue +2 -2
- package/src/components/UiDialog/UiDialogClose.vue +1 -1
- package/src/components/UiDropdown/UiDropdownContent.vue +2 -2
- package/src/components/UiDropdown/UiDropdownItem.vue +8 -1
- package/src/components/UiDropdown/UiDropdownTrigger.vue +6 -7
- package/src/components/UiFieldWrapper/UiFieldWrapper.vue +1 -1
- package/src/components/UiInputCheckbox/UiInputCheckbox.vue +2 -2
- package/src/components/UiInputEmoji/UiInputEmoji.vue +173 -0
- package/src/components/UiInputEmoji/index.ts +1 -0
- package/src/components/UiInputRadio/UiInputRadio.vue +374 -0
- package/src/components/UiInputRadio/UiInputRadioGroup.vue +156 -0
- package/src/components/UiInputRadio/index.ts +3 -0
- package/src/components/UiInputRadio/types.ts +25 -0
- package/src/components/UiLoader/UiLoader.vue +8 -10
- package/src/components/UiLogo/UiLogo.vue +41 -7
- package/src/components/UiMenu/UiMenuContent.vue +1 -1
- package/src/components/UiMenu/UiMenuGroup.vue +1 -1
- package/src/components/UiMenu/UiMenuItem.vue +1 -1
- package/src/components/UiMenu/UiMenuSubContent.vue +1 -1
- package/src/components/UiMenu/UiMenuSubTrigger.vue +2 -2
- package/src/components/UiPage/UiPage.vue +102 -34
- package/src/components/UiPageHeader/UiPageHeader.vue +29 -9
- package/src/components/UiRoomButton/UiRoomButton.vue +4 -4
- package/src/components/UiShell/UiShell.vue +1 -1
- package/src/components/UiSideMenu/UiSideMenu.vue +13 -10
- package/src/components/UiSideMenu/UiSideMenuItemContent.vue +2 -2
- package/src/components/UiStepper/UiStepperIndicator.vue +8 -11
- package/src/components/UiSwitch/UiSwitch.vue +359 -0
- package/src/components/UiSwitch/index.ts +2 -0
- package/src/components/UiSwitch/types.ts +5 -0
- package/src/components/UiTabs/UiTabs.vue +7 -4
- package/src/components/UiTextField/UiTextField.vue +6 -11
- package/src/components/UiTimeline/README.md +6 -6
- package/src/components/UiTimeline/UiTimelineItem.vue +3 -3
- package/src/composables/useContentMode.ts +53 -0
- package/src/composables/useIconSize.ts +28 -0
- package/src/constants/sizes.ts +1 -1
- package/src/global.d.ts +8 -0
- package/dist/UiTimelineItem.vue_vue_type_style_index_0_lang-DJFdCn2R.mjs +0 -5456
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, inject } from 'vue'
|
|
2
|
+
import { computed, inject, useAttrs } from 'vue'
|
|
3
3
|
import {
|
|
4
4
|
TooltipProvider,
|
|
5
5
|
TooltipRoot,
|
|
@@ -9,16 +9,21 @@ import {
|
|
|
9
9
|
TooltipArrow,
|
|
10
10
|
} from 'reka-ui'
|
|
11
11
|
import type { RouteLocationRaw } from 'vue-router'
|
|
12
|
+
import { useIconSize } from '../../composables/useIconSize'
|
|
13
|
+
import type { ActionBarSize } from './types'
|
|
12
14
|
import { ACTION_BAR_CONTEXT_KEY } from './types'
|
|
13
15
|
|
|
14
16
|
interface UiActionBarItemProps {
|
|
15
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Icon class, e.g. `i-ph-trash`. Ignored when the `icon` slot is filled —
|
|
19
|
+
* use the slot for glyphs no icon class covers (emoji, an avatar, a dot).
|
|
20
|
+
*/
|
|
16
21
|
icon?: string
|
|
17
22
|
/** Visible/accessible label. Required so icon-only items stay accessible. */
|
|
18
23
|
label: string
|
|
19
24
|
/**
|
|
20
25
|
* Optional icon class rendered next to the label inside the hover tooltip —
|
|
21
|
-
* e.g. `i-
|
|
26
|
+
* e.g. `i-ph-arrow-square-out` to flag that the action opens in a new tab.
|
|
22
27
|
* Only shown for icon-only items (the tooltip is suppressed when
|
|
23
28
|
* `showLabel` is set).
|
|
24
29
|
*/
|
|
@@ -55,13 +60,20 @@ const props = withDefaults(defineProps<UiActionBarItemProps>(), {
|
|
|
55
60
|
|
|
56
61
|
const emit = defineEmits<{ click: [event: MouseEvent] }>()
|
|
57
62
|
|
|
63
|
+
// The trigger is rendered through `TooltipTrigger as-child`, which merges its
|
|
64
|
+
// own props over the child vnode — so normal attribute fallthrough never
|
|
65
|
+
// reaches the button. Read the consumer's `data-testid` explicitly, otherwise
|
|
66
|
+
// every item in a wrapped bar would share one indistinguishable test id.
|
|
67
|
+
const attrs = useAttrs()
|
|
68
|
+
const testId = computed(() => (attrs['data-testid'] as string | undefined) ?? 'ui-action-bar-item')
|
|
69
|
+
|
|
58
70
|
const ctx = inject(ACTION_BAR_CONTEXT_KEY, null)
|
|
59
71
|
const size = computed(() => ctx?.size.value ?? 'md')
|
|
60
72
|
const orientation = computed(() => ctx?.orientation.value ?? 'horizontal')
|
|
61
73
|
|
|
62
74
|
// Icon-only items get a tooltip; labeled items don't need one.
|
|
63
75
|
const showTooltip = computed(() => !props.showLabel)
|
|
64
|
-
const tooltipSide = computed(() => (orientation.value === 'vertical' ? 'right' : 'bottom'))
|
|
76
|
+
const tooltipSide = computed(() => ctx?.tooltipSide.value ?? (orientation.value === 'vertical' ? 'right' : 'bottom'))
|
|
65
77
|
|
|
66
78
|
const component = computed(() => {
|
|
67
79
|
if (props.disabled) return { is: 'button', props: { type: 'button', disabled: true } }
|
|
@@ -70,22 +82,30 @@ const component = computed(() => {
|
|
|
70
82
|
return { is: 'button', props: { type: 'button' } }
|
|
71
83
|
})
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
85
|
+
// Per-size layout classes. `square` sizes icon-only items; labeled items use
|
|
86
|
+
// `padded` and grow with their text instead of staying square. xs reuses the
|
|
87
|
+
// sm text style — the design system defines no xs one.
|
|
88
|
+
const SIZE_CLASSES: Record<ActionBarSize, { square: string, padded: string, text: string }> = {
|
|
89
|
+
xs: { square: 'size-6', padded: 'h-6 px-2 gap-1', text: 'text-action-button-sm' },
|
|
90
|
+
sm: { square: 'size-8', padded: 'h-8 px-2.5 gap-1', text: 'text-action-button-sm' },
|
|
91
|
+
md: { square: 'size-10', padded: 'h-10 px-3 gap-1.5', text: 'text-action-button-md' },
|
|
92
|
+
}
|
|
79
93
|
|
|
80
|
-
const
|
|
81
|
-
const
|
|
94
|
+
const sizeConfig = computed(() => SIZE_CLASSES[size.value])
|
|
95
|
+
const iconSizeClass = useIconSize(size)
|
|
82
96
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
// Everything bound on the item root: per-size layout and text classes, plus
|
|
98
|
+
// the persistent pressed background when the item is an active toggle (the
|
|
99
|
+
// dedicated neutral "active" token keeps an active-but-not-hovered toggle
|
|
100
|
+
// visually distinct from a merely hovered item).
|
|
101
|
+
const itemClasses = computed(() => [
|
|
102
|
+
props.showLabel ? sizeConfig.value.padded : sizeConfig.value.square,
|
|
103
|
+
sizeConfig.value.text,
|
|
104
|
+
{
|
|
105
|
+
'bg-actions-neutral-active-overlay': props.active === true,
|
|
106
|
+
'cursor-pointer': !props.disabled,
|
|
107
|
+
},
|
|
108
|
+
])
|
|
89
109
|
|
|
90
110
|
function onClick (event: MouseEvent) {
|
|
91
111
|
if (props.disabled) {
|
|
@@ -113,20 +133,22 @@ function onClick (event: MouseEvent) {
|
|
|
113
133
|
v-bind="component.props"
|
|
114
134
|
:aria-label="label"
|
|
115
135
|
:aria-pressed="active"
|
|
116
|
-
data-testid="
|
|
136
|
+
:data-testid="testId"
|
|
117
137
|
class="group inline-flex items-center justify-center rounded-lg shrink-0 outline-none transition-colors
|
|
118
138
|
text-actions-neutral-idle
|
|
119
139
|
hover:bg-actions-neutral-hover-overlay
|
|
120
140
|
focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring
|
|
121
141
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
122
|
-
:class="
|
|
142
|
+
:class="itemClasses"
|
|
123
143
|
@click="onClick"
|
|
124
144
|
>
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
<slot name="icon">
|
|
146
|
+
<span
|
|
147
|
+
v-if="icon"
|
|
148
|
+
:class="[icon, iconSizeClass]"
|
|
149
|
+
class="shrink-0"
|
|
150
|
+
/>
|
|
151
|
+
</slot>
|
|
130
152
|
</component>
|
|
131
153
|
</TooltipTrigger>
|
|
132
154
|
<TooltipPortal>
|
|
@@ -160,20 +182,22 @@ function onClick (event: MouseEvent) {
|
|
|
160
182
|
v-bind="component.props"
|
|
161
183
|
:aria-label="label"
|
|
162
184
|
:aria-pressed="active"
|
|
163
|
-
data-testid="
|
|
185
|
+
:data-testid="testId"
|
|
164
186
|
class="group inline-flex items-center justify-center rounded-lg shrink-0 outline-none transition-colors
|
|
165
187
|
text-actions-neutral-idle
|
|
166
188
|
hover:bg-actions-neutral-hover-overlay
|
|
167
189
|
focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring
|
|
168
190
|
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
169
|
-
:class="
|
|
191
|
+
:class="itemClasses"
|
|
170
192
|
@click="onClick"
|
|
171
193
|
>
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
<slot name="icon">
|
|
195
|
+
<span
|
|
196
|
+
v-if="icon"
|
|
197
|
+
:class="[icon, iconSizeClass]"
|
|
198
|
+
class="shrink-0"
|
|
199
|
+
/>
|
|
200
|
+
</slot>
|
|
177
201
|
<span class="whitespace-nowrap">{{ label }}</span>
|
|
178
202
|
</component>
|
|
179
203
|
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ComputedRef, InjectionKey } from 'vue'
|
|
2
2
|
|
|
3
3
|
export type ActionBarOrientation = 'horizontal' | 'vertical'
|
|
4
|
-
export type ActionBarSize = 'sm' | 'md'
|
|
4
|
+
export type ActionBarSize = 'xs' | 'sm' | 'md'
|
|
5
5
|
export type ActionBarPosition =
|
|
6
6
|
| 'top-left'
|
|
7
7
|
| 'top-right'
|
|
@@ -9,10 +9,12 @@ export type ActionBarPosition =
|
|
|
9
9
|
| 'center-right'
|
|
10
10
|
| 'bottom-left'
|
|
11
11
|
| 'bottom-right'
|
|
12
|
+
export type ActionBarTooltipSide = 'top' | 'right' | 'bottom' | 'left'
|
|
12
13
|
|
|
13
14
|
export interface ActionBarContext {
|
|
14
15
|
orientation: ComputedRef<ActionBarOrientation>
|
|
15
16
|
size: ComputedRef<ActionBarSize>
|
|
17
|
+
tooltipSide: ComputedRef<ActionBarTooltipSide | undefined>
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -115,11 +115,11 @@ onMounted(() => {
|
|
|
115
115
|
// Icon mapping based on color
|
|
116
116
|
const iconClass = computed(() => {
|
|
117
117
|
const colorMap = {
|
|
118
|
-
neutral: 'i-
|
|
119
|
-
primary: 'i-
|
|
120
|
-
success: 'i-
|
|
121
|
-
warning: 'i-
|
|
122
|
-
danger: 'i-
|
|
118
|
+
neutral: 'i-ph-info',
|
|
119
|
+
primary: 'i-ph-info',
|
|
120
|
+
success: 'i-ph-check-circle',
|
|
121
|
+
warning: 'i-ph-warning',
|
|
122
|
+
danger: 'i-ph-warning-circle',
|
|
123
123
|
}
|
|
124
124
|
return colorMap[props.color]
|
|
125
125
|
})
|
|
@@ -295,7 +295,7 @@ function handleAction () {
|
|
|
295
295
|
<!-- Icon -->
|
|
296
296
|
<div
|
|
297
297
|
v-if="showIcon"
|
|
298
|
-
class="shrink-0
|
|
298
|
+
class="shrink-0 size-5"
|
|
299
299
|
:class="[iconClass, titleColorClass]"
|
|
300
300
|
/>
|
|
301
301
|
|
|
@@ -355,7 +355,7 @@ function handleAction () {
|
|
|
355
355
|
:label="translate('general_close')"
|
|
356
356
|
variant="tertiary"
|
|
357
357
|
color="neutral"
|
|
358
|
-
icon="i-
|
|
358
|
+
icon="i-ph-x"
|
|
359
359
|
class="shrink-0"
|
|
360
360
|
data-testid="ui-banner-close"
|
|
361
361
|
@click="handleClose"
|
|
@@ -86,6 +86,7 @@ import { computed, getCurrentInstance, watch } from 'vue'
|
|
|
86
86
|
import { useSlotsAsComponents, RenderSlot } from '../../utils/slot'
|
|
87
87
|
import { SIZES, type Size } from '../../constants'
|
|
88
88
|
import { useButtonStyles, BUTTON_COLORS, type ButtonVariant, type ButtonColor } from '../../composables/useButtonStyles'
|
|
89
|
+
import { useIconSize } from '../../composables/useIconSize'
|
|
89
90
|
import UiLoader from '../UiLoader/UiLoader.vue'
|
|
90
91
|
import UiTooltip from '../UiTooltip/UiTooltip.vue'
|
|
91
92
|
import type { RouteLocationRaw } from 'vue-router'
|
|
@@ -107,7 +108,7 @@ export default {
|
|
|
107
108
|
default: 'cta',
|
|
108
109
|
},
|
|
109
110
|
size: {
|
|
110
|
-
type: String,
|
|
111
|
+
type: String as () => Size,
|
|
111
112
|
default: 'md',
|
|
112
113
|
validator: (value: Size) => SIZES.includes(value),
|
|
113
114
|
},
|
|
@@ -223,11 +224,7 @@ export default {
|
|
|
223
224
|
const isLink = computed(() => !finalDisabled.value && (!!props.href || !!props.to))
|
|
224
225
|
|
|
225
226
|
// Icon size classes
|
|
226
|
-
const iconSizeClasses =
|
|
227
|
-
'size-3': props.size === 'sm',
|
|
228
|
-
'size-4': props.size === 'md',
|
|
229
|
-
'size-5': props.size === 'lg',
|
|
230
|
-
}))
|
|
227
|
+
const iconSizeClasses = useIconSize(() => props.size)
|
|
231
228
|
|
|
232
229
|
// Label value
|
|
233
230
|
const labelFinal = computed(() => {
|
|
@@ -237,8 +234,8 @@ export default {
|
|
|
237
234
|
|
|
238
235
|
// Fallback: generate label from icon for icon-only buttons
|
|
239
236
|
if (isSquare.value && props.icon && typeof props.icon === 'string') {
|
|
240
|
-
// Extract readable name from icon class (e.g. "i-
|
|
241
|
-
const iconName = props.icon.replace(/^i-
|
|
237
|
+
// Extract readable name from icon class (e.g. "i-ph-gear" -> "gear")
|
|
238
|
+
const iconName = props.icon.replace(/^i-[a-z0-9]+-/, '').replace(/-/g, ' ')
|
|
242
239
|
return iconName ? `${iconName} icon` : 'icon'
|
|
243
240
|
}
|
|
244
241
|
|
|
@@ -258,17 +255,20 @@ export default {
|
|
|
258
255
|
// Style: Dynamic button classes
|
|
259
256
|
const buttonClasses = computed(() => [
|
|
260
257
|
{
|
|
258
|
+
'size-6': isSquare.value && props.size === 'xs',
|
|
261
259
|
'size-8': isSquare.value && props.size === 'sm',
|
|
262
260
|
'size-10': isSquare.value && props.size === 'md',
|
|
263
261
|
'size-12': isSquare.value && props.size === 'lg',
|
|
264
262
|
},
|
|
265
263
|
{ 'grid place-items-center': isSquare.value },
|
|
266
264
|
{
|
|
265
|
+
// xs reuses the sm text style — the design system defines no xs one
|
|
266
|
+
'h-6 text-action-button-sm': props.size === 'xs',
|
|
267
267
|
'h-8 text-action-button-sm': props.size === 'sm',
|
|
268
268
|
'h-10 text-action-button-md': props.size === 'md',
|
|
269
269
|
'h-12 text-action-button-lg': props.size === 'lg',
|
|
270
270
|
},
|
|
271
|
-
props.size === 'sm' ? 'px-2.5' : props.size === 'lg' ? 'px-4' : 'px-3',
|
|
271
|
+
props.size === 'xs' ? 'px-2' : props.size === 'sm' ? 'px-2.5' : props.size === 'lg' ? 'px-4' : 'px-3',
|
|
272
272
|
// Links should be inline-flex to prevent full width while maintaining flex centering
|
|
273
273
|
{ 'inline-flex': isLink.value },
|
|
274
274
|
])
|
|
@@ -124,6 +124,7 @@ useResizeObserver(resizeTarget, debouncedUpdate)
|
|
|
124
124
|
|
|
125
125
|
const sizeClasses = computed(() => {
|
|
126
126
|
switch (props.size) {
|
|
127
|
+
case 'xs': return 'h-6 p-0.5 gap-0.5'
|
|
127
128
|
case 'sm': return 'h-8 p-0.5 gap-0.5'
|
|
128
129
|
case 'lg': return 'h-12 p-1 gap-1'
|
|
129
130
|
case 'md':
|
|
@@ -150,6 +151,7 @@ const INDICATOR_BG: Record<ButtonGroupColor, string> = {
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
const INDICATOR_RADIUS: Record<ButtonGroupSize, string> = {
|
|
154
|
+
xs: 'rounded-md',
|
|
153
155
|
sm: 'rounded-md',
|
|
154
156
|
md: 'rounded-lg',
|
|
155
157
|
lg: 'rounded-lg',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ToggleGroupItem } from 'reka-ui'
|
|
3
3
|
import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
4
|
+
import { useIconSize } from '../../composables/useIconSize'
|
|
4
5
|
import type { ButtonGroupColor } from './types'
|
|
5
6
|
import {
|
|
6
7
|
BUTTON_GROUP_COLOR_KEY,
|
|
@@ -12,11 +13,14 @@ interface UiButtonGroupItemProps {
|
|
|
12
13
|
value: string
|
|
13
14
|
disabled?: boolean
|
|
14
15
|
ariaLabel?: string
|
|
16
|
+
/** Icon class (e.g. "i-ph-text-align-left") rendered before the slot, sized per group size. */
|
|
17
|
+
icon?: string
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const props = withDefaults(defineProps<UiButtonGroupItemProps>(), {
|
|
18
21
|
disabled: false,
|
|
19
22
|
ariaLabel: undefined,
|
|
23
|
+
icon: undefined,
|
|
20
24
|
})
|
|
21
25
|
|
|
22
26
|
const size = inject(BUTTON_GROUP_SIZE_KEY, computed(() => 'md' as const))
|
|
@@ -55,6 +59,7 @@ onBeforeUnmount(() => {
|
|
|
55
59
|
// (rail height − rail padding) in horizontal mode, and drives height in vertical mode.
|
|
56
60
|
const sizeClasses = computed(() => {
|
|
57
61
|
switch (size.value) {
|
|
62
|
+
case 'xs': return 'px-2 py-0.5 gap-1 rounded-md text-action-button-sm'
|
|
58
63
|
case 'sm': return 'px-2.5 py-1.5 gap-1 rounded-md text-action-button-sm'
|
|
59
64
|
case 'lg': return 'px-6 py-2.5 gap-2 rounded-lg text-action-button-lg'
|
|
60
65
|
case 'md':
|
|
@@ -62,6 +67,8 @@ const sizeClasses = computed(() => {
|
|
|
62
67
|
}
|
|
63
68
|
})
|
|
64
69
|
|
|
70
|
+
const iconSizeClass = useIconSize(size)
|
|
71
|
+
|
|
65
72
|
// Per-color state classes. The selected fill is drawn by the parent's sliding
|
|
66
73
|
// indicator <div>, so items only set text color per state. Hover bg/text apply
|
|
67
74
|
// only to unselected items (data-[state=off]) so the hover tint never stacks
|
|
@@ -120,6 +127,11 @@ const colorClasses = computed(() => COLOR_CLASSES[color.value])
|
|
|
120
127
|
data-[state=off]:data-[disabled]:hover:bg-transparent"
|
|
121
128
|
:class="[sizeClasses, colorClasses]"
|
|
122
129
|
>
|
|
130
|
+
<span
|
|
131
|
+
v-if="icon"
|
|
132
|
+
:class="[icon, iconSizeClass]"
|
|
133
|
+
class="shrink-0"
|
|
134
|
+
/>
|
|
123
135
|
<slot />
|
|
124
136
|
</ToggleGroupItem>
|
|
125
137
|
</template>
|
|
@@ -176,7 +176,7 @@ defineExpose({
|
|
|
176
176
|
variant="tertiary"
|
|
177
177
|
color="primary"
|
|
178
178
|
size="md"
|
|
179
|
-
icon="i-
|
|
179
|
+
icon="i-ph-caret-left"
|
|
180
180
|
label="Previous slide"
|
|
181
181
|
:disabled="!canScrollPrev"
|
|
182
182
|
@click="scrollPrev"
|
|
@@ -205,7 +205,7 @@ defineExpose({
|
|
|
205
205
|
variant="tertiary"
|
|
206
206
|
color="primary"
|
|
207
207
|
size="md"
|
|
208
|
-
icon="i-
|
|
208
|
+
icon="i-ph-caret-right"
|
|
209
209
|
label="Next slide"
|
|
210
210
|
:disabled="!canScrollNext"
|
|
211
211
|
@click="scrollNext"
|
|
@@ -54,8 +54,8 @@ const presetsLabel = computed<string | null>(() => {
|
|
|
54
54
|
// Preset selector swatch sizes; the selection check icon scales to half the swatch.
|
|
55
55
|
// maxPerRow is how many swatches (at that size + gap) fit the panel's content width.
|
|
56
56
|
const SWATCH_SIZE_CLASSES: Record<UiColorPickerSwatchSize, { swatch: string; swatchPickerList: string; indicatorWrapper: string; indicator: string; maxPerRow: number }> = {
|
|
57
|
-
sm: { swatch: 'size-6', swatchPickerList: 'gap-2', indicatorWrapper: 'ring-2', indicator: '
|
|
58
|
-
lg: { swatch: 'size-12', swatchPickerList: 'gap-4', indicatorWrapper: 'ring-4', indicator: '
|
|
57
|
+
sm: { swatch: 'size-6', swatchPickerList: 'gap-2', indicatorWrapper: 'ring-2', indicator: 'size-3', maxPerRow: 8 },
|
|
58
|
+
lg: { swatch: 'size-12', swatchPickerList: 'gap-4', indicatorWrapper: 'ring-4', indicator: 'size-5', maxPerRow: 4 },
|
|
59
59
|
}
|
|
60
60
|
const swatchSizeClasses = computed(() => SWATCH_SIZE_CLASSES[props.presetConfig?.swatchSize ?? 'lg'])
|
|
61
61
|
|
|
@@ -328,7 +328,7 @@ watch(
|
|
|
328
328
|
<!-- Mode switcher — its own bordered box; hidden when only one mode is available -->
|
|
329
329
|
<div
|
|
330
330
|
v-if="showSwitcher"
|
|
331
|
-
class="h-full w-
|
|
331
|
+
class="h-full w-21 shrink-0"
|
|
332
332
|
>
|
|
333
333
|
<UiDropdown
|
|
334
334
|
v-model="colorMode"
|
|
@@ -456,7 +456,7 @@ watch(
|
|
|
456
456
|
class="absolute inset-0 flex items-center justify-center rounded-full bg-actions-neutral-hover-overlay opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
|
|
457
457
|
>
|
|
458
458
|
<div
|
|
459
|
-
class="i-
|
|
459
|
+
class="i-ph-check-circle-fill text-text-neutral-invert-tertiary"
|
|
460
460
|
:class="swatchSizeClasses.indicator"
|
|
461
461
|
/>
|
|
462
462
|
</span>
|
|
@@ -472,7 +472,7 @@ watch(
|
|
|
472
472
|
:class="swatchSizeClasses.indicatorWrapper"
|
|
473
473
|
>
|
|
474
474
|
<div
|
|
475
|
-
class="i-
|
|
475
|
+
class="i-ph-check-circle-fill text-text-neutral-invert-base"
|
|
476
476
|
:class="swatchSizeClasses.indicator"
|
|
477
477
|
/>
|
|
478
478
|
</span>
|
|
@@ -150,14 +150,14 @@ const goToImage = (index: number) => {
|
|
|
150
150
|
class="absolute left-4 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-surface-neutral-bg-main/80 backdrop-blur-xs flex items-center justify-center hover:bg-surface-neutral-bg-main transition-colors"
|
|
151
151
|
@click="prevImage"
|
|
152
152
|
>
|
|
153
|
-
<span class="i-
|
|
153
|
+
<span class="i-ph-caret-left w-4 h-4 text-text-neutral-base" />
|
|
154
154
|
</button>
|
|
155
155
|
<button
|
|
156
156
|
v-if="images.length > 1"
|
|
157
157
|
class="absolute right-4 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-surface-neutral-bg-main/80 backdrop-blur-xs flex items-center justify-center hover:bg-surface-neutral-bg-main transition-colors"
|
|
158
158
|
@click="nextImage"
|
|
159
159
|
>
|
|
160
|
-
<span class="i-
|
|
160
|
+
<span class="i-ph-caret-right w-4 h-4 text-text-neutral-base" />
|
|
161
161
|
</button>
|
|
162
162
|
|
|
163
163
|
<!-- Dots indicator -->
|
|
@@ -46,7 +46,7 @@ const slots = useSlots()
|
|
|
46
46
|
v-if="showScrollButtons"
|
|
47
47
|
class="flex items-center justify-center h-6 cursor-default text-text-neutral-base"
|
|
48
48
|
>
|
|
49
|
-
<div class="i-
|
|
49
|
+
<div class="i-ph-caret-up size-4" />
|
|
50
50
|
</SelectScrollUpButton>
|
|
51
51
|
|
|
52
52
|
<SelectViewport class="flex flex-col gap-1">
|
|
@@ -57,7 +57,7 @@ const slots = useSlots()
|
|
|
57
57
|
v-if="showScrollButtons"
|
|
58
58
|
class="flex items-center justify-center h-6 cursor-default text-text-neutral-base"
|
|
59
59
|
>
|
|
60
|
-
<div class="i-
|
|
60
|
+
<div class="i-ph-caret-down size-4" />
|
|
61
61
|
</SelectScrollDownButton>
|
|
62
62
|
</div>
|
|
63
63
|
|
|
@@ -48,8 +48,15 @@ const isMultiple = inject(UI_DROPDOWN_MULTIPLE_KEY, undefined)
|
|
|
48
48
|
class="shrink-0 group-data-highlighted:text-actions-neutral-idle group-data-[state=checked]:text-actions-primary-idle"
|
|
49
49
|
/>
|
|
50
50
|
|
|
51
|
+
<div
|
|
52
|
+
v-if="$slots.leading"
|
|
53
|
+
class="shrink-0"
|
|
54
|
+
>
|
|
55
|
+
<slot name="leading" />
|
|
56
|
+
</div>
|
|
57
|
+
|
|
51
58
|
<div class="flex flex-1 flex-col items-start justify-center min-w-0 py-1.5">
|
|
52
|
-
<SelectItemText class="whitespace-nowrap text-content-text-md-
|
|
59
|
+
<SelectItemText class="whitespace-nowrap text-content-text-md-medium text-actions-neutral-idle group-data-highlighted:text-actions-neutral-idle group-data-[state=checked]:text-actions-primary-idle">
|
|
53
60
|
<slot name="title">
|
|
54
61
|
{{ title }}
|
|
55
62
|
</slot>
|
|
@@ -3,6 +3,7 @@ import type { SelectTriggerProps } from 'reka-ui'
|
|
|
3
3
|
import { SelectIcon, SelectTrigger, SelectValue } from 'reka-ui'
|
|
4
4
|
import { computed, useSlots } from 'vue'
|
|
5
5
|
import { useButtonStyles } from '../../composables/useButtonStyles'
|
|
6
|
+
import { useIconSize } from '../../composables/useIconSize'
|
|
6
7
|
|
|
7
8
|
type TSize = 'sm' | 'md' | 'lg' | 'xl'
|
|
8
9
|
type TColor = 'neutral' | 'primary'
|
|
@@ -59,25 +60,21 @@ const sizeClasses = computed(() => {
|
|
|
59
60
|
sm: {
|
|
60
61
|
field: 'h-8 px-2 gap-1',
|
|
61
62
|
text: 'text-action-button-sm tracking-[0.05px]',
|
|
62
|
-
icon: 'size-3',
|
|
63
63
|
padding: 'px-2',
|
|
64
64
|
},
|
|
65
65
|
md: {
|
|
66
66
|
field: 'h-10 px-3 gap-1.5',
|
|
67
67
|
text: 'text-action-button-md leading-[20px] tracking-[-0.04px]',
|
|
68
|
-
icon: 'size-4',
|
|
69
68
|
padding: 'px-3',
|
|
70
69
|
},
|
|
71
70
|
lg: {
|
|
72
71
|
field: 'h-12 px-4 gap-2',
|
|
73
72
|
text: 'text-action-button-lg leading-[20px] tracking-[-0.04px]',
|
|
74
|
-
icon: 'size-4',
|
|
75
73
|
padding: 'px-4',
|
|
76
74
|
},
|
|
77
75
|
xl: {
|
|
78
76
|
field: 'h-16 px-4 gap-2',
|
|
79
77
|
text: 'text-action-button-lg leading-[24px] tracking-[-0.04px]',
|
|
80
|
-
icon: 'size-6',
|
|
81
78
|
padding: 'px-4',
|
|
82
79
|
},
|
|
83
80
|
}
|
|
@@ -85,6 +82,8 @@ const sizeClasses = computed(() => {
|
|
|
85
82
|
return sizes[props.size]
|
|
86
83
|
})
|
|
87
84
|
|
|
85
|
+
const iconSizeClass = useIconSize(() => props.size)
|
|
86
|
+
|
|
88
87
|
const fieldClasses = computed(() => {
|
|
89
88
|
const base = [
|
|
90
89
|
'focus-indicator-none',
|
|
@@ -204,7 +203,7 @@ const iconContainerClasses = computed(() => {
|
|
|
204
203
|
isTertiary.value
|
|
205
204
|
? textClass.value
|
|
206
205
|
: isPrimary.value ? 'text-actions-primary-idle' : 'text-text-neutral-tertiary',
|
|
207
|
-
|
|
206
|
+
iconSizeClass.value,
|
|
208
207
|
].join(' ')
|
|
209
208
|
})
|
|
210
209
|
</script>
|
|
@@ -231,8 +230,8 @@ const iconContainerClasses = computed(() => {
|
|
|
231
230
|
:class="iconContainerClasses"
|
|
232
231
|
>
|
|
233
232
|
<div
|
|
234
|
-
class="i-
|
|
235
|
-
:class="
|
|
233
|
+
class="i-ph-caret-down transition-transform duration-200 ease-in-out group-data-[state=open]:rotate-180"
|
|
234
|
+
:class="iconSizeClass"
|
|
236
235
|
/>
|
|
237
236
|
</SelectIcon>
|
|
238
237
|
</SelectTrigger>
|
|
@@ -54,7 +54,7 @@ defineExpose({ fieldId })
|
|
|
54
54
|
:content="labelInfo"
|
|
55
55
|
>
|
|
56
56
|
<div class="flex items-center justify-center size-4 text-text-neutral-secondary cursor-help">
|
|
57
|
-
<span class="i-
|
|
57
|
+
<span class="i-ph-info size-4" />
|
|
58
58
|
</div>
|
|
59
59
|
</UiTooltip>
|
|
60
60
|
</div>
|
|
@@ -191,7 +191,7 @@ const iconClasses = computed(() => {
|
|
|
191
191
|
|
|
192
192
|
// Icon type and color based on state
|
|
193
193
|
if (props.indeterminate) {
|
|
194
|
-
baseClasses.push('i-
|
|
194
|
+
baseClasses.push('i-ph-minus text-actions-neutral-invert-idle opacity-100')
|
|
195
195
|
} else if (isChecked.value) {
|
|
196
196
|
// Checked: always visible with light color on primary/error background
|
|
197
197
|
baseClasses.push('i-ls-checkbox-check text-actions-neutral-invert-idle opacity-100')
|
|
@@ -403,7 +403,7 @@ defineExpose({
|
|
|
403
403
|
'text-text-primary-base': isChecked && !error,
|
|
404
404
|
}"
|
|
405
405
|
>
|
|
406
|
-
<span class="i-
|
|
406
|
+
<span class="i-ph-info-fill size-4" />
|
|
407
407
|
</div>
|
|
408
408
|
</UiTooltip>
|
|
409
409
|
</div>
|