@livestorm/ui-vue 3.0.0 → 4.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 +3 -3
- package/components-vite.js +2 -2
- package/dist/UiButton-uyW8EHRJ.mjs +602 -0
- package/dist/UiTimelineItem-EiOEi1yF.mjs +7293 -0
- package/dist/UiTooltip-DZcPD6_N.mjs +158 -0
- package/dist/a11y-CaZxRWzo.mjs +6 -0
- package/dist/breakpoints.mjs +4 -9
- package/dist/bus.mjs +5 -5
- package/dist/components.mjs +236 -231
- package/dist/i18n-B8QOMuFD.mjs +46 -0
- package/dist/index.mjs +4 -91
- package/dist/integrations.mjs +3 -14
- package/dist/layouts.mjs +228 -236
- package/dist/responsive.mjs +31 -36
- package/dist/slot.mjs +11 -17
- package/dist/toastDSV2-D3xDZPe5.mjs +105 -0
- package/dist/types/index.d.ts +19 -1
- package/dist/types/src/components/Toast/ToastMessage.vue.d.ts +17 -7
- package/dist/types/src/components/Toast/toastDSV2.d.ts +89 -64
- package/dist/types/src/components/UiActionBar/types.d.ts +2 -0
- package/dist/types/src/components/UiAvatar/UiAvatar.vue.d.ts +4 -2
- package/dist/types/src/components/UiAvatarList/UiAvatarList.vue.d.ts +3 -2
- package/dist/types/src/components/UiBanner/UiBanner.vue.d.ts +13 -1
- package/dist/types/src/components/UiButton/UiButton.vue.d.ts +95 -35
- package/dist/types/src/components/UiButtonGroup/UiButtonGroup.vue.d.ts +8 -2
- package/dist/types/src/components/UiButtonGroup/types.d.ts +4 -0
- package/dist/types/src/components/UiChoice/UiChoice.vue.d.ts +71 -0
- package/dist/types/src/components/UiChoice/index.d.ts +2 -0
- package/dist/types/src/components/UiChoice/types.d.ts +7 -0
- package/dist/types/src/components/UiColorField/UiColorField.vue.d.ts +1 -1
- package/dist/types/src/components/UiColorPicker/UiColorPicker.vue.d.ts +2 -2
- package/dist/types/src/components/UiCombobox/UiCombobox.types.d.ts +47 -0
- package/dist/types/src/components/UiCombobox/UiCombobox.vue.d.ts +40 -0
- package/dist/types/src/components/UiCombobox/index.d.ts +2 -0
- package/dist/types/src/components/UiDataTable/UiDataTable.types.d.ts +46 -0
- package/dist/types/src/components/UiDataTable/UiDataTable.vue.d.ts +136 -0
- package/dist/types/src/components/UiDataTable/index.d.ts +5 -0
- package/dist/types/src/components/UiDrawer/UiDrawer.vue.d.ts +28 -0
- package/dist/types/src/components/UiDrawer/UiDrawerContent.vue.d.ts +18 -0
- package/dist/types/src/components/UiDrawer/UiDrawerTrigger.vue.d.ts +14 -0
- package/dist/types/src/components/UiDropdown/DropdownItemCheckbox.vue.d.ts +3 -0
- package/dist/types/src/components/UiDropdown/UiDropdownContent.vue.d.ts +10 -0
- package/dist/types/src/components/UiDropdown/UiDropdownGroup.vue.d.ts +17 -0
- package/dist/types/src/components/UiDropdown/UiDropdownItem.vue.d.ts +8 -5
- package/dist/types/src/components/UiDropdown/UiDropdownTrigger.vue.d.ts +21 -5
- package/dist/types/src/components/UiDropdown/index.d.ts +1 -0
- package/dist/types/src/components/UiEmojiPickerList/UiEmojiPickerList.vue.d.ts +22 -0
- package/dist/types/src/components/UiEmptyState/UiEmptyState.vue.d.ts +8 -3
- package/dist/types/src/components/UiFieldWrapper/UiFieldWrapper.vue.d.ts +2 -2
- package/dist/types/src/components/UiInputCheckbox/CheckboxGlyph.vue.d.ts +17 -0
- package/dist/types/src/components/UiInputCheckbox/UiInputCheckbox.vue.d.ts +35 -4
- package/dist/types/src/components/UiInputRadio/RadioGlyph.vue.d.ts +19 -0
- package/dist/types/src/components/UiInputRadio/UiInputRadio.vue.d.ts +21 -8
- package/dist/types/src/components/UiInputRadio/UiInputRadioGroup.vue.d.ts +6 -1
- package/dist/types/src/components/UiInputRadio/types.d.ts +1 -1
- package/dist/types/src/components/UiInputSelect/UiInputSelect.vue.d.ts +5 -5
- package/dist/types/src/components/UiInputTextarea/UiInputTextarea.vue.d.ts +5 -5
- package/dist/types/src/components/UiListItem/UiListItem.vue.d.ts +18 -4
- package/dist/types/src/components/UiLoader/UiSpinnerIcon.vue.d.ts +3 -0
- package/dist/types/src/components/UiMenu/UiMenuContent.vue.d.ts +15 -2
- package/dist/types/src/components/UiMenu/UiMenuItem.vue.d.ts +9 -3
- package/dist/types/src/components/UiNestedOptions/UiNestedOptions.vue.d.ts +13 -0
- package/dist/types/src/components/UiPage/UiPage.vue.d.ts +79 -15
- package/dist/types/src/components/UiPagination/UiPagination.vue.d.ts +36 -0
- package/dist/types/src/components/UiPagination/index.d.ts +2 -0
- package/dist/types/src/components/UiPagination/types.d.ts +4 -0
- package/dist/types/src/components/UiPane/UiPane.vue.d.ts +22 -0
- package/dist/types/src/components/UiPane/UiPaneContent.vue.d.ts +75 -0
- package/dist/types/src/components/UiPane/UiPaneTrigger.vue.d.ts +14 -0
- package/dist/types/src/components/UiPane/index.d.ts +5 -0
- package/dist/types/src/components/UiRating/UiRating.vue.d.ts +62 -0
- package/dist/types/src/components/UiRating/types.d.ts +1 -0
- package/dist/types/src/components/UiRoomButton/UiRoomButton.vue.d.ts +5 -3
- package/dist/types/src/components/UiRoomCountdown/UiRoomCountdown.vue.d.ts +32 -0
- package/dist/types/src/components/UiShell/UiShell.vue.d.ts +3 -0
- package/dist/types/src/components/UiSideMenu/UiSideMenu.vue.d.ts +22 -1
- package/dist/types/src/components/UiSideMenu/UiSideMenuItemContent.vue.d.ts +2 -2
- package/dist/types/src/components/UiSwitch/UiSwitch.vue.d.ts +10 -5
- package/dist/types/src/components/UiTable/UiTable.vue.d.ts +13 -4
- package/dist/types/src/components/UiTable/UiTableActionsCell.vue.d.ts +49 -0
- package/dist/types/src/components/UiTable/UiTableActionsHeaderCell.vue.d.ts +3 -0
- package/dist/types/src/components/UiTable/UiTableBody.vue.d.ts +11 -1
- package/dist/types/src/components/UiTable/UiTableCell.vue.d.ts +16 -1
- package/dist/types/src/components/UiTable/UiTableHeaderCell.vue.d.ts +31 -2
- package/dist/types/src/components/UiTable/UiTableRow.vue.d.ts +32 -2
- package/dist/types/src/components/UiTable/context.d.ts +3 -0
- package/dist/types/src/components/UiTable/index.d.ts +4 -0
- package/dist/types/src/components/UiTabs/UiTabs.vue.d.ts +3 -0
- package/dist/types/src/components/UiTag/UiTag.types.d.ts +25 -0
- package/dist/types/src/components/UiTag/UiTag.vue.d.ts +5 -26
- package/dist/types/src/components/UiTag/index.d.ts +2 -1
- package/dist/types/src/components/UiTextField/UiTextField.vue.d.ts +10 -5
- package/dist/types/src/components/UiTooltip/UiTooltipProvider.vue.d.ts +1 -0
- package/dist/types/src/components/UiTooltip/UiTooltipTrigger.vue.d.ts +2 -2
- package/dist/types/src/composables/storedThemingApplies.d.ts +17 -0
- package/dist/types/src/composables/useButtonStyles.d.ts +1 -0
- package/dist/types/src/composables/useChoiceboxSurface.d.ts +11 -0
- package/dist/types/src/composables/useContentMode.d.ts +7 -9
- package/dist/types/src/composables/useControlModes.d.ts +7 -0
- package/dist/types/src/composables/useControlShape.d.ts +24 -0
- package/dist/types/src/composables/useFieldFocusEvents.d.ts +12 -0
- package/dist/types/src/composables/useIconSize.d.ts +2 -0
- package/dist/types/src/composables/usePullGesture.d.ts +42 -0
- package/dist/types/src/composables/useThemeAndModes.d.ts +4 -0
- package/dist/types/src/theming/colorScale.d.ts +165 -0
- package/dist/types/src/theming/legacyColorScale.d.ts +27 -0
- package/dist/types/src/theming/presetColors.d.ts +11 -0
- package/dist/types/src/theming/primitiveTokens.d.ts +2 -0
- package/dist/types/src/utils/effectiveOpacity.d.ts +2 -0
- package/dist/types/src/utils/numbers.d.ts +1 -2
- package/dist/types/src/utils/tokenColor.d.ts +2 -0
- package/dist/ui-vue.css +2 -1
- package/dist/utils.mjs +165 -194
- package/index.d.ts +18 -0
- package/index.js +36 -0
- package/package.json +26 -21
- package/src/components/Toast/LayoutToasts.vue +19 -11
- package/src/components/Toast/ToastMessage.vue +125 -68
- package/src/components/Toast/toastDSV2.ts +238 -67
- package/src/components/UiActionBar/UiActionBar.vue +4 -3
- package/src/components/UiActionBar/UiActionBarItem.vue +9 -6
- package/src/components/UiActionBar/types.ts +13 -0
- package/src/components/UiAvatar/UiAvatar.vue +8 -2
- package/src/components/UiAvatarList/UiAvatarList.vue +51 -20
- package/src/components/UiBadge/UiBadge.vue +1 -1
- package/src/components/UiBanner/UiBanner.vue +71 -21
- package/src/components/UiBreadcrumb/UiBreadcrumb.vue +1 -1
- package/src/components/UiButton/UiButton.vue +92 -25
- package/src/components/UiButtonGroup/UiButtonGroup.vue +21 -17
- package/src/components/UiButtonGroup/UiButtonGroupItem.vue +33 -15
- package/src/components/UiButtonGroup/types.ts +19 -0
- package/src/components/UiCard/UiCard.vue +20 -23
- package/src/components/UiChoice/UiChoice.vue +187 -0
- package/src/components/UiChoice/index.ts +2 -0
- package/src/components/UiChoice/types.ts +11 -0
- package/src/components/UiColorField/UiColorField.vue +2 -2
- package/src/components/UiColorPicker/UiColorPicker.vue +11 -8
- package/src/components/UiCombobox/UiCombobox.types.ts +52 -0
- package/src/components/UiCombobox/UiCombobox.vue +296 -0
- package/src/components/UiCombobox/index.ts +2 -0
- package/src/components/UiDataTable/UiDataTable.types.ts +47 -0
- package/src/components/UiDataTable/UiDataTable.vue +422 -0
- package/src/components/UiDataTable/index.ts +12 -0
- package/src/components/UiDialog/UiDialogContent.vue +22 -7
- package/src/components/UiDrawer/UiDrawer.vue +16 -0
- package/src/components/UiDrawer/UiDrawerContent.vue +42 -0
- package/src/components/UiDrawer/UiDrawerTrigger.vue +17 -0
- package/src/components/UiDropdown/DropdownItemCheckbox.vue +10 -0
- package/src/components/UiDropdown/UiDropdownContent.vue +32 -21
- package/src/components/UiDropdown/UiDropdownGroup.vue +23 -0
- package/src/components/UiDropdown/UiDropdownItem.vue +38 -18
- package/src/components/UiDropdown/UiDropdownSeparator.vue +1 -1
- package/src/components/UiDropdown/UiDropdownTrigger.vue +138 -43
- package/src/components/UiDropdown/index.ts +1 -0
- package/src/components/UiEmojiPickerList/UiEmojiPickerList.vue +122 -0
- package/src/components/UiEmptyState/UiEmptyState.vue +16 -4
- package/src/components/UiFieldWrapper/UiFieldWrapper.vue +9 -3
- package/src/components/UiHorizontalSeparator/UiHorizontalSeparator.vue +2 -2
- package/src/components/UiInputCheckbox/CheckboxGlyph.vue +78 -0
- package/src/components/UiInputCheckbox/UiInputCheckbox.vue +213 -177
- package/src/components/UiInputEmoji/UiInputEmoji.vue +18 -95
- package/src/components/UiInputRadio/RadioGlyph.vue +45 -0
- package/src/components/UiInputRadio/UiInputRadio.vue +177 -169
- package/src/components/UiInputRadio/UiInputRadioGroup.vue +15 -5
- package/src/components/UiInputRadio/types.ts +1 -2
- package/src/components/UiInputSelect/UiInputSelect.vue +9 -7
- package/src/components/UiInputTextarea/UiInputTextarea.vue +20 -35
- package/src/components/UiLabel/UiLabel.vue +1 -1
- package/src/components/UiListItem/UiListItem.vue +55 -9
- package/src/components/UiLoader/UiLoader.vue +4 -20
- package/src/components/UiLoader/UiSpinnerIcon.vue +21 -0
- package/src/components/UiMenu/UiMenuContent.vue +24 -3
- package/src/components/UiMenu/UiMenuGroup.vue +1 -1
- package/src/components/UiMenu/UiMenuItem.vue +14 -3
- package/src/components/UiMenu/UiMenuSubContent.vue +1 -1
- package/src/components/UiMenu/UiMenuSubTrigger.vue +1 -1
- package/src/components/UiNestedOptions/UiNestedOptions.vue +6 -0
- package/src/components/UiPage/UiPage.vue +190 -32
- package/src/components/UiPagination/UiPagination.vue +99 -0
- package/src/components/UiPagination/index.ts +2 -0
- package/src/components/UiPagination/types.ts +28 -0
- package/src/components/UiPane/UiPane.vue +20 -0
- package/src/components/UiPane/UiPaneContent.vue +232 -0
- package/src/components/UiPane/UiPaneTrigger.vue +14 -0
- package/src/components/UiPane/index.ts +12 -0
- package/src/components/UiPopover/UiPopoverContent.vue +21 -1
- package/src/components/UiProgress/UiProgress.vue +2 -2
- package/src/components/UiRating/UiRating.vue +258 -0
- package/src/components/UiRating/types.ts +1 -0
- package/src/components/UiRoomButton/UiRoomButton.vue +10 -41
- package/src/components/UiRoomCountdown/UiRoomCountdown.vue +376 -0
- package/src/components/UiShell/UiShell.vue +9 -3
- package/src/components/UiSideMenu/UiSideMenu.vue +155 -18
- package/src/components/UiSideMenu/UiSideMenuItemContent.vue +20 -16
- package/src/components/UiStepper/UiStepper.vue +3 -16
- package/src/components/UiStepper/UiStepperDescription.vue +1 -13
- package/src/components/UiStepper/UiStepperIndicator.vue +11 -48
- package/src/components/UiStepper/UiStepperItem.vue +1 -8
- package/src/components/UiStepper/UiStepperSeparator.vue +5 -16
- package/src/components/UiStepper/UiStepperTitle.vue +1 -10
- package/src/components/UiSwitch/UiSwitch.vue +44 -28
- package/src/components/UiTable/UiTable.vue +98 -8
- package/src/components/UiTable/UiTableActionsCell.vue +135 -0
- package/src/components/UiTable/UiTableActionsHeaderCell.vue +19 -0
- package/src/components/UiTable/UiTableBody.vue +16 -1
- package/src/components/UiTable/UiTableCell.vue +33 -16
- package/src/components/UiTable/UiTableHeader.vue +6 -5
- package/src/components/UiTable/UiTableHeaderCell.vue +61 -9
- package/src/components/UiTable/UiTableRow.vue +46 -10
- package/src/components/UiTable/context.ts +5 -0
- package/src/components/UiTable/index.ts +4 -0
- package/src/components/UiTabs/UiTabs.vue +56 -61
- package/src/components/UiTag/UiTag.types.ts +28 -0
- package/src/components/UiTag/UiTag.vue +24 -42
- package/src/components/UiTag/index.ts +2 -0
- package/src/components/UiTextField/UiTextField.vue +21 -36
- package/src/components/UiTooltip/UiTooltipContent.vue +1 -1
- package/src/components/UiTooltip/UiTooltipProvider.vue +3 -0
- package/src/components/UiTooltip/UiTooltipTrigger.vue +16 -1
- package/src/composables/storedThemingApplies.ts +19 -0
- package/src/composables/useButtonStyles.ts +9 -13
- package/src/composables/useChoiceboxSurface.ts +36 -0
- package/src/composables/useContentMode.ts +29 -16
- package/src/composables/useControlModes.ts +11 -0
- package/src/composables/useControlShape.ts +34 -0
- package/src/composables/useFieldFocusEvents.ts +30 -0
- package/src/composables/useIconSize.ts +15 -0
- package/src/composables/usePullGesture.ts +142 -0
- package/src/composables/useThemeAndModes.ts +28 -1
- package/src/css/body.css +16 -1
- package/src/css/fonts.css +39 -5
- package/src/global.d.ts +36 -0
- package/src/theming/colorScale.ts +173 -0
- package/src/theming/legacyColorScale.ts +58 -0
- package/src/theming/presetColors.ts +33 -0
- package/src/theming/primitiveTokens.ts +71 -0
- package/src/utils/effectiveOpacity.ts +8 -0
- package/src/utils/i18n.ts +6 -0
- package/src/utils/numbers.ts +1 -1
- package/src/utils/responsive.d.ts +14 -0
- package/src/utils/tokenColor.ts +9 -0
- package/dist/UiTimelineItem.vue_vue_type_style_index_0_lang-LpdvGf5X.mjs +0 -6093
- package/dist/a11y-BhF_nl7L.mjs +0 -159
- package/dist/i18n-Cktw9hIu.mjs +0 -49
- package/dist/toastDSV2-wMh-UMzW.mjs +0 -74
- package/dist/types/src/components/UiAvatar/index.d.ts +0 -1
- package/dist/types/src/components/UiAvatarList/index.d.ts +0 -1
- package/dist/types/src/components/UiBadge/index.d.ts +0 -1
- package/dist/types/src/components/UiBanner/index.d.ts +0 -2
- package/dist/types/src/components/UiButtonGroup/index.d.ts +0 -3
- package/dist/types/src/components/UiCarousel/index.d.ts +0 -2
- package/dist/types/src/components/UiColorField/index.d.ts +0 -2
- package/dist/types/src/components/UiDialog/MarketplaceHeaderExample.vue.d.ts +0 -28
- package/dist/types/src/components/UiDialog/index.d.ts +0 -7
- package/dist/types/src/components/UiFieldWrapper/index.d.ts +0 -1
- package/dist/types/src/components/UiInputCheckbox/index.d.ts +0 -2
- package/dist/types/src/components/UiInputEmoji/index.d.ts +0 -1
- package/dist/types/src/components/UiInputRadio/index.d.ts +0 -3
- package/dist/types/src/components/UiInputSelect/index.d.ts +0 -2
- package/dist/types/src/components/UiInputTextarea/index.d.ts +0 -2
- package/dist/types/src/components/UiMenu/index.d.ts +0 -10
- package/dist/types/src/components/UiProgress/index.d.ts +0 -2
- package/dist/types/src/components/UiSwitch/index.d.ts +0 -2
- package/dist/types/src/components/UiTabs/index.d.ts +0 -2
- package/dist/types/src/components/UiTimeline/index.d.ts +0 -2
- package/dist/types/src/components/UiTooltip/index.d.ts +0 -4
- package/dist/types/src/mixins/DynamicAction.d.ts +0 -30
- package/dist/useThemeAndModes-BzYwSOEv.mjs +0 -108
- package/src/components/UiAvatar/index.js +0 -1
- package/src/components/UiAvatarList/index.js +0 -1
- package/src/components/UiBadge/index.ts +0 -2
- package/src/components/UiBanner/index.ts +0 -2
- package/src/components/UiButtonGroup/index.ts +0 -3
- package/src/components/UiCarousel/index.ts +0 -2
- package/src/components/UiColorField/index.ts +0 -2
- package/src/components/UiDialog/MarketplaceHeaderExample.vue +0 -217
- package/src/components/UiDialog/index.ts +0 -18
- package/src/components/UiFieldWrapper/index.ts +0 -1
- package/src/components/UiInputCheckbox/index.ts +0 -2
- package/src/components/UiInputEmoji/index.ts +0 -1
- package/src/components/UiInputRadio/index.ts +0 -3
- package/src/components/UiInputSelect/index.ts +0 -2
- package/src/components/UiInputTextarea/index.ts +0 -2
- package/src/components/UiMenu/index.ts +0 -10
- package/src/components/UiProgress/index.ts +0 -2
- package/src/components/UiSwitch/index.ts +0 -2
- package/src/components/UiTabs/index.ts +0 -5
- package/src/components/UiTag/index.js +0 -1
- package/src/components/UiTimeline/index.ts +0 -2
- package/src/components/UiTooltip/index.js +0 -4
- package/src/mixins/DynamicAction.js +0 -61
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { SelectGroup, SelectLabel } from 'reka-ui'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
/** Heading read before the options of the group; leave it out for an untitled group. */
|
|
6
|
+
label?: string
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<SelectGroup
|
|
12
|
+
v-bind="$attrs"
|
|
13
|
+
class="flex flex-col gap-1"
|
|
14
|
+
>
|
|
15
|
+
<SelectLabel
|
|
16
|
+
v-if="label"
|
|
17
|
+
class="px-4 pt-2 pb-1 text-content-legends-bold-md text-text-neutral-tertiary"
|
|
18
|
+
>
|
|
19
|
+
{{ label }}
|
|
20
|
+
</SelectLabel>
|
|
21
|
+
<slot />
|
|
22
|
+
</SelectGroup>
|
|
23
|
+
</template>
|
|
@@ -1,25 +1,49 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { SelectItemProps } from 'reka-ui'
|
|
2
|
+
import type { SelectItemEmits, SelectItemProps } from 'reka-ui'
|
|
3
3
|
import { SelectItem, SelectItemText } from 'reka-ui'
|
|
4
|
-
import { inject } from 'vue'
|
|
4
|
+
import { computed, inject } from 'vue'
|
|
5
5
|
import { UI_DROPDOWN_MULTIPLE_KEY } from './context'
|
|
6
|
+
import DropdownItemCheckbox from './DropdownItemCheckbox.vue'
|
|
6
7
|
|
|
7
8
|
interface Props extends SelectItemProps {
|
|
8
9
|
title?: string
|
|
9
10
|
description?: string
|
|
10
11
|
icon?: string
|
|
12
|
+
/** `danger` marks an option that takes something away, such as removing access. */
|
|
13
|
+
variant?: 'default' | 'danger'
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
withDefaults(defineProps<Props>(), {
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
14
17
|
title: undefined,
|
|
15
18
|
description: undefined,
|
|
16
19
|
icon: undefined,
|
|
20
|
+
variant: 'default',
|
|
17
21
|
})
|
|
18
22
|
|
|
23
|
+
const isDanger = computed(() => props.variant === 'danger')
|
|
24
|
+
|
|
25
|
+
const stateBackgroundClass = computed(() => isDanger.value
|
|
26
|
+
? 'data-highlighted:bg-actions-danger-hover-overlay data-[state=checked]:bg-actions-danger-pressed-overlay'
|
|
27
|
+
: 'data-highlighted:bg-actions-neutral-hover-overlay data-[state=checked]:bg-actions-primary-active-overlay')
|
|
28
|
+
|
|
29
|
+
const titleColorClass = computed(() => isDanger.value
|
|
30
|
+
? 'text-text-danger-body'
|
|
31
|
+
: 'text-actions-neutral-idle group-data-highlighted/item:text-actions-neutral-idle group-data-[state=checked]/item:text-actions-primary-idle')
|
|
32
|
+
|
|
33
|
+
const descriptionColorClass = computed(() => isDanger.value
|
|
34
|
+
? 'text-text-neutral-secondary'
|
|
35
|
+
: 'text-text-neutral-secondary group-data-highlighted/item:text-text-neutral-secondary group-data-[state=checked]/item:text-text-primary-secondary')
|
|
36
|
+
|
|
19
37
|
// `UiDropdown` provides this when `multiple` is set so items render a
|
|
20
38
|
// leading checkbox automatically. Falls back to single-select behavior
|
|
21
39
|
// when the item is used outside a `UiDropdown` (defensive default).
|
|
22
40
|
const isMultiple = inject(UI_DROPDOWN_MULTIPLE_KEY, undefined)
|
|
41
|
+
|
|
42
|
+
// Reka picks on Enter keydown without cancelling it, so its keypress would activate whatever the pick focused.
|
|
43
|
+
function consumePickingKey (event: SelectItemEmits['select'][0]) {
|
|
44
|
+
const { originalEvent } = event.detail
|
|
45
|
+
if (originalEvent instanceof KeyboardEvent) originalEvent.preventDefault()
|
|
46
|
+
}
|
|
23
47
|
</script>
|
|
24
48
|
|
|
25
49
|
<template>
|
|
@@ -27,25 +51,17 @@ const isMultiple = inject(UI_DROPDOWN_MULTIPLE_KEY, undefined)
|
|
|
27
51
|
v-bind="$attrs"
|
|
28
52
|
:value="value"
|
|
29
53
|
:disabled="disabled"
|
|
30
|
-
class="focus-indicator-none group flex px-3 py-1 rounded-
|
|
54
|
+
class="focus-indicator-none group/item flex px-3 py-1 rounded-row cursor-pointer outline-hidden transition-all duration-150 ease-in-out data-disabled:opacity-50 data-disabled:cursor-not-allowed data-disabled:pointer-events-none"
|
|
55
|
+
:class="stateBackgroundClass"
|
|
56
|
+
@select="consumePickingKey"
|
|
31
57
|
>
|
|
32
58
|
<div class="flex flex-1 gap-2 items-center overflow-hidden">
|
|
33
|
-
|
|
34
|
-
size, border radius, primary fill, and `i-ls-checkbox-check`
|
|
35
|
-
icon) without dragging in the input's runtime/a11y plumbing.
|
|
36
|
-
Driven entirely by the parent `SelectItem`'s `data-[state=checked]`
|
|
37
|
-
attribute — no per-item state. -->
|
|
38
|
-
<div
|
|
39
|
-
v-if="isMultiple"
|
|
40
|
-
class="shrink-0 size-4 rounded-sm border border-borders-neutral-strong bg-surface-neutral-bg-main grid place-content-center transition-colors group-data-[state=checked]:bg-actions-primary-idle group-data-[state=checked]:border-borders-primary-default"
|
|
41
|
-
>
|
|
42
|
-
<div class="i-ls-checkbox-check size-3 text-actions-neutral-invert-idle opacity-0 group-data-[state=checked]:opacity-100" />
|
|
43
|
-
</div>
|
|
59
|
+
<DropdownItemCheckbox v-if="isMultiple" />
|
|
44
60
|
|
|
45
61
|
<div
|
|
46
62
|
v-if="icon"
|
|
47
63
|
:class="[icon, 'size-4']"
|
|
48
|
-
class="shrink-0 group-data-highlighted:text-actions-neutral-idle group-data-[state=checked]:text-actions-primary-idle"
|
|
64
|
+
class="shrink-0 group-data-highlighted/item:text-actions-neutral-idle group-data-[state=checked]/item:text-actions-primary-idle"
|
|
49
65
|
/>
|
|
50
66
|
|
|
51
67
|
<div
|
|
@@ -56,14 +72,18 @@ const isMultiple = inject(UI_DROPDOWN_MULTIPLE_KEY, undefined)
|
|
|
56
72
|
</div>
|
|
57
73
|
|
|
58
74
|
<div class="flex flex-1 flex-col items-start justify-center min-w-0 py-1.5">
|
|
59
|
-
<SelectItemText
|
|
75
|
+
<SelectItemText
|
|
76
|
+
class="max-w-full truncate text-content-text-md-medium"
|
|
77
|
+
:class="titleColorClass"
|
|
78
|
+
>
|
|
60
79
|
<slot name="title">
|
|
61
80
|
{{ title }}
|
|
62
81
|
</slot>
|
|
63
82
|
</SelectItemText>
|
|
64
83
|
<div
|
|
65
84
|
v-if="description || $slots.description"
|
|
66
|
-
class="text-content-legends-medium-md
|
|
85
|
+
class="max-w-full truncate text-content-legends-medium-md"
|
|
86
|
+
:class="descriptionColorClass"
|
|
67
87
|
>
|
|
68
88
|
<slot name="description">
|
|
69
89
|
{{ description }}
|
|
@@ -3,7 +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 {
|
|
6
|
+
import { useControlShape } from '../../composables/useControlShape'
|
|
7
7
|
|
|
8
8
|
type TSize = 'sm' | 'md' | 'lg' | 'xl'
|
|
9
9
|
type TColor = 'neutral' | 'primary'
|
|
@@ -24,12 +24,21 @@ interface Props extends SelectTriggerProps {
|
|
|
24
24
|
color?: TColor
|
|
25
25
|
/**
|
|
26
26
|
* Visual variant, named to match `UiButton`. `secondary` (default) is the
|
|
27
|
-
* bordered field look. `tertiary` drops the border + background
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* bordered field look. `tertiary` drops the border + background: the trigger
|
|
28
|
+
* shows only its value, coloured like a `UiButton` tertiary and set in the
|
|
29
|
+
* regular body text style, with a hover overlay. It suits a value picked
|
|
30
|
+
* inside a row, such as a role beside a person.
|
|
30
31
|
*/
|
|
31
32
|
variant?: TVariant
|
|
32
33
|
error?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* Shows the value as static text in the box of an editable trigger: same size, padding and
|
|
36
|
+
* border width, no fill, and an empty slot where the chevron would be, so its text ends where an
|
|
37
|
+
* editable trigger's value ends. It is not focusable and opens nothing; it renders as a `group`, so pass
|
|
38
|
+
* `aria-labelledby` (for example the row's name) to label it like the editable trigger.
|
|
39
|
+
* The value reads in the secondary text colour. Render it from the `value` slot.
|
|
40
|
+
*/
|
|
41
|
+
readonly?: boolean
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -40,59 +49,112 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
40
49
|
color: 'neutral',
|
|
41
50
|
variant: 'secondary',
|
|
42
51
|
error: false,
|
|
52
|
+
readonly: false,
|
|
43
53
|
})
|
|
44
54
|
|
|
45
55
|
const slots = useSlots()
|
|
46
56
|
|
|
47
|
-
// Shared button styling — both variants pull their background /
|
|
57
|
+
// Shared button styling — both variants pull their background / text /
|
|
48
58
|
// overlay tokens from the same source `UiButton` uses, so the trigger stays in
|
|
49
59
|
// lockstep with the button palette. `error` maps onto the button `error` color;
|
|
50
60
|
// otherwise the trigger's `primary`/`neutral` colors line up 1:1.
|
|
51
61
|
const buttonColor = computed(() => (props.error ? 'error' as const : props.color))
|
|
52
|
-
const { textClass, bgClass,
|
|
62
|
+
const { textClass, bgClass, beforeHoverOverlayClass } = useButtonStyles({
|
|
53
63
|
variant: () => props.variant,
|
|
54
64
|
color: buttonColor,
|
|
55
65
|
disabled: () => props.disabled,
|
|
56
66
|
})
|
|
57
67
|
|
|
68
|
+
const FIELD_BORDER: Record<'neutral' | 'primary' | 'error', string> = {
|
|
69
|
+
neutral: 'border-control-field-border',
|
|
70
|
+
primary: 'border-borders-primary-strong',
|
|
71
|
+
error: 'border-control-error-border',
|
|
72
|
+
}
|
|
73
|
+
const borderClass = computed(() => FIELD_BORDER[buttonColor.value])
|
|
74
|
+
|
|
75
|
+
const fieldBgClass = computed(() =>
|
|
76
|
+
buttonColor.value === 'neutral' ? 'bg-control-field-bg' : bgClass.value,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// With a `leading` slot the start padding shrinks so the leading box (24px on
|
|
80
|
+
// sm and md, 32px on lg and xl, enforced on its wrapper) sits as far from the
|
|
81
|
+
// start edge as from the top and bottom: (field height - box) / 2 - 1px border.
|
|
58
82
|
const sizeClasses = computed(() => {
|
|
59
83
|
const sizes = {
|
|
60
84
|
sm: {
|
|
61
|
-
field: '
|
|
62
|
-
text: 'text-action-button-sm tracking-[0.05px]',
|
|
85
|
+
field: 'gap-1',
|
|
63
86
|
padding: 'px-2',
|
|
87
|
+
leadingPadding: 'pl-[3px] pr-2',
|
|
88
|
+
leadingBox: 'size-6',
|
|
89
|
+
text: 'text-action-button-sm tracking-[0.05px]',
|
|
90
|
+
tertiaryText: 'text-content-legends-medium-md',
|
|
64
91
|
},
|
|
65
92
|
md: {
|
|
66
|
-
field: '
|
|
67
|
-
text: 'text-action-button-md leading-[20px] tracking-[-0.04px]',
|
|
93
|
+
field: 'gap-1.5',
|
|
68
94
|
padding: 'px-3',
|
|
95
|
+
leadingPadding: 'pl-[5px] pr-3',
|
|
96
|
+
leadingBox: 'size-6',
|
|
97
|
+
text: 'text-action-button-md leading-[20px] tracking-[-0.04px]',
|
|
98
|
+
tertiaryText: 'text-content-text-md-regular',
|
|
69
99
|
},
|
|
70
100
|
lg: {
|
|
71
|
-
field: '
|
|
72
|
-
text: 'text-action-button-lg leading-[20px] tracking-[-0.04px]',
|
|
101
|
+
field: 'gap-2',
|
|
73
102
|
padding: 'px-4',
|
|
103
|
+
leadingPadding: 'pl-[7px] pr-4',
|
|
104
|
+
leadingBox: 'size-8',
|
|
105
|
+
text: 'text-action-button-lg leading-[20px] tracking-[-0.04px]',
|
|
106
|
+
tertiaryText: 'text-content-text-lg-regular',
|
|
74
107
|
},
|
|
75
108
|
xl: {
|
|
76
|
-
field: '
|
|
77
|
-
text: 'text-action-button-lg leading-[24px] tracking-[-0.04px]',
|
|
109
|
+
field: 'gap-2',
|
|
78
110
|
padding: 'px-4',
|
|
111
|
+
leadingPadding: 'pl-[15px] pr-4',
|
|
112
|
+
leadingBox: 'size-8',
|
|
113
|
+
text: 'text-action-button-lg leading-[24px] tracking-[-0.04px]',
|
|
114
|
+
tertiaryText: 'text-content-text-lg-regular',
|
|
79
115
|
},
|
|
80
116
|
}
|
|
81
117
|
|
|
82
118
|
return sizes[props.size]
|
|
83
119
|
})
|
|
84
120
|
|
|
85
|
-
const
|
|
121
|
+
const shape = useControlShape('field', () => props.size)
|
|
122
|
+
|
|
123
|
+
const boxClasses = computed(() => [
|
|
124
|
+
'flex',
|
|
125
|
+
'items-center',
|
|
126
|
+
'justify-between',
|
|
127
|
+
'w-full',
|
|
128
|
+
shape.value.radius,
|
|
129
|
+
shape.value.height,
|
|
130
|
+
'border',
|
|
131
|
+
'border-solid',
|
|
132
|
+
'border-transparent',
|
|
133
|
+
'bg-transparent',
|
|
134
|
+
sizeClasses.value.field,
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
const readonlyTextClasses = computed(() => [
|
|
138
|
+
'flex-1',
|
|
139
|
+
'overflow-hidden',
|
|
140
|
+
'text-ellipsis',
|
|
141
|
+
'whitespace-nowrap',
|
|
142
|
+
'min-w-0',
|
|
143
|
+
'text-left',
|
|
144
|
+
'text-text-neutral-secondary',
|
|
145
|
+
props.variant === 'tertiary' ? sizeClasses.value.tertiaryText : sizeClasses.value.text,
|
|
146
|
+
])
|
|
86
147
|
|
|
87
148
|
const fieldClasses = computed(() => {
|
|
88
149
|
const base = [
|
|
89
150
|
'focus-indicator-none',
|
|
90
|
-
'group',
|
|
151
|
+
'group/trigger',
|
|
91
152
|
'flex',
|
|
92
153
|
'items-center',
|
|
93
154
|
'justify-between',
|
|
94
155
|
'w-full',
|
|
95
|
-
|
|
156
|
+
shape.value.radius,
|
|
157
|
+
shape.value.height,
|
|
96
158
|
'border',
|
|
97
159
|
'border-solid',
|
|
98
160
|
'transition-all',
|
|
@@ -102,12 +164,11 @@ const fieldClasses = computed(() => {
|
|
|
102
164
|
'cursor-pointer',
|
|
103
165
|
'font-medium',
|
|
104
166
|
'focus:outline-hidden',
|
|
105
|
-
'focus-visible:ring
|
|
106
|
-
'focus-visible:ring-inset',
|
|
167
|
+
'focus-visible:focus-ring',
|
|
107
168
|
// Hover overlay via ::before
|
|
108
169
|
"before:content-['']",
|
|
109
170
|
'before:absolute',
|
|
110
|
-
'before
|
|
171
|
+
'before:-inset-px',
|
|
111
172
|
'before:rounded-[inherit]',
|
|
112
173
|
'before:opacity-0',
|
|
113
174
|
'before:transition-opacity',
|
|
@@ -128,33 +189,29 @@ const fieldClasses = computed(() => {
|
|
|
128
189
|
state.push('opacity-50', '!cursor-not-allowed')
|
|
129
190
|
}
|
|
130
191
|
|
|
131
|
-
// Background /
|
|
132
|
-
//
|
|
192
|
+
// Background / hover overlay come from `useButtonStyles`, keyed by
|
|
193
|
+
// `variant` × `buttonColor` — the exact tokens `UiButton` uses. Tertiary
|
|
133
194
|
// resolves to no border/bg (transparent), secondary to the bordered field.
|
|
134
195
|
if (props.variant === 'tertiary') {
|
|
135
196
|
state.push('border-transparent', 'bg-transparent')
|
|
136
197
|
}
|
|
137
198
|
else {
|
|
138
|
-
state.push(
|
|
199
|
+
state.push(fieldBgClass.value, borderClass.value)
|
|
139
200
|
}
|
|
140
201
|
if (beforeHoverOverlayClass.value) state.push(beforeHoverOverlayClass.value)
|
|
141
202
|
|
|
142
|
-
//
|
|
143
|
-
// open state). Error keeps its danger-colored focus ring; everything else
|
|
144
|
-
// uses the standard primary focus highlight.
|
|
203
|
+
// The open-state border is trigger-specific: a button has no open state.
|
|
145
204
|
if (props.error) {
|
|
146
|
-
state.push(
|
|
147
|
-
'focus-visible:border-borders-danger-strong',
|
|
148
|
-
'focus-visible:ring-borders-danger-strong',
|
|
149
|
-
'data-[state=open]:border-borders-danger-strong',
|
|
150
|
-
)
|
|
205
|
+
state.push('data-[state=open]:border-control-error-border')
|
|
151
206
|
}
|
|
152
207
|
else {
|
|
153
|
-
state.push('focus-visible:ring-focus-ring')
|
|
154
208
|
if (props.variant !== 'tertiary') {
|
|
209
|
+
state.push('data-[state=open]:border-control-field-border-focus')
|
|
210
|
+
}
|
|
211
|
+
if (props.variant !== 'tertiary' && buttonColor.value === 'neutral') {
|
|
155
212
|
state.push(
|
|
156
|
-
'focus-visible:
|
|
157
|
-
'data-[state=open]:
|
|
213
|
+
'focus-visible:bg-control-field-bg-focus',
|
|
214
|
+
'data-[state=open]:bg-control-field-bg-focus',
|
|
158
215
|
)
|
|
159
216
|
}
|
|
160
217
|
}
|
|
@@ -174,10 +231,12 @@ const textClasses = computed(() => {
|
|
|
174
231
|
: isPrimary.value ? 'text-actions-primary-idle' : 'text-text-neutral-base'
|
|
175
232
|
// Placeholder color: primary (incl. tertiary-primary) keeps the primary
|
|
176
233
|
// token so an empty filter chip still reads as actionable; everything else
|
|
177
|
-
// uses the muted placeholder token.
|
|
178
|
-
const placeholderClass =
|
|
179
|
-
? '
|
|
180
|
-
: '
|
|
234
|
+
// uses the muted placeholder token. A `value` slot renders a value, never the placeholder.
|
|
235
|
+
const placeholderClass = slots.value
|
|
236
|
+
? ''
|
|
237
|
+
: (isPrimary.value || (isTertiary.value && props.color === 'primary'))
|
|
238
|
+
? 'group-data-placeholder/trigger:text-actions-primary-idle'
|
|
239
|
+
: 'group-data-placeholder/trigger:text-control-placeholder'
|
|
181
240
|
return [
|
|
182
241
|
'flex-1',
|
|
183
242
|
'overflow-hidden',
|
|
@@ -187,7 +246,7 @@ const textClasses = computed(() => {
|
|
|
187
246
|
'text-left',
|
|
188
247
|
colorClass,
|
|
189
248
|
placeholderClass,
|
|
190
|
-
sizeClasses.value.text,
|
|
249
|
+
isTertiary.value ? sizeClasses.value.tertiaryText : sizeClasses.value.text,
|
|
191
250
|
].join(' ')
|
|
192
251
|
})
|
|
193
252
|
|
|
@@ -203,17 +262,53 @@ const iconContainerClasses = computed(() => {
|
|
|
203
262
|
isTertiary.value
|
|
204
263
|
? textClass.value
|
|
205
264
|
: isPrimary.value ? 'text-actions-primary-idle' : 'text-text-neutral-tertiary',
|
|
206
|
-
|
|
265
|
+
shape.value.chevron,
|
|
207
266
|
].join(' ')
|
|
208
267
|
})
|
|
209
268
|
</script>
|
|
210
269
|
|
|
211
270
|
<template>
|
|
271
|
+
<span
|
|
272
|
+
v-if="readonly"
|
|
273
|
+
data-testid="ui-dropdown-trigger-readonly"
|
|
274
|
+
role="group"
|
|
275
|
+
v-bind="$attrs"
|
|
276
|
+
:class="[boxClasses, slots.leading ? sizeClasses.leadingPadding : sizeClasses.padding]"
|
|
277
|
+
>
|
|
278
|
+
<span
|
|
279
|
+
v-if="slots.leading"
|
|
280
|
+
data-testid="ui-dropdown-trigger-leading"
|
|
281
|
+
class="flex items-center justify-center shrink-0"
|
|
282
|
+
:class="sizeClasses.leadingBox"
|
|
283
|
+
>
|
|
284
|
+
<slot name="leading" />
|
|
285
|
+
</span>
|
|
286
|
+
<div :class="readonlyTextClasses">
|
|
287
|
+
<slot name="value">
|
|
288
|
+
{{ placeholder }}
|
|
289
|
+
</slot>
|
|
290
|
+
</div>
|
|
291
|
+
<span
|
|
292
|
+
v-if="showIcon"
|
|
293
|
+
class="shrink-0"
|
|
294
|
+
:class="shape.chevron"
|
|
295
|
+
aria-hidden="true"
|
|
296
|
+
/>
|
|
297
|
+
</span>
|
|
212
298
|
<SelectTrigger
|
|
299
|
+
v-else
|
|
213
300
|
v-bind="$attrs"
|
|
214
|
-
:class="fieldClasses"
|
|
301
|
+
:class="[fieldClasses, slots.leading ? sizeClasses.leadingPadding : sizeClasses.padding]"
|
|
215
302
|
:disabled="disabled"
|
|
216
303
|
>
|
|
304
|
+
<span
|
|
305
|
+
v-if="slots.leading"
|
|
306
|
+
data-testid="ui-dropdown-trigger-leading"
|
|
307
|
+
class="flex items-center justify-center shrink-0"
|
|
308
|
+
:class="sizeClasses.leadingBox"
|
|
309
|
+
>
|
|
310
|
+
<slot name="leading" />
|
|
311
|
+
</span>
|
|
217
312
|
<div :class="textClasses">
|
|
218
313
|
<SelectValue
|
|
219
314
|
v-if="!slots.value"
|
|
@@ -230,8 +325,8 @@ const iconContainerClasses = computed(() => {
|
|
|
230
325
|
:class="iconContainerClasses"
|
|
231
326
|
>
|
|
232
327
|
<div
|
|
233
|
-
class="i-ph-caret-down transition-transform duration-200 ease-in-out group-data-[state=open]:rotate-180"
|
|
234
|
-
:class="
|
|
328
|
+
class="i-ph-caret-down transition-transform duration-200 ease-in-out group-data-[state=open]/trigger:rotate-180"
|
|
329
|
+
:class="shape.chevron"
|
|
235
330
|
/>
|
|
236
331
|
</SelectIcon>
|
|
237
332
|
</SelectTrigger>
|
|
@@ -3,6 +3,7 @@ export { default as UiDropdown } from './UiDropdown.vue'
|
|
|
3
3
|
export { default as UiDropdownTrigger } from './UiDropdownTrigger.vue'
|
|
4
4
|
export { default as UiDropdownContent } from './UiDropdownContent.vue'
|
|
5
5
|
export { default as UiDropdownItem } from './UiDropdownItem.vue'
|
|
6
|
+
export { default as UiDropdownGroup } from './UiDropdownGroup.vue'
|
|
6
7
|
export { default as UiDropdownSeparator } from './UiDropdownSeparator.vue'
|
|
7
8
|
|
|
8
9
|
// Re-export useful Reka UI types and components for advanced usage
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useStorage } from '@vueuse/core'
|
|
4
|
+
import EmojiPicker from 'vue-frimousse'
|
|
5
|
+
import { useControlShape } from '../../composables/useControlShape'
|
|
6
|
+
|
|
7
|
+
interface UiEmojiPickerListProps {
|
|
8
|
+
locale?: string
|
|
9
|
+
searchPlaceholder?: string
|
|
10
|
+
loadingLabel?: string
|
|
11
|
+
emptyLabel?: string
|
|
12
|
+
previewPlaceholder?: string
|
|
13
|
+
skinToneLabel?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<UiEmojiPickerListProps>(), {
|
|
17
|
+
locale: 'en',
|
|
18
|
+
searchPlaceholder: 'Search emoji...',
|
|
19
|
+
loadingLabel: 'Loading...',
|
|
20
|
+
emptyLabel: 'No emoji found',
|
|
21
|
+
previewPlaceholder: 'Change emoji skintone',
|
|
22
|
+
skinToneLabel: 'Change skin tone',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const emit = defineEmits<{ select: [emoji: string] }>()
|
|
26
|
+
|
|
27
|
+
type SkinTone = 'none' | 'light' | 'medium-light' | 'medium' | 'medium-dark' | 'dark'
|
|
28
|
+
const skinTones: SkinTone[] = ['none', 'light', 'medium-light', 'medium', 'medium-dark', 'dark']
|
|
29
|
+
const preferredSkinTone = useStorage<SkinTone>('livestorm:emoji-skin-tone', 'none')
|
|
30
|
+
|
|
31
|
+
const searchShape = useControlShape('field', 'sm')
|
|
32
|
+
|
|
33
|
+
const locale = computed(() => props.locale.toLowerCase().replace('_', '-'))
|
|
34
|
+
|
|
35
|
+
function selectEmoji (emoji: { emoji: string }): void {
|
|
36
|
+
emit('select', emoji.emoji)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function cycleSkinTone (): void {
|
|
40
|
+
const currentIndex = skinTones.indexOf(preferredSkinTone.value)
|
|
41
|
+
preferredSkinTone.value = skinTones[(currentIndex + 1) % skinTones.length] ?? 'none'
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<EmojiPicker.Root
|
|
47
|
+
:locale="locale"
|
|
48
|
+
:columns="9"
|
|
49
|
+
:skin-tone="preferredSkinTone"
|
|
50
|
+
:on-emoji-select="selectEmoji"
|
|
51
|
+
class="flex flex-col gap-2"
|
|
52
|
+
>
|
|
53
|
+
<EmojiPicker.Search
|
|
54
|
+
class="w-full border border-borders-neutral-default bg-surface-neutral-bg-main px-2 text-content-legends-medium-md text-text-neutral-base outline-none placeholder:text-text-neutral-tertiary focus-visible:focus-ring"
|
|
55
|
+
:class="[searchShape.height, searchShape.radius]"
|
|
56
|
+
:placeholder="searchPlaceholder"
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<EmojiPicker.Viewport class="box-border h-64 pr-2">
|
|
60
|
+
<EmojiPicker.Loading class="flex h-64 items-center justify-center text-content-legends-medium-md text-text-neutral-tertiary">
|
|
61
|
+
{{ loadingLabel }}
|
|
62
|
+
</EmojiPicker.Loading>
|
|
63
|
+
<EmojiPicker.Empty class="flex h-64 items-center justify-center text-content-legends-medium-md text-text-neutral-tertiary">
|
|
64
|
+
{{ emptyLabel }}
|
|
65
|
+
</EmojiPicker.Empty>
|
|
66
|
+
<EmojiPicker.List class="flex w-full flex-col gap-2">
|
|
67
|
+
<template #category-header="{ category }">
|
|
68
|
+
<div class="bg-surface-neutral-bg-main py-1 text-content-legends-bold-md text-text-neutral-secondary">
|
|
69
|
+
{{ category.label }}
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<template #emoji="{ emoji }">
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
:aria-label="emoji.label"
|
|
77
|
+
class="flex size-button-sm w-full! items-center justify-center rounded-button-sm text-xl leading-none hover:bg-actions-neutral-hover-overlay data-[active]:bg-actions-neutral-hover-overlay"
|
|
78
|
+
>
|
|
79
|
+
{{ emoji.emoji }}
|
|
80
|
+
</button>
|
|
81
|
+
</template>
|
|
82
|
+
</EmojiPicker.List>
|
|
83
|
+
</EmojiPicker.Viewport>
|
|
84
|
+
|
|
85
|
+
<div class="flex h-10 items-center justify-between gap-2 border-t border-divider pt-2">
|
|
86
|
+
<EmojiPicker.ActiveEmoji>
|
|
87
|
+
<template #default="{ emoji }">
|
|
88
|
+
<span
|
|
89
|
+
v-if="emoji"
|
|
90
|
+
class="flex h-6 min-w-0 flex-1 items-center gap-2 text-content-legends-medium-md text-text-neutral-secondary"
|
|
91
|
+
>
|
|
92
|
+
<span class="flex size-6 shrink-0 items-center justify-center text-xl leading-none">{{ emoji.emoji }}</span>
|
|
93
|
+
<span class="min-w-0 truncate">{{ emoji.label }}</span>
|
|
94
|
+
</span>
|
|
95
|
+
<span
|
|
96
|
+
v-else
|
|
97
|
+
class="flex h-6 min-w-0 flex-1 items-center text-content-legends-medium-md text-text-neutral-secondary"
|
|
98
|
+
>{{ previewPlaceholder }}</span>
|
|
99
|
+
</template>
|
|
100
|
+
</EmojiPicker.ActiveEmoji>
|
|
101
|
+
|
|
102
|
+
<EmojiPicker.SkinToneSelector
|
|
103
|
+
class="flex size-button-sm shrink-0 items-center justify-center rounded-button-sm text-xl leading-none hover:bg-actions-neutral-hover-overlay"
|
|
104
|
+
:aria-label="skinToneLabel"
|
|
105
|
+
@click="cycleSkinTone"
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
</EmojiPicker.Root>
|
|
109
|
+
</template>
|
|
110
|
+
|
|
111
|
+
<style scoped>
|
|
112
|
+
:deep([frimousse-row]) {
|
|
113
|
+
display: grid !important;
|
|
114
|
+
width: 100%;
|
|
115
|
+
grid-template-columns: repeat(9, minmax(0, 1fr));
|
|
116
|
+
gap: 0.125rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
:deep([frimousse-row] > [frimousse-emoji]) {
|
|
120
|
+
min-width: 0;
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<!-- UiEmptyState.vue - Empty state component for showing placeholder content with optional illustration and actions. -->
|
|
2
2
|
<script setup lang="ts">
|
|
3
3
|
import { computed, useSlots } from 'vue'
|
|
4
|
+
import { DotLottieVue } from '@lottiefiles/dotlottie-vue'
|
|
4
5
|
|
|
5
6
|
export interface UiEmptyStateProps {
|
|
6
7
|
/** Title shown as the primary heading */
|
|
@@ -9,6 +10,8 @@ export interface UiEmptyStateProps {
|
|
|
9
10
|
description?: string
|
|
10
11
|
/** Optional illustration image URL */
|
|
11
12
|
image?: string
|
|
13
|
+
/** Optional .lottie animation URL, takes precedence over `image` */
|
|
14
|
+
lottieSrc?: string
|
|
12
15
|
/** Layout orientation. `vertical` stacks the illustration above the text; `horizontal` places it to the right. */
|
|
13
16
|
variant?: 'vertical' | 'horizontal'
|
|
14
17
|
}
|
|
@@ -16,6 +19,7 @@ export interface UiEmptyStateProps {
|
|
|
16
19
|
const props = withDefaults(defineProps<UiEmptyStateProps>(), {
|
|
17
20
|
description: undefined,
|
|
18
21
|
image: undefined,
|
|
22
|
+
lottieSrc: undefined,
|
|
19
23
|
variant: 'vertical',
|
|
20
24
|
})
|
|
21
25
|
|
|
@@ -32,8 +36,14 @@ const isHorizontal = computed(() => props.variant === 'horizontal')
|
|
|
32
36
|
? 'flex-row items-center max-w-[576px] min-w-[384px]'
|
|
33
37
|
: 'flex-col items-center max-w-[320px] min-w-[256px]'"
|
|
34
38
|
>
|
|
39
|
+
<DotLottieVue
|
|
40
|
+
v-if="lottieSrc && !isHorizontal"
|
|
41
|
+
:src="lottieSrc"
|
|
42
|
+
autoplay
|
|
43
|
+
class="w-[270px]"
|
|
44
|
+
/>
|
|
35
45
|
<img
|
|
36
|
-
v-if="image && !isHorizontal"
|
|
46
|
+
v-else-if="image && !isHorizontal"
|
|
37
47
|
:src="image"
|
|
38
48
|
alt=""
|
|
39
49
|
class="w-[270px]"
|
|
@@ -51,10 +61,12 @@ const isHorizontal = computed(() => props.variant === 'horizontal')
|
|
|
51
61
|
{{ title }}
|
|
52
62
|
</h3>
|
|
53
63
|
<p
|
|
54
|
-
v-if="description"
|
|
55
|
-
class="text-content-text-md-
|
|
64
|
+
v-if="description || $slots.description"
|
|
65
|
+
class="text-content-text-md-medium text-text-neutral-tertiary"
|
|
56
66
|
>
|
|
57
|
-
|
|
67
|
+
<slot name="description">
|
|
68
|
+
{{ description }}
|
|
69
|
+
</slot>
|
|
58
70
|
</p>
|
|
59
71
|
</div>
|
|
60
72
|
|
|
@@ -45,7 +45,7 @@ defineExpose({ fieldId })
|
|
|
45
45
|
>
|
|
46
46
|
<Label
|
|
47
47
|
:for="fieldId"
|
|
48
|
-
class="text-content-legends-bold-md text-text-neutral-
|
|
48
|
+
class="text-content-legends-bold-md text-text-neutral-secondary"
|
|
49
49
|
>
|
|
50
50
|
{{ label }}{{ required ? '*' : '' }}
|
|
51
51
|
</Label>
|
|
@@ -70,9 +70,15 @@ defineExpose({ fieldId })
|
|
|
70
70
|
<p
|
|
71
71
|
v-if="displayMessage"
|
|
72
72
|
:id="`${fieldId}-message`"
|
|
73
|
-
class="text-content-legends-bold-md"
|
|
74
|
-
:class="error ? 'text-text-danger-
|
|
73
|
+
class="flex items-center gap-1 text-content-legends-bold-md"
|
|
74
|
+
:class="error ? 'text-text-danger-body' : 'text-text-neutral-secondary'"
|
|
75
75
|
>
|
|
76
|
+
<span
|
|
77
|
+
v-if="error"
|
|
78
|
+
class="i-ph-warning-circle size-4 shrink-0"
|
|
79
|
+
data-testid="ui-field-wrapper-error-icon"
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
/>
|
|
76
82
|
{{ displayMessage }}
|
|
77
83
|
</p>
|
|
78
84
|
<slot name="footer" />
|