@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
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, inject, ref } from 'vue'
|
|
3
|
-
import { Label,
|
|
4
|
-
import type { RadioValue
|
|
2
|
+
import { computed, inject, provide, ref } from 'vue'
|
|
3
|
+
import { Label, useId } from 'reka-ui'
|
|
4
|
+
import type { RadioValue } from './types'
|
|
5
5
|
import { RADIO_GROUP_KEY } from './types'
|
|
6
|
+
import { CHOICE_ROW_KEY } from '../UiChoice/types'
|
|
7
|
+
import type { ControlPosition } from '../UiChoice/types'
|
|
6
8
|
import UiTooltip from '../UiTooltip/UiTooltip.vue'
|
|
9
|
+
import RadioGlyph from './RadioGlyph.vue'
|
|
7
10
|
|
|
8
11
|
interface UiInputRadioProps {
|
|
9
12
|
/**
|
|
@@ -23,6 +26,11 @@ interface UiInputRadioProps {
|
|
|
23
26
|
* Info tooltip for the label
|
|
24
27
|
*/
|
|
25
28
|
labelInfo?: string
|
|
29
|
+
/**
|
|
30
|
+
* Icon class rendered between the radio and the text (e.g. `i-ph-bell`).
|
|
31
|
+
* In vertical orientation it sits above the text instead.
|
|
32
|
+
*/
|
|
33
|
+
icon?: string
|
|
26
34
|
/**
|
|
27
35
|
* Whether this option is disabled
|
|
28
36
|
* @default false
|
|
@@ -34,10 +42,10 @@ interface UiInputRadioProps {
|
|
|
34
42
|
*/
|
|
35
43
|
orientation?: 'horizontal' | 'vertical'
|
|
36
44
|
/**
|
|
37
|
-
*
|
|
38
|
-
* @default '
|
|
45
|
+
* Which side of the text the radio sits on, in horizontal orientation
|
|
46
|
+
* @default 'start'
|
|
39
47
|
*/
|
|
40
|
-
|
|
48
|
+
controlPosition?: ControlPosition
|
|
41
49
|
/**
|
|
42
50
|
* Radio id attribute
|
|
43
51
|
*/
|
|
@@ -48,10 +56,11 @@ const props = withDefaults(defineProps<UiInputRadioProps>(), {
|
|
|
48
56
|
label: undefined,
|
|
49
57
|
description: undefined,
|
|
50
58
|
labelInfo: undefined,
|
|
59
|
+
icon: undefined,
|
|
51
60
|
id: undefined,
|
|
61
|
+
controlPosition: 'start',
|
|
52
62
|
disabled: false,
|
|
53
63
|
orientation: 'horizontal',
|
|
54
|
-
variant: 'default',
|
|
55
64
|
})
|
|
56
65
|
|
|
57
66
|
const emit = defineEmits<{
|
|
@@ -61,6 +70,9 @@ const emit = defineEmits<{
|
|
|
61
70
|
// Group context: UiInputRadio is only usable inside UiInputRadioGroup
|
|
62
71
|
// (reka's RadioGroupItem enforces this too)
|
|
63
72
|
const group = inject(RADIO_GROUP_KEY, undefined)
|
|
73
|
+
const inChoiceRow = inject(CHOICE_ROW_KEY, false)
|
|
74
|
+
// Controls in the content slot are not the row's control
|
|
75
|
+
provide(CHOICE_ROW_KEY, false)
|
|
64
76
|
|
|
65
77
|
const generatedId = useId()
|
|
66
78
|
const inputId = computed(() => props.id ?? generatedId)
|
|
@@ -77,6 +89,11 @@ const isChecked = computed(() => {
|
|
|
77
89
|
const isDisabled = computed(() => props.disabled || (group?.disabled.value ?? false))
|
|
78
90
|
const error = computed(() => group?.error.value ?? false)
|
|
79
91
|
const size = computed(() => group?.size.value ?? 'md')
|
|
92
|
+
const isOptionRow = computed(() => props.orientation === 'horizontal')
|
|
93
|
+
// Side by side, a choice keeps its title on one line: the choice widens or the row
|
|
94
|
+
// wraps instead. A title wider than the whole row truncates.
|
|
95
|
+
const titleOnOneLine = computed(() => inChoiceRow && group?.orientation.value === 'horizontal')
|
|
96
|
+
const controlAtEnd = computed(() => props.orientation === 'horizontal' && props.controlPosition === 'end')
|
|
80
97
|
|
|
81
98
|
const descriptionId = computed(() => props.description ? `${inputId.value}-description` : undefined)
|
|
82
99
|
|
|
@@ -87,145 +104,129 @@ const ariaDescribedby = computed(() => {
|
|
|
87
104
|
|
|
88
105
|
const radioClasses = computed(() => {
|
|
89
106
|
const baseClasses = [
|
|
90
|
-
'relative shrink-0
|
|
91
|
-
'
|
|
92
|
-
'focus:outline-hidden',
|
|
107
|
+
'relative shrink-0 rounded-full border',
|
|
108
|
+
'focus:outline-hidden focus-indicator-none',
|
|
93
109
|
'cursor-pointer disabled:cursor-not-allowed',
|
|
94
110
|
]
|
|
95
111
|
|
|
96
|
-
// Size classes
|
|
97
112
|
const sizeClasses = {
|
|
98
|
-
sm: '
|
|
99
|
-
md: '
|
|
100
|
-
lg: '
|
|
113
|
+
sm: 'size-unit-0-75rem',
|
|
114
|
+
md: 'size-check-sm',
|
|
115
|
+
lg: 'size-check-md',
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
baseClasses.push(sizeClasses[size.value])
|
|
104
119
|
|
|
105
|
-
//
|
|
106
|
-
//
|
|
120
|
+
// Checked is a thin ring around a dot on the light surface; unchecked is a denser
|
|
121
|
+
// fill than the fields, which once disabled falls back to the field fill with an outline
|
|
107
122
|
if (isChecked.value) {
|
|
108
|
-
baseClasses.push('
|
|
109
|
-
|
|
123
|
+
baseClasses.push('bg-surface-neutral-bg-main', checkedRingClass.value)
|
|
124
|
+
} else if (isDisabled.value && !error.value) {
|
|
125
|
+
baseClasses.push('bg-control-field-bg border-control-disabled-border')
|
|
110
126
|
} else {
|
|
111
|
-
baseClasses.push('bg-
|
|
112
|
-
baseClasses.push(error.value ? 'border-borders-danger-strong' : 'border-borders-neutral-strong')
|
|
127
|
+
baseClasses.push('bg-control-check-bg', error.value ? 'border-control-error-border' : 'border-control-field-border')
|
|
113
128
|
if (!isDisabled.value) {
|
|
114
|
-
baseClasses.push('group-hover:
|
|
129
|
+
baseClasses.push('group-hover/option:bg-control-check-bg-hover')
|
|
115
130
|
}
|
|
116
131
|
}
|
|
117
132
|
|
|
118
|
-
// Focus ring
|
|
119
|
-
if (error.value) {
|
|
120
|
-
baseClasses.push('focus-visible:ring focus-visible:ring-borders-danger-strong')
|
|
121
|
-
} else if (isChecked.value) {
|
|
122
|
-
baseClasses.push('focus-visible:ring focus-visible:ring-borders-primary-strong')
|
|
123
|
-
} else {
|
|
124
|
-
baseClasses.push('focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
133
|
return baseClasses
|
|
128
134
|
})
|
|
129
135
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full z-1',
|
|
138
|
-
'bg-surface-neutral-bg-main transition-all',
|
|
139
|
-
]
|
|
140
|
-
|
|
141
|
-
if (isDisabled.value || error.value) {
|
|
142
|
-
baseClasses.push('size-3/4')
|
|
143
|
-
} else {
|
|
144
|
-
baseClasses.push('size-1/2 group-hover:size-3/4')
|
|
136
|
+
// Transform centering (not inset/margins): layout boxes get snapped to
|
|
137
|
+
// device pixels, which renders fractional gaps visibly off-center
|
|
138
|
+
const dotClasses = computed(() => {
|
|
139
|
+
const sizeClasses = {
|
|
140
|
+
sm: 'size-unit-0-375rem',
|
|
141
|
+
md: 'size-unit-0-5rem',
|
|
142
|
+
lg: 'size-unit-0-625rem',
|
|
145
143
|
}
|
|
146
|
-
|
|
147
|
-
return baseClasses
|
|
144
|
+
return ['absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full', sizeClasses[size.value]]
|
|
148
145
|
})
|
|
149
146
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
baseClasses.push(error.value ? 'bg-actions-danger-idle' : 'bg-actions-neutral-idle')
|
|
147
|
+
const checkedRingClass = computed(() => {
|
|
148
|
+
if (error.value) {
|
|
149
|
+
return 'border-actions-danger-idle'
|
|
150
|
+
}
|
|
151
|
+
return isDisabled.value ? 'border-borders-neutral-strong' : 'border-actions-primary-idle'
|
|
152
|
+
})
|
|
158
153
|
|
|
159
|
-
|
|
154
|
+
const indicatorClasses = computed(() => {
|
|
155
|
+
if (error.value) {
|
|
156
|
+
return [...dotClasses.value, 'bg-actions-danger-idle']
|
|
157
|
+
}
|
|
158
|
+
return [...dotClasses.value, isDisabled.value ? 'bg-text-neutral-secondary' : 'bg-actions-primary-idle']
|
|
160
159
|
})
|
|
161
160
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
// A choicebox tints its text with the selection. Disabled, a choicebox recedes
|
|
162
|
+
// whole, while a plain option keeps its selection legible in neutral.
|
|
163
|
+
const tone = computed(() => {
|
|
164
|
+
if (error.value) {
|
|
165
|
+
return 'danger'
|
|
166
|
+
}
|
|
167
|
+
if (isDisabled.value) {
|
|
168
|
+
return isChecked.value && !inChoiceRow ? 'neutral' : 'disabled'
|
|
169
|
+
}
|
|
170
|
+
return isChecked.value && inChoiceRow ? 'selected' : 'neutral'
|
|
171
|
+
})
|
|
167
172
|
|
|
168
173
|
const labelClasses = computed(() => ({
|
|
169
|
-
'text-text-
|
|
170
|
-
'text-text-
|
|
171
|
-
'text-text-
|
|
174
|
+
'text-content-text-md-medium': inChoiceRow,
|
|
175
|
+
'text-content-text-md-regular': !inChoiceRow,
|
|
176
|
+
'text-text-neutral-base': tone.value === 'neutral',
|
|
177
|
+
'text-text-neutral-tertiary': tone.value === 'disabled',
|
|
178
|
+
'text-text-danger-base': tone.value === 'danger',
|
|
179
|
+
'text-text-primary-title': tone.value === 'selected',
|
|
180
|
+
'flex items-center min-w-0': titleOnOneLine.value,
|
|
172
181
|
'cursor-not-allowed': isDisabled.value,
|
|
173
182
|
'cursor-pointer': !isDisabled.value,
|
|
174
183
|
}))
|
|
175
184
|
|
|
176
185
|
const descriptionClasses = computed(() => ({
|
|
177
186
|
'text-center': props.orientation === 'vertical',
|
|
178
|
-
'text-text-neutral-
|
|
179
|
-
'text-text-
|
|
180
|
-
'text-text-
|
|
187
|
+
'text-text-neutral-secondary': tone.value === 'neutral',
|
|
188
|
+
'text-text-neutral-tertiary': tone.value === 'disabled',
|
|
189
|
+
'text-text-danger-tertiary': tone.value === 'danger',
|
|
190
|
+
'text-text-primary-base': tone.value === 'selected',
|
|
181
191
|
'cursor-not-allowed': isDisabled.value,
|
|
182
192
|
'cursor-pointer': !isDisabled.value,
|
|
183
193
|
}))
|
|
184
194
|
|
|
185
195
|
const innerWrapperClasses = computed(() => {
|
|
186
|
-
|
|
187
|
-
// center the glyph and text block vertically
|
|
188
|
-
const classes: string[] = ['group relative rounded-md flex h-full']
|
|
196
|
+
const classes: string[] = ['group/option relative flex h-full']
|
|
189
197
|
|
|
190
|
-
|
|
191
|
-
classes.push('flex-col items-center')
|
|
192
|
-
} else {
|
|
193
|
-
classes.push('items-center')
|
|
194
|
-
}
|
|
198
|
+
classes.push(props.orientation === 'vertical' ? 'flex-col items-center' : 'items-start')
|
|
195
199
|
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
classes.push('
|
|
200
|
+
if (isOptionRow.value && !inChoiceRow) {
|
|
201
|
+
// The row bleeds around the option without taking layout space, so a one-line
|
|
202
|
+
// option is a 32px hit target; hover shows on the glyph only. Inside UiChoice the choice draws the row.
|
|
203
|
+
classes.push('isolate before:absolute before:-inset-x-3 before:-inset-y-1.5 before:-z-10 before:rounded-field-md has-[[role=radio]:focus-visible]:before:focus-ring')
|
|
204
|
+
} else if (props.orientation === 'vertical') {
|
|
205
|
+
classes.push(inChoiceRow ? 'rounded-field-md' : 'rounded-field-md has-[[role=radio]:focus-visible]:focus-ring')
|
|
200
206
|
}
|
|
201
207
|
|
|
202
|
-
|
|
203
|
-
classes.push('p-2 ring-inset')
|
|
204
|
-
if (isChecked.value && !error.value) {
|
|
205
|
-
// checked
|
|
206
|
-
classes.push('bg-actions-primary-idle-alpha-light ring-2 ring-actions-primary-idle')
|
|
207
|
-
} else if (!isChecked.value && !error.value) {
|
|
208
|
-
// idle
|
|
209
|
-
classes.push('bg-actions-neutral-idle-alpha-light ring-transparent')
|
|
210
|
-
} else if (isChecked.value && error.value) {
|
|
211
|
-
// error + checked
|
|
212
|
-
classes.push('bg-actions-danger-idle-alpha-light ring-2 ring-actions-danger-idle')
|
|
213
|
-
} else {
|
|
214
|
-
// error + unchecked
|
|
215
|
-
classes.push('bg-actions-danger-idle-alpha-light')
|
|
216
|
-
}
|
|
217
|
-
}
|
|
208
|
+
classes.push(isDisabled.value ? 'cursor-not-allowed' : 'cursor-pointer')
|
|
218
209
|
|
|
219
210
|
return classes
|
|
220
211
|
})
|
|
221
212
|
|
|
222
|
-
const
|
|
213
|
+
const glyphProps = computed(() => ({
|
|
214
|
+
inputId: inputId.value,
|
|
215
|
+
value: props.value,
|
|
216
|
+
disabled: props.disabled,
|
|
217
|
+
error: error.value,
|
|
218
|
+
ariaDescribedby: ariaDescribedby.value,
|
|
219
|
+
radioClasses: radioClasses.value,
|
|
220
|
+
indicatorClasses: indicatorClasses.value,
|
|
221
|
+
}))
|
|
222
|
+
|
|
223
|
+
const glyphRef = ref<InstanceType<typeof RadioGlyph> | null>(null)
|
|
223
224
|
|
|
224
225
|
function radioElement (): HTMLElement | undefined {
|
|
225
|
-
return
|
|
226
|
+
return glyphRef.value?.el()
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
// Make the whole wrapper (description, padding
|
|
229
|
+
// Make the whole wrapper (description, padding) select the radio.
|
|
229
230
|
// Clicks on the button itself or on the label are skipped: the button handles
|
|
230
231
|
// its own clicks and the label forwards to the button natively via `for`.
|
|
231
232
|
function onWrapperClick (event: MouseEvent) {
|
|
@@ -242,6 +243,10 @@ function onWrapperClick (event: MouseEvent) {
|
|
|
242
243
|
if (target.closest('label')) {
|
|
243
244
|
return
|
|
244
245
|
}
|
|
246
|
+
// Controls in the content slot handle their own clicks
|
|
247
|
+
if (target.closest('input, textarea, select, button, a')) {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
245
250
|
|
|
246
251
|
radioEl.click()
|
|
247
252
|
}
|
|
@@ -253,86 +258,112 @@ defineExpose({
|
|
|
253
258
|
</script>
|
|
254
259
|
|
|
255
260
|
<template>
|
|
256
|
-
<!--
|
|
257
|
-
|
|
261
|
+
<!-- An option that hosts a field or pushes its radio to the end fills its
|
|
262
|
+
slot; otherwise it shrink-wraps to keep the click zone on content -->
|
|
258
263
|
<div
|
|
259
264
|
class="ui-input-radio-wrapper"
|
|
260
|
-
|
|
265
|
+
data-testid="ui-input-radio"
|
|
266
|
+
:class="[$slots.content || controlAtEnd ? 'w-full' : 'w-fit', { 'max-w-full': titleOnOneLine }]"
|
|
261
267
|
>
|
|
262
268
|
<div
|
|
263
269
|
:class="innerWrapperClasses"
|
|
270
|
+
data-testid="ui-input-radio-row"
|
|
264
271
|
@click="onWrapperClick"
|
|
265
272
|
>
|
|
266
|
-
<!-- Radio - horizontal only
|
|
273
|
+
<!-- Radio - horizontal only. The box matches the label's line-height so
|
|
274
|
+
the glyph centers on the label line, and the checkbox's width so
|
|
275
|
+
labels line up; the glyph hugs the outer edge like a checkbox does -->
|
|
267
276
|
<span
|
|
268
277
|
v-if="orientation === 'horizontal'"
|
|
269
|
-
class="flex
|
|
278
|
+
class="flex size-5 items-center shrink-0"
|
|
279
|
+
:class="controlAtEnd ? ['order-last justify-end', inChoiceRow ? 'ms-5' : 'ms-3'] : ['justify-start', inChoiceRow ? 'me-3' : 'me-1']"
|
|
270
280
|
>
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
:value="value"
|
|
275
|
-
:disabled="disabled"
|
|
276
|
-
:class="radioClasses"
|
|
277
|
-
:aria-invalid="error || undefined"
|
|
278
|
-
:aria-describedby="ariaDescribedby"
|
|
281
|
+
<RadioGlyph
|
|
282
|
+
ref="glyphRef"
|
|
283
|
+
v-bind="glyphProps"
|
|
279
284
|
@select="emit('select', $event)"
|
|
280
|
-
|
|
281
|
-
|
|
285
|
+
/>
|
|
286
|
+
</span>
|
|
287
|
+
|
|
288
|
+
<!-- Optional icon: between the radio and the text, or above the text in
|
|
289
|
+
vertical orientation. min-h-5 keeps a small icon on the label line -->
|
|
290
|
+
<span
|
|
291
|
+
v-if="icon || $slots.icon"
|
|
292
|
+
class="flex items-center justify-center shrink-0"
|
|
293
|
+
data-testid="ui-input-radio-icon"
|
|
294
|
+
:class="orientation === 'vertical' ? 'mb-2' : 'mr-2 min-h-5'"
|
|
295
|
+
>
|
|
296
|
+
<slot name="icon">
|
|
282
297
|
<span
|
|
283
|
-
|
|
284
|
-
:class="
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
class="size-4"
|
|
299
|
+
:class="[icon, {
|
|
300
|
+
'text-text-neutral-base': tone === 'neutral',
|
|
301
|
+
'text-text-neutral-tertiary': tone === 'disabled',
|
|
302
|
+
'text-text-danger-base': tone === 'danger',
|
|
303
|
+
'text-text-primary-title': tone === 'selected',
|
|
304
|
+
}]"
|
|
290
305
|
/>
|
|
291
|
-
</
|
|
306
|
+
</slot>
|
|
292
307
|
</span>
|
|
293
308
|
|
|
294
309
|
<div
|
|
295
310
|
class="flex flex-col min-w-0"
|
|
296
|
-
:class="orientation === 'vertical' ? 'items-center' : 'flex-1'"
|
|
311
|
+
:class="[orientation === 'vertical' ? 'items-center' : 'flex-1', inChoiceRow ? 'gap-1' : 'gap-0.5']"
|
|
312
|
+
data-testid="ui-input-radio-text"
|
|
297
313
|
>
|
|
298
|
-
<
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
314
|
+
<Label
|
|
315
|
+
:for="inputId"
|
|
316
|
+
:class="labelClasses"
|
|
317
|
+
>
|
|
318
|
+
<span
|
|
319
|
+
v-if="titleOnOneLine"
|
|
320
|
+
class="truncate"
|
|
303
321
|
>
|
|
304
322
|
<slot name="label">
|
|
305
323
|
{{ label }}
|
|
306
324
|
</slot>
|
|
307
|
-
</
|
|
325
|
+
</span>
|
|
326
|
+
<slot
|
|
327
|
+
v-else
|
|
328
|
+
name="label"
|
|
329
|
+
>
|
|
330
|
+
{{ label }}
|
|
331
|
+
</slot>
|
|
308
332
|
|
|
309
|
-
<!--
|
|
310
|
-
|
|
333
|
+
<!-- icon: inline so it wraps with the text instead of centering against the whole label block,
|
|
334
|
+
and the word joiner keeps it on the line of the last word.
|
|
335
|
+
-translate-y-px because the icon aligns with the text so that its visually centered -->
|
|
336
|
+
<span
|
|
311
337
|
v-if="labelInfo"
|
|
312
|
-
:content
|
|
313
|
-
>
|
|
314
|
-
<
|
|
315
|
-
class="flex items-center justify-center size-4 cursor-help shrink-0
|
|
338
|
+
class="whitespace-nowrap before:content-['\2060']"
|
|
339
|
+
><UiTooltip :content="labelInfo">
|
|
340
|
+
<span
|
|
341
|
+
class="inline-flex items-center justify-center size-4 ms-2 align-middle -translate-y-px cursor-help shrink-0"
|
|
316
342
|
:class="{
|
|
317
|
-
'text-text-neutral-
|
|
318
|
-
'text-text-danger-
|
|
319
|
-
'text-text-primary-
|
|
343
|
+
'text-text-neutral-tertiary': tone === 'neutral' || tone === 'disabled',
|
|
344
|
+
'text-text-danger-tertiary': tone === 'danger',
|
|
345
|
+
'text-text-primary-tertiary': tone === 'selected',
|
|
320
346
|
}"
|
|
321
|
-
|
|
347
|
+
data-testid="ui-input-radio-label-info"
|
|
348
|
+
@click.stop.prevent
|
|
322
349
|
>
|
|
323
350
|
<span class="i-ph-info-fill size-4" />
|
|
324
|
-
</
|
|
325
|
-
</UiTooltip>
|
|
326
|
-
</
|
|
351
|
+
</span>
|
|
352
|
+
</UiTooltip></span>
|
|
353
|
+
</Label>
|
|
327
354
|
|
|
328
355
|
<span
|
|
329
356
|
v-if="description"
|
|
330
357
|
:id="descriptionId"
|
|
331
|
-
class="text-content-text-md-
|
|
358
|
+
class="text-content-text-md-regular"
|
|
332
359
|
:class="descriptionClasses"
|
|
333
360
|
>
|
|
334
361
|
{{ description }}
|
|
335
362
|
</span>
|
|
363
|
+
|
|
364
|
+
<!-- Extra content under the text, e.g. a field the option configures.
|
|
365
|
+
Sits outside the label so it stays out of the accessible name -->
|
|
366
|
+
<slot name="content" />
|
|
336
367
|
</div>
|
|
337
368
|
|
|
338
369
|
<!-- Radio - vertical only -->
|
|
@@ -340,35 +371,12 @@ defineExpose({
|
|
|
340
371
|
v-if="orientation === 'vertical'"
|
|
341
372
|
class="flex justify-center mt-2"
|
|
342
373
|
>
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
:value="value"
|
|
347
|
-
:disabled="disabled"
|
|
348
|
-
:class="radioClasses"
|
|
349
|
-
:aria-invalid="error || undefined"
|
|
350
|
-
:aria-describedby="ariaDescribedby"
|
|
374
|
+
<RadioGlyph
|
|
375
|
+
ref="glyphRef"
|
|
376
|
+
v-bind="glyphProps"
|
|
351
377
|
@select="emit('select', $event)"
|
|
352
|
-
|
|
353
|
-
<RadioGroupIndicator :class="indicatorClasses" />
|
|
354
|
-
<span
|
|
355
|
-
v-if="!isChecked && !isDisabled"
|
|
356
|
-
:class="previewDotClasses"
|
|
357
|
-
/>
|
|
358
|
-
<div
|
|
359
|
-
v-if="!isDisabled"
|
|
360
|
-
class="absolute inset-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
|
|
361
|
-
:class="hoverOverlayClasses"
|
|
362
|
-
/>
|
|
363
|
-
</RadioGroupItem>
|
|
378
|
+
/>
|
|
364
379
|
</span>
|
|
365
|
-
|
|
366
|
-
<!-- Card hover overlay -->
|
|
367
|
-
<div
|
|
368
|
-
v-if="variant === 'card' && !isDisabled"
|
|
369
|
-
class="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none rounded-md"
|
|
370
|
-
:class="hoverOverlayClasses"
|
|
371
|
-
/>
|
|
372
380
|
</div>
|
|
373
381
|
</div>
|
|
374
382
|
</template>
|
|
@@ -55,6 +55,10 @@ interface UiInputRadioGroupProps {
|
|
|
55
55
|
* @default false
|
|
56
56
|
*/
|
|
57
57
|
fullWidth?: boolean
|
|
58
|
+
/**
|
|
59
|
+
* Id of the element naming the group, set on the radiogroup itself
|
|
60
|
+
*/
|
|
61
|
+
ariaLabelledby?: string
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
const props = withDefaults(defineProps<UiInputRadioGroupProps>(), {
|
|
@@ -68,6 +72,7 @@ const props = withDefaults(defineProps<UiInputRadioGroupProps>(), {
|
|
|
68
72
|
size: 'md',
|
|
69
73
|
orientation: 'vertical',
|
|
70
74
|
fullWidth: false,
|
|
75
|
+
ariaLabelledby: undefined,
|
|
71
76
|
})
|
|
72
77
|
|
|
73
78
|
const emit = defineEmits<{
|
|
@@ -98,18 +103,21 @@ const displayMessage = computed(() => {
|
|
|
98
103
|
|
|
99
104
|
const messageId = computed(() => displayMessage.value ? `${groupId}-message` : undefined)
|
|
100
105
|
|
|
101
|
-
// items-stretch keeps card tiles equal-height when they share a row
|
|
106
|
+
// items-stretch keeps card tiles equal-height when they share a row. The 12px
|
|
107
|
+
// gap holds the 6px bleed of each option row, so one-line rows stay 32px hit
|
|
108
|
+
// targets that never overlap.
|
|
102
109
|
const rootClasses = computed(() =>
|
|
103
110
|
props.orientation === 'horizontal'
|
|
104
|
-
? 'flex flex-row flex-wrap items-stretch gap-
|
|
105
|
-
: 'flex flex-col gap-
|
|
111
|
+
? 'flex flex-row flex-wrap items-stretch gap-3'
|
|
112
|
+
: 'flex flex-col gap-3',
|
|
106
113
|
)
|
|
107
114
|
|
|
115
|
+
// mt-1.5 clears the 6px bleed of the last option row
|
|
108
116
|
const displayMessageClasses = computed(() => {
|
|
109
|
-
const baseClasses = ['text-content-legends-bold-md mt-1']
|
|
117
|
+
const baseClasses = ['text-content-legends-bold-md mt-1.5']
|
|
110
118
|
|
|
111
119
|
if (props.error) {
|
|
112
|
-
baseClasses.push('text-text-danger-
|
|
120
|
+
baseClasses.push('text-text-danger-body')
|
|
113
121
|
} else {
|
|
114
122
|
baseClasses.push('text-text-neutral-secondary')
|
|
115
123
|
}
|
|
@@ -121,6 +129,7 @@ provide(RADIO_GROUP_KEY, {
|
|
|
121
129
|
modelValue: currentValue,
|
|
122
130
|
error: toRef(props, 'error'),
|
|
123
131
|
size: toRef(props, 'size'),
|
|
132
|
+
orientation: toRef(props, 'orientation'),
|
|
124
133
|
disabled: toRef(props, 'disabled'),
|
|
125
134
|
messageId,
|
|
126
135
|
})
|
|
@@ -138,6 +147,7 @@ provide(RADIO_GROUP_KEY, {
|
|
|
138
147
|
:loop="loop"
|
|
139
148
|
:required="required"
|
|
140
149
|
:orientation="orientation"
|
|
150
|
+
:aria-labelledby="ariaLabelledby"
|
|
141
151
|
:class="rootClasses"
|
|
142
152
|
@update:model-value="onUpdateModelValue"
|
|
143
153
|
>
|
|
@@ -3,8 +3,6 @@ import type { AcceptableValue } from 'reka-ui'
|
|
|
3
3
|
|
|
4
4
|
export type RadioSize = 'sm' | 'md' | 'lg'
|
|
5
5
|
|
|
6
|
-
export type RadioVariant = 'default' | 'card'
|
|
7
|
-
|
|
8
6
|
/**
|
|
9
7
|
* Valid radio option values. `null` is a supported option value;
|
|
10
8
|
* `undefined` means "no selection". Object values must be reference-stable.
|
|
@@ -16,6 +14,7 @@ export interface RadioGroupContext {
|
|
|
16
14
|
modelValue: Readonly<Ref<RadioValue | undefined>>
|
|
17
15
|
error: Readonly<Ref<boolean>>
|
|
18
16
|
size: Readonly<Ref<RadioSize>>
|
|
17
|
+
orientation: Readonly<Ref<'vertical' | 'horizontal'>>
|
|
19
18
|
disabled: Readonly<Ref<boolean>>
|
|
20
19
|
/** id of the group error message, for aria-describedby on items */
|
|
21
20
|
messageId: Readonly<Ref<string | undefined>>
|
|
@@ -7,6 +7,7 @@ import UiDropdownContent from '../UiDropdown/UiDropdownContent.vue'
|
|
|
7
7
|
import UiDropdownItem from '../UiDropdown/UiDropdownItem.vue'
|
|
8
8
|
import type { AcceptableValue } from 'reka-ui'
|
|
9
9
|
import type { SelectSize } from './types'
|
|
10
|
+
import { useControlShape } from '../../composables/useControlShape'
|
|
10
11
|
|
|
11
12
|
interface UiInputSelectProps {
|
|
12
13
|
size?: SelectSize
|
|
@@ -57,6 +58,8 @@ const props = withDefaults(defineProps<UiInputSelectProps>(), {
|
|
|
57
58
|
multiple: false,
|
|
58
59
|
})
|
|
59
60
|
|
|
61
|
+
const shape = useControlShape('field', () => props.size)
|
|
62
|
+
|
|
60
63
|
// Sentinel value for the "clear" option — intentionally verbose to avoid collisions with real values
|
|
61
64
|
const CLEAR_VALUE = '__ui-input-select-clear__'
|
|
62
65
|
|
|
@@ -110,13 +113,12 @@ function handleUpdate (value: AcceptableValue) {
|
|
|
110
113
|
<!-- Loading State -->
|
|
111
114
|
<div
|
|
112
115
|
v-if="loading"
|
|
113
|
-
class="flex items-center gap-2 w-full
|
|
114
|
-
:class="{
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
|
|
119
|
-
}"
|
|
116
|
+
class="flex items-center gap-2 w-full border border-solid border-control-field-border bg-control-field-bg"
|
|
117
|
+
:class="[shape.radius, shape.height, {
|
|
118
|
+
'px-2': size === 'sm',
|
|
119
|
+
'px-3': size === 'md',
|
|
120
|
+
'px-4': size === 'lg' || size === 'xl',
|
|
121
|
+
}]"
|
|
120
122
|
>
|
|
121
123
|
<div class="h-3 w-24 bg-surface-neutral-alpha-400 rounded-md animate-pulse" />
|
|
122
124
|
</div>
|