@multiplatform.one/forms 6.4.3 → 6.6.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/dist/cjs/Button/index.cjs +23 -8
- package/dist/cjs/Button/index.native.js +23 -8
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/Skeleton/index.cjs +1 -0
- package/dist/cjs/Skeleton/index.native.js +4 -0
- package/dist/cjs/Skeleton/index.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/Checkbox.cjs +22 -15
- package/dist/cjs/fields/Checkbox/Checkbox.native.js +11 -6
- package/dist/cjs/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/index.cjs +9 -3
- package/dist/cjs/fields/Checkbox/index.native.js +9 -3
- package/dist/cjs/fields/Checkbox/index.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/index.cjs +9 -4
- package/dist/cjs/fields/Combobox/index.native.js +9 -4
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DateRangePicker.cjs +30 -20
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js +36 -21
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.cjs +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.native.js +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/MonthPicker.cjs +24 -14
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js +36 -15
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/MultiDatePicker.cjs +27 -18
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js +32 -19
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.cjs +11 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.native.js +12 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/GeolocationMap.native.js +2 -2
- package/dist/cjs/fields/RadioGroup/index.cjs +2 -2
- package/dist/cjs/fields/RadioGroup/index.native.js +2 -2
- package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/index.cjs +17 -2
- package/dist/cjs/fields/Rating/index.native.js +25 -2
- package/dist/cjs/fields/Rating/index.native.js.map +1 -1
- package/dist/cjs/fields/SearchInput/index.cjs +5 -1
- package/dist/cjs/fields/SearchInput/index.native.js +5 -1
- package/dist/cjs/fields/SearchInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.cjs +6 -10
- package/dist/cjs/fields/Select/index.native.js +12 -10
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/Signature/index.cjs +1 -1
- package/dist/cjs/fields/Signature/index.native.js +1 -1
- package/dist/cjs/fields/Signature/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.cjs +1 -0
- package/dist/cjs/fields/Slider/index.native.js +4 -0
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/TextArea/index.cjs +3 -2
- package/dist/cjs/fields/TextArea/index.native.js +5 -2
- package/dist/cjs/fields/TextArea/index.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/index.cjs +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.cjs +6 -22
- package/dist/cjs/fields/ToggleGroup/index.native.js +11 -28
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/shared/TimeColumn.cjs +1 -1
- package/dist/cjs/shared/TimeColumn.native.js +1 -1
- package/dist/cjs/shared/TimeColumn.native.js.map +1 -1
- package/dist/cjs/shared/focusManagement.cjs +7 -1
- package/dist/cjs/shared/focusManagement.native.js +5 -1
- package/dist/cjs/shared/focusManagement.native.js.map +1 -1
- package/dist/cjs/shared/t.cjs +5 -0
- package/dist/cjs/shared/t.native.js +5 -0
- package/dist/cjs/shared/t.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +24 -9
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +24 -9
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/Skeleton/index.mjs +2 -1
- package/dist/esm/Skeleton/index.mjs.map +1 -1
- package/dist/esm/Skeleton/index.native.js +5 -1
- package/dist/esm/Skeleton/index.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/Checkbox.mjs +24 -17
- package/dist/esm/fields/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/esm/fields/Checkbox/Checkbox.native.js +13 -8
- package/dist/esm/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/index.mjs +11 -5
- package/dist/esm/fields/Checkbox/index.mjs.map +1 -1
- package/dist/esm/fields/Checkbox/index.native.js +11 -5
- package/dist/esm/fields/Checkbox/index.native.js.map +1 -1
- package/dist/esm/fields/Combobox/index.mjs +9 -4
- package/dist/esm/fields/Combobox/index.mjs.map +1 -1
- package/dist/esm/fields/Combobox/index.native.js +9 -4
- package/dist/esm/fields/Combobox/index.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/DateRangePicker.mjs +31 -21
- package/dist/esm/fields/DatePicker/DateRangePicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/DateRangePicker.native.js +37 -22
- package/dist/esm/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.mjs +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.native.js +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/MonthPicker.mjs +25 -15
- package/dist/esm/fields/DatePicker/MonthPicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/MonthPicker.native.js +37 -16
- package/dist/esm/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/MultiDatePicker.mjs +28 -19
- package/dist/esm/fields/DatePicker/MultiDatePicker.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/MultiDatePicker.native.js +33 -20
- package/dist/esm/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.mjs +10 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.native.js +11 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/GeolocationMap.native.js +2 -2
- package/dist/esm/fields/RadioGroup/index.mjs +2 -2
- package/dist/esm/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js +2 -2
- package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/esm/fields/Rating/index.mjs +17 -2
- package/dist/esm/fields/Rating/index.mjs.map +1 -1
- package/dist/esm/fields/Rating/index.native.js +25 -2
- package/dist/esm/fields/Rating/index.native.js.map +1 -1
- package/dist/esm/fields/SearchInput/index.mjs +6 -2
- package/dist/esm/fields/SearchInput/index.mjs.map +1 -1
- package/dist/esm/fields/SearchInput/index.native.js +6 -2
- package/dist/esm/fields/SearchInput/index.native.js.map +1 -1
- package/dist/esm/fields/Select/index.mjs +6 -10
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +12 -10
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/Signature/index.mjs +1 -1
- package/dist/esm/fields/Signature/index.mjs.map +1 -1
- package/dist/esm/fields/Signature/index.native.js +1 -1
- package/dist/esm/fields/Signature/index.native.js.map +1 -1
- package/dist/esm/fields/Slider/index.mjs +1 -0
- package/dist/esm/fields/Slider/index.mjs.map +1 -1
- package/dist/esm/fields/Slider/index.native.js +4 -0
- package/dist/esm/fields/Slider/index.native.js.map +1 -1
- package/dist/esm/fields/TextArea/index.mjs +3 -2
- package/dist/esm/fields/TextArea/index.mjs.map +1 -1
- package/dist/esm/fields/TextArea/index.native.js +5 -2
- package/dist/esm/fields/TextArea/index.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/index.mjs +1 -1
- package/dist/esm/fields/TimePicker/index.mjs.map +1 -1
- package/dist/esm/fields/TimePicker/index.native.js +1 -1
- package/dist/esm/fields/TimePicker/index.native.js.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.mjs +7 -23
- package/dist/esm/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.native.js +12 -29
- package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/esm/shared/TimeColumn.mjs +1 -1
- package/dist/esm/shared/TimeColumn.mjs.map +1 -1
- package/dist/esm/shared/TimeColumn.native.js +1 -1
- package/dist/esm/shared/TimeColumn.native.js.map +1 -1
- package/dist/esm/shared/focusManagement.mjs +7 -1
- package/dist/esm/shared/focusManagement.mjs.map +1 -1
- package/dist/esm/shared/focusManagement.native.js +5 -1
- package/dist/esm/shared/focusManagement.native.js.map +1 -1
- package/dist/esm/shared/t.mjs +5 -1
- package/dist/esm/shared/t.mjs.map +1 -1
- package/dist/esm/shared/t.native.js +5 -1
- package/dist/esm/shared/t.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +24 -9
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +23 -8
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/Skeleton/index.mjs +2 -1
- package/dist/jsx/Skeleton/index.mjs.map +1 -1
- package/dist/jsx/Skeleton/index.native.js +4 -0
- package/dist/jsx/Skeleton/index.native.js.map +1 -1
- package/dist/jsx/fields/Checkbox/Checkbox.mjs +24 -17
- package/dist/jsx/fields/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/jsx/fields/Checkbox/Checkbox.native.js +11 -6
- package/dist/jsx/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/jsx/fields/Checkbox/index.mjs +11 -5
- package/dist/jsx/fields/Checkbox/index.mjs.map +1 -1
- package/dist/jsx/fields/Checkbox/index.native.js +9 -3
- package/dist/jsx/fields/Checkbox/index.native.js.map +1 -1
- package/dist/jsx/fields/Combobox/index.mjs +9 -4
- package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
- package/dist/jsx/fields/Combobox/index.native.js +9 -4
- package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/DateRangePicker.mjs +31 -21
- package/dist/jsx/fields/DatePicker/DateRangePicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/DateRangePicker.native.js +36 -21
- package/dist/jsx/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.mjs +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.native.js +1 -1
- package/dist/jsx/fields/DatePicker/DatetimePicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/MonthPicker.mjs +25 -15
- package/dist/jsx/fields/DatePicker/MonthPicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/MonthPicker.native.js +36 -15
- package/dist/jsx/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/MultiDatePicker.mjs +28 -19
- package/dist/jsx/fields/DatePicker/MultiDatePicker.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/MultiDatePicker.native.js +32 -19
- package/dist/jsx/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.mjs +10 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.native.js +12 -1
- package/dist/jsx/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/jsx/fields/Geolocation/GeolocationMap.native.js +2 -2
- package/dist/jsx/fields/RadioGroup/index.mjs +2 -2
- package/dist/jsx/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/RadioGroup/index.native.js +2 -2
- package/dist/jsx/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/jsx/fields/Rating/index.mjs +17 -2
- package/dist/jsx/fields/Rating/index.mjs.map +1 -1
- package/dist/jsx/fields/Rating/index.native.js +25 -2
- package/dist/jsx/fields/Rating/index.native.js.map +1 -1
- package/dist/jsx/fields/SearchInput/index.mjs +6 -2
- package/dist/jsx/fields/SearchInput/index.mjs.map +1 -1
- package/dist/jsx/fields/SearchInput/index.native.js +5 -1
- package/dist/jsx/fields/SearchInput/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/index.mjs +6 -10
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +12 -10
- package/dist/jsx/fields/Select/index.native.js.map +1 -1
- package/dist/jsx/fields/Signature/index.mjs +1 -1
- package/dist/jsx/fields/Signature/index.mjs.map +1 -1
- package/dist/jsx/fields/Signature/index.native.js +1 -1
- package/dist/jsx/fields/Signature/index.native.js.map +1 -1
- package/dist/jsx/fields/Slider/index.mjs +1 -0
- package/dist/jsx/fields/Slider/index.mjs.map +1 -1
- package/dist/jsx/fields/Slider/index.native.js +4 -0
- package/dist/jsx/fields/Slider/index.native.js.map +1 -1
- package/dist/jsx/fields/TextArea/index.mjs +3 -2
- package/dist/jsx/fields/TextArea/index.mjs.map +1 -1
- package/dist/jsx/fields/TextArea/index.native.js +5 -2
- package/dist/jsx/fields/TextArea/index.native.js.map +1 -1
- package/dist/jsx/fields/TimePicker/index.mjs +1 -1
- package/dist/jsx/fields/TimePicker/index.mjs.map +1 -1
- package/dist/jsx/fields/TimePicker/index.native.js +1 -1
- package/dist/jsx/fields/TimePicker/index.native.js.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.mjs +7 -23
- package/dist/jsx/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.native.js +11 -28
- package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/jsx/shared/TimeColumn.mjs +1 -1
- package/dist/jsx/shared/TimeColumn.mjs.map +1 -1
- package/dist/jsx/shared/TimeColumn.native.js +1 -1
- package/dist/jsx/shared/TimeColumn.native.js.map +1 -1
- package/dist/jsx/shared/focusManagement.mjs +7 -1
- package/dist/jsx/shared/focusManagement.mjs.map +1 -1
- package/dist/jsx/shared/focusManagement.native.js +5 -1
- package/dist/jsx/shared/focusManagement.native.js.map +1 -1
- package/dist/jsx/shared/t.mjs +5 -1
- package/dist/jsx/shared/t.mjs.map +1 -1
- package/dist/jsx/shared/t.native.js +5 -0
- package/dist/jsx/shared/t.native.js.map +1 -1
- package/package.json +8 -7
- package/src/Button/Button.stories.tsx +19 -0
- package/src/Button/index.tsx +45 -12
- package/src/ControlGroup/ControlGroup.stories.tsx +12 -0
- package/src/FieldLayout.stories.tsx +3 -2
- package/src/Form/Form.stories.tsx +13 -0
- package/src/Skeleton/index.tsx +5 -1
- package/src/fields/Checkbox/Checkbox.spec.tsx +50 -1
- package/src/fields/Checkbox/Checkbox.tsx +47 -19
- package/src/fields/Checkbox/index.tsx +23 -5
- package/src/fields/ColorPicker/ColorPicker.stories.tsx +2 -1
- package/src/fields/Combobox/index.tsx +8 -3
- package/src/fields/DatePicker/DatePicker.spec.tsx +93 -0
- package/src/fields/DatePicker/DatePicker.stories.tsx +2 -1
- package/src/fields/DatePicker/DateRangePicker.spec.tsx +72 -0
- package/src/fields/DatePicker/DateRangePicker.tsx +44 -30
- package/src/fields/DatePicker/DatetimePicker.spec.tsx +39 -0
- package/src/fields/DatePicker/DatetimePicker.tsx +1 -1
- package/src/fields/DatePicker/MonthPicker.spec.tsx +61 -0
- package/src/fields/DatePicker/MonthPicker.tsx +47 -21
- package/src/fields/DatePicker/MultiDatePicker.spec.tsx +34 -0
- package/src/fields/DatePicker/MultiDatePicker.tsx +39 -27
- package/src/fields/DatePicker/fieldValueSync.tsx +23 -0
- package/src/fields/FileUpload/FileUpload.stories.tsx +11 -1
- package/src/fields/Geolocation/Geolocation.stories.tsx +9 -0
- package/src/fields/Geolocation/GeolocationMap.native.tsx +2 -2
- package/src/fields/ImageField/ImageField.stories.tsx +2 -1
- package/src/fields/Input/Input.stories.tsx +19 -0
- package/src/fields/Progress/Progress.stories.tsx +19 -0
- package/src/fields/RadioGroup/RadioGroup.spec.tsx +139 -2
- package/src/fields/RadioGroup/index.tsx +2 -2
- package/src/fields/Rating/Rating.spec.tsx +43 -1
- package/src/fields/Rating/index.tsx +43 -4
- package/src/fields/SearchInput/SearchInput.spec.tsx +11 -0
- package/src/fields/SearchInput/index.tsx +6 -2
- package/src/fields/Select/Select.stories.tsx +19 -0
- package/src/fields/Select/index.tsx +12 -5
- package/src/fields/Signature/index.tsx +1 -1
- package/src/fields/Slider/Slider.spec.tsx +89 -4
- package/src/fields/Slider/Slider.stories.tsx +19 -0
- package/src/fields/Slider/index.tsx +4 -0
- package/src/fields/Stepper/Stepper.stories.tsx +12 -0
- package/src/fields/Switch/Switch.spec.tsx +50 -2
- package/src/fields/TextArea/TextArea.spec.tsx +57 -0
- package/src/fields/TextArea/TextArea.stories.tsx +19 -0
- package/src/fields/TextArea/index.tsx +10 -2
- package/src/fields/TimePicker/index.tsx +1 -1
- package/src/fields/ToggleGroup/ToggleGroup.spec.tsx +64 -1
- package/src/fields/ToggleGroup/ToggleGroup.stories.tsx +19 -0
- package/src/fields/ToggleGroup/index.tsx +31 -29
- package/src/shared/FloatingListScrollArrows.stories.tsx +4 -2
- package/src/shared/TimeColumn.tsx +1 -1
- package/src/shared/focusManagement.ts +12 -1
- package/src/shared/t.spec.ts +39 -0
- package/src/shared/t.ts +20 -0
- package/types/Button/index.d.ts +9 -1
- package/types/Button/index.d.ts.map +1 -1
- package/types/Skeleton/index.d.ts.map +1 -1
- package/types/fields/Checkbox/Checkbox.d.ts.map +1 -1
- package/types/fields/Checkbox/index.d.ts.map +1 -1
- package/types/fields/Combobox/index.d.ts.map +1 -1
- package/types/fields/DatePicker/DateRangePicker.d.ts.map +1 -1
- package/types/fields/DatePicker/MonthPicker.d.ts.map +1 -1
- package/types/fields/DatePicker/MultiDatePicker.d.ts.map +1 -1
- package/types/fields/DatePicker/fieldValueSync.d.ts +14 -0
- package/types/fields/DatePicker/fieldValueSync.d.ts.map +1 -1
- package/types/fields/Rating/index.d.ts.map +1 -1
- package/types/fields/SearchInput/index.d.ts.map +1 -1
- package/types/fields/Select/index.d.ts.map +1 -1
- package/types/fields/Slider/index.d.ts.map +1 -1
- package/types/fields/TextArea/index.d.ts.map +1 -1
- package/types/fields/ToggleGroup/index.d.ts.map +1 -1
- package/types/shared/focusManagement.d.ts.map +1 -1
- package/types/shared/t.d.ts +16 -0
- package/types/shared/t.d.ts.map +1 -1
|
@@ -311,7 +311,7 @@ const SelectItem = memo(function SelectItem({
|
|
|
311
311
|
: formInputColors.background.focus,
|
|
312
312
|
},
|
|
313
313
|
})}
|
|
314
|
-
{...(ctx.knobProps.transition ? {
|
|
314
|
+
{...(ctx.knobProps.transition ? { transition: ctx.knobProps.transition } : undefined)}
|
|
315
315
|
>
|
|
316
316
|
<Text
|
|
317
317
|
fontFamily={ctx.knobProps.body.fontFamily}
|
|
@@ -839,9 +839,14 @@ function FloatingSelectCore({
|
|
|
839
839
|
cursor="pointer"
|
|
840
840
|
disabled={disabled}
|
|
841
841
|
size={knobProps.sizeToken}
|
|
842
|
+
// LC-54 PRESS-FLOOR: the size knob may shrink chrome (compact → $3
|
|
843
|
+
// = 36px) but the trigger's press target never drops below 44 —
|
|
844
|
+
// same minHeight floor the house Button holds. Without it a compact
|
|
845
|
+
// Select sat 36px beside 44px Inputs/Buttons in one control row.
|
|
846
|
+
minHeight={44}
|
|
842
847
|
{...(inTableCell
|
|
843
|
-
? // Chromeless in cells
|
|
844
|
-
{
|
|
848
|
+
? // Chromeless in cells (the floor above still applies).
|
|
849
|
+
{}
|
|
845
850
|
: {
|
|
846
851
|
borderRadius: knobProps.borderRadius.borderRadius,
|
|
847
852
|
borderWidth: knobProps.borderRadius.borderWidth,
|
|
@@ -1257,9 +1262,11 @@ function NativeSelectCore({
|
|
|
1257
1262
|
<InputParts.Box
|
|
1258
1263
|
disabled={disabled}
|
|
1259
1264
|
size={knobProps.sizeToken}
|
|
1265
|
+
// LC-54 PRESS-FLOOR: ≥44px trigger target at every size (matches
|
|
1266
|
+
// the web trigger branch above).
|
|
1267
|
+
minHeight={44}
|
|
1260
1268
|
{...(inTableCell
|
|
1261
|
-
?
|
|
1262
|
-
{ minHeight: 44 }
|
|
1269
|
+
? {}
|
|
1263
1270
|
: {
|
|
1264
1271
|
borderRadius: knobProps.borderRadius.borderRadius,
|
|
1265
1272
|
borderWidth: knobProps.borderRadius.borderWidth,
|
|
@@ -454,7 +454,7 @@ function SignatureCanvasImpl({
|
|
|
454
454
|
opacity={disabled ? 0.6 : 1}
|
|
455
455
|
aspectRatio={aspectRatio}
|
|
456
456
|
minHeight={minHeightPx}
|
|
457
|
-
{...(knobProps.transition ? {
|
|
457
|
+
{...(knobProps.transition ? { transition: knobProps.transition } : undefined)}
|
|
458
458
|
aria-required={required || undefined}
|
|
459
459
|
aria-invalid={hasError || undefined}
|
|
460
460
|
>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import { useForm } from "@tanstack/react-form";
|
|
2
3
|
import { act, fireEvent, renderHook, waitFor } from "@testing-library/react";
|
|
3
4
|
import { getSize } from "@tamagui/get-token";
|
|
@@ -205,6 +206,76 @@ describe("Slider", () => {
|
|
|
205
206
|
});
|
|
206
207
|
});
|
|
207
208
|
|
|
209
|
+
describe("late-arriving value display (create-mode seeding)", () => {
|
|
210
|
+
// The upstream value was fed into tamagui as mount-only `defaultValue`,
|
|
211
|
+
// so a value applied AFTER first paint (create-mode engine seeding,
|
|
212
|
+
// form.setFieldValue, live server merge) never moved the thumb. The
|
|
213
|
+
// slider must run controlled when an upstream value drives it — and
|
|
214
|
+
// adopting a programmatic value must not emit onChange (LC-14: a seeded
|
|
215
|
+
// value is not a user edit).
|
|
216
|
+
const getThumb = (container: ParentNode) =>
|
|
217
|
+
container.querySelector('[role="slider"]') as HTMLElement | null;
|
|
218
|
+
|
|
219
|
+
it("standalone controlled: a value arriving after mount renders, without onChange", async () => {
|
|
220
|
+
const onChange = vi.fn();
|
|
221
|
+
const onValueChange = vi.fn();
|
|
222
|
+
const result = renderWithProviders(
|
|
223
|
+
<Slider label="Progress" value={[25]} onChange={onChange} onValueChange={onValueChange} />,
|
|
224
|
+
);
|
|
225
|
+
expect(getThumb(result.container)?.getAttribute("aria-valuenow")).toBe("25");
|
|
226
|
+
|
|
227
|
+
// Late seed: the upstream doc value arrives after first paint.
|
|
228
|
+
result.rerender(
|
|
229
|
+
<Slider label="Progress" value={[75]} onChange={onChange} onValueChange={onValueChange} />,
|
|
230
|
+
);
|
|
231
|
+
await waitFor(() => {
|
|
232
|
+
expect(getThumb(result.container)?.getAttribute("aria-valuenow")).toBe("75");
|
|
233
|
+
});
|
|
234
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
235
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("form-integrated: a field value set after mount renders, without onChange", async () => {
|
|
239
|
+
const onChange = vi.fn();
|
|
240
|
+
let formApi: { setFieldValue: (name: "volume", value: number[]) => void } | undefined;
|
|
241
|
+
const TestComponent = () => {
|
|
242
|
+
const form = useForm({ defaultValues: { volume: [10] } });
|
|
243
|
+
formApi = form as unknown as typeof formApi;
|
|
244
|
+
return (
|
|
245
|
+
<Form form={form}>
|
|
246
|
+
<Slider label="Volume" name="volume" onChange={onChange} />
|
|
247
|
+
</Form>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
const result = renderWithProviders(<TestComponent />);
|
|
251
|
+
expect(getThumb(result.container)?.getAttribute("aria-valuenow")).toBe("10");
|
|
252
|
+
|
|
253
|
+
await act(async () => {
|
|
254
|
+
formApi?.setFieldValue("volume", [60]);
|
|
255
|
+
});
|
|
256
|
+
await waitFor(() => {
|
|
257
|
+
expect(getThumb(result.container)?.getAttribute("aria-valuenow")).toBe("60");
|
|
258
|
+
});
|
|
259
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("uncontrolled standalone (defaultValue only) still moves on keyboard input", async () => {
|
|
263
|
+
const onChange = vi.fn();
|
|
264
|
+
const result = renderWithProviders(
|
|
265
|
+
<Slider label="Free" defaultValue={[40]} onChange={onChange} />,
|
|
266
|
+
);
|
|
267
|
+
const thumb = getThumb(result.container);
|
|
268
|
+
expect(thumb?.getAttribute("aria-valuenow")).toBe("40");
|
|
269
|
+
await act(async () => {
|
|
270
|
+
if (thumb) fireEvent.keyDown(thumb, { key: "ArrowRight" });
|
|
271
|
+
});
|
|
272
|
+
await waitFor(() => {
|
|
273
|
+
expect(getThumb(result.container)?.getAttribute("aria-valuenow")).toBe("41");
|
|
274
|
+
});
|
|
275
|
+
expect(onChange).toHaveBeenCalledWith([41]);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
208
279
|
describe("field integration", () => {
|
|
209
280
|
it("should render error text when provided", () => {
|
|
210
281
|
const result = renderWithProviders(
|
|
@@ -688,10 +759,24 @@ describe("Slider", () => {
|
|
|
688
759
|
});
|
|
689
760
|
|
|
690
761
|
it("track click arms the left/transform tween then disarms after the jump window", async () => {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
)
|
|
762
|
+
// Controlled slider with the standard feedback loop (value + onChange
|
|
763
|
+
// echoing back) — a bare static `value` is a frozen controlled input
|
|
764
|
+
// now that the upstream value really drives tamagui.
|
|
765
|
+
const JumpExample = () => {
|
|
766
|
+
const [values, setValues] = useState([60]);
|
|
767
|
+
return (
|
|
768
|
+
// min > 0 so the degenerate unmeasured-layout pointer value stays truthy
|
|
769
|
+
<Slider
|
|
770
|
+
label="Jump"
|
|
771
|
+
name="jump"
|
|
772
|
+
value={values}
|
|
773
|
+
onChange={setValues}
|
|
774
|
+
min={10}
|
|
775
|
+
max={110}
|
|
776
|
+
/>
|
|
777
|
+
);
|
|
778
|
+
};
|
|
779
|
+
const result = renderWithProviders(<JumpExample />);
|
|
695
780
|
const thumb = result.container.querySelector('[role="slider"]') as HTMLElement;
|
|
696
781
|
const track = getTrack(result.container, thumb);
|
|
697
782
|
expect(track).toBeDefined();
|
|
@@ -199,7 +199,26 @@ export const Interactive: Story = {
|
|
|
199
199
|
},
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY): the right column renders the
|
|
204
|
+
* RAW tamagui Slider beside the house Slider strictly so the difference stays
|
|
205
|
+
* visible. Raw primitives here are by design and labeled — knob/contrast probes
|
|
206
|
+
* skip this story via `parameters.foreignContrastSpecimen`.
|
|
207
|
+
*/
|
|
202
208
|
export const Comparison: Story = {
|
|
209
|
+
name: "Foreign contrast specimen (raw tamagui)",
|
|
210
|
+
parameters: {
|
|
211
|
+
// Machine-readable exemption for knob/contrast probes and the LC-56 linter arm.
|
|
212
|
+
foreignContrastSpecimen: true,
|
|
213
|
+
docs: {
|
|
214
|
+
description: {
|
|
215
|
+
story:
|
|
216
|
+
"FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY). The right column is the raw " +
|
|
217
|
+
"tamagui Slider, rendered for contrast only: it ignores house knobs and control " +
|
|
218
|
+
"recipes by design. Probes asserting house rendering must skip this story.",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
203
222
|
render: () => {
|
|
204
223
|
const ComparisonExample = () => {
|
|
205
224
|
const [customVal, setCustomVal] = useState([50]);
|
|
@@ -333,6 +333,10 @@ function SliderControl({
|
|
|
333
333
|
const trackActive = (
|
|
334
334
|
<TamaguiSlider.TrackActive
|
|
335
335
|
backgroundColor={formControlColors.border}
|
|
336
|
+
// The fill rides the same radius as the track it sits in: without it
|
|
337
|
+
// tamagui's default left a pill fill inside a square track at
|
|
338
|
+
// `borderRadius:none` — an undeclared knob-immune shape (D-09 / LC-60).
|
|
339
|
+
borderRadius={borderRadius}
|
|
336
340
|
transition={transitionProp}
|
|
337
341
|
/>
|
|
338
342
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { action } from "@multiplatform.one/storybook";
|
|
2
|
+
import type { GuardrailSpecimenParameter } from "@multiplatform.one/theme";
|
|
2
3
|
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
3
4
|
import { useForm } from "@tanstack/react-form";
|
|
4
5
|
import { useState } from "react";
|
|
@@ -94,6 +95,17 @@ export const Basic: Story = {
|
|
|
94
95
|
};
|
|
95
96
|
|
|
96
97
|
export const Disabled: Story = {
|
|
98
|
+
parameters: {
|
|
99
|
+
// LC-57 allowlist: this story IS the disabled-dial demo. Stepper has no
|
|
100
|
+
// disabledReason plumbing (component-contract change, tracked separately),
|
|
101
|
+
// so its +/- buttons bare-disable here by design. The warn still fires;
|
|
102
|
+
// the harvester tolerates exactly this code on this story.
|
|
103
|
+
guardrailSpecimen: {
|
|
104
|
+
warns: ["bare-disabled"],
|
|
105
|
+
reason:
|
|
106
|
+
"Disabled-dial specimen: the whole field rests disabled and Stepper has no disabledReason plumbing yet.",
|
|
107
|
+
} satisfies GuardrailSpecimenParameter,
|
|
108
|
+
},
|
|
97
109
|
args: {
|
|
98
110
|
label: "Disabled Field",
|
|
99
111
|
disabled: true,
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import { useForm } from "@tanstack/react-form";
|
|
2
|
-
import { act, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
-
import { YStack } from "tamagui";
|
|
2
|
+
import { act, fireEvent, render, waitFor } from "@testing-library/react";
|
|
3
|
+
import { TamaguiProvider, YStack, createTamagui } from "tamagui";
|
|
4
4
|
import { describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
6
|
+
import {
|
|
7
|
+
createThemesBuilder,
|
|
8
|
+
defaultAccentTheme,
|
|
9
|
+
defaultBaseTheme,
|
|
10
|
+
defaultBuilderOptions,
|
|
11
|
+
} from "@multiplatform.one/theme";
|
|
12
|
+
import { configWithoutAnimations } from "@tamagui/config";
|
|
13
|
+
import { animationsCSS } from "@tamagui/config/v5-css";
|
|
6
14
|
import { Button } from "../../Button";
|
|
7
15
|
import { Form } from "../../Form";
|
|
8
16
|
import { Switch } from "./index";
|
|
9
17
|
|
|
18
|
+
// House builder themes for the LC-05 mark assertion — created at MODULE
|
|
19
|
+
// scope: tamagui registers a config's theme variables globally at
|
|
20
|
+
// createTamagui time, and a config created after the first render (inside a
|
|
21
|
+
// test body) never resolves its tokens (Progress.spec pattern).
|
|
22
|
+
const houseThemes = createThemesBuilder(
|
|
23
|
+
defaultBaseTheme,
|
|
24
|
+
defaultAccentTheme,
|
|
25
|
+
defaultBuilderOptions,
|
|
26
|
+
).themes() as Record<string, Record<string, string>>;
|
|
27
|
+
const houseConfig = createTamagui({
|
|
28
|
+
...configWithoutAnimations,
|
|
29
|
+
animations: animationsCSS,
|
|
30
|
+
themes: houseThemes as any,
|
|
31
|
+
});
|
|
32
|
+
|
|
10
33
|
describe("Switch", () => {
|
|
11
34
|
describe("without form context", () => {
|
|
12
35
|
it("should render with label and helper text correctly", () => {
|
|
@@ -315,4 +338,29 @@ describe("Switch", () => {
|
|
|
315
338
|
});
|
|
316
339
|
});
|
|
317
340
|
});
|
|
341
|
+
|
|
342
|
+
describe("checked track mark (LC-05 ONE-EMPHASIS / UX-002)", () => {
|
|
343
|
+
// The checked Switch track IS a selection mark, so it resolves the shared
|
|
344
|
+
// accent mark token; the unchecked track stays neutral chrome. Stock test
|
|
345
|
+
// themes carry no $accentBackground, so mount the house builder themes
|
|
346
|
+
// for real resolution.
|
|
347
|
+
it("paints the checked track with the accent mark and the unchecked track neutral", () => {
|
|
348
|
+
const { container } = render(
|
|
349
|
+
<TamaguiProvider config={houseConfig} defaultTheme="light" disableInjectCSS>
|
|
350
|
+
<YStack>
|
|
351
|
+
<Switch label="On" name="on" defaultValue={true} />
|
|
352
|
+
<Switch label="Off" name="off" defaultValue={false} />
|
|
353
|
+
</YStack>
|
|
354
|
+
</TamaguiProvider>,
|
|
355
|
+
);
|
|
356
|
+
const switches = Array.from(container.querySelectorAll("[role='switch']"));
|
|
357
|
+
expect(switches.length).toBe(2);
|
|
358
|
+
const on = switches.find((s) => s.getAttribute("aria-checked") === "true") as HTMLElement;
|
|
359
|
+
const off = switches.find((s) => s.getAttribute("aria-checked") === "false") as HTMLElement;
|
|
360
|
+
expect(on).toBeTruthy();
|
|
361
|
+
expect(off).toBeTruthy();
|
|
362
|
+
expect(on.className).toContain("_bg-accentBackg");
|
|
363
|
+
expect(off.className).not.toContain("_bg-accentBackg");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
318
366
|
});
|
|
@@ -182,6 +182,63 @@ describe("TextArea", () => {
|
|
|
182
182
|
});
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
describe("late-arriving value display (create-mode seeding)", () => {
|
|
186
|
+
// The DOM textarea itself adopts late `defaultValue` updates while
|
|
187
|
+
// pristine (React DOM keeps the value attribute synced), but `charCount`
|
|
188
|
+
// was mount-only state — a value applied AFTER first paint (create-mode
|
|
189
|
+
// engine seeding, setFieldValue) rendered a stale count. Adopting a
|
|
190
|
+
// programmatic value must not emit onChange (LC-14).
|
|
191
|
+
it("form-integrated: a field value set after mount displays with a live count, without onChange", async () => {
|
|
192
|
+
const onChange = vi.fn();
|
|
193
|
+
const onChangeText = vi.fn();
|
|
194
|
+
let formApi: { setFieldValue: (name: "notes", value: string) => void } | undefined;
|
|
195
|
+
const TestComponent = () => {
|
|
196
|
+
const form = useForm({ defaultValues: { notes: "" } });
|
|
197
|
+
formApi = form as unknown as typeof formApi;
|
|
198
|
+
return (
|
|
199
|
+
<Form form={form}>
|
|
200
|
+
<TextArea
|
|
201
|
+
label="Notes"
|
|
202
|
+
name="notes"
|
|
203
|
+
showCount
|
|
204
|
+
onChange={onChange}
|
|
205
|
+
onChangeText={onChangeText}
|
|
206
|
+
/>
|
|
207
|
+
</Form>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
const result = renderWithProviders(<TestComponent />);
|
|
211
|
+
expect(result.findTextElement("0 characters")).toBeDefined();
|
|
212
|
+
|
|
213
|
+
await act(async () => {
|
|
214
|
+
formApi?.setFieldValue("notes", "seeded text");
|
|
215
|
+
});
|
|
216
|
+
await waitFor(() => {
|
|
217
|
+
expect((result.getTextArea() as HTMLTextAreaElement)?.value).toBe("seeded text");
|
|
218
|
+
expect(result.findTextElement("11 characters")).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
221
|
+
expect(onChangeText).not.toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("standalone controlled: a value arriving after mount updates the count, without onChange", async () => {
|
|
225
|
+
const onChangeText = vi.fn();
|
|
226
|
+
const result = renderWithProviders(
|
|
227
|
+
<TextArea label="Notes" value="" showCount onChangeText={onChangeText} />,
|
|
228
|
+
);
|
|
229
|
+
expect(result.findTextElement("0 characters")).toBeDefined();
|
|
230
|
+
|
|
231
|
+
result.rerender(
|
|
232
|
+
<TextArea label="Notes" value="late value" showCount onChangeText={onChangeText} />,
|
|
233
|
+
);
|
|
234
|
+
await waitFor(() => {
|
|
235
|
+
expect((result.getTextArea() as HTMLTextAreaElement)?.value).toBe("late value");
|
|
236
|
+
expect(result.findTextElement("10 characters")).toBeDefined();
|
|
237
|
+
});
|
|
238
|
+
expect(onChangeText).not.toHaveBeenCalled();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
185
242
|
describe("with useForm hook directly", () => {
|
|
186
243
|
it("should handle form submission with useForm", async () => {
|
|
187
244
|
const onSubmitMock = vi.fn();
|
|
@@ -190,7 +190,26 @@ export const AutoResize: Story = {
|
|
|
190
190
|
},
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY): the right column renders the
|
|
195
|
+
* RAW tamagui TextArea beside the house TextArea strictly so the difference
|
|
196
|
+
* stays visible. Raw primitives here are by design and labeled — knob/contrast
|
|
197
|
+
* probes skip this story via `parameters.foreignContrastSpecimen`.
|
|
198
|
+
*/
|
|
193
199
|
export const Comparison: Story = {
|
|
200
|
+
name: "Foreign contrast specimen (raw tamagui)",
|
|
201
|
+
parameters: {
|
|
202
|
+
// Machine-readable exemption for knob/contrast probes and the LC-56 linter arm.
|
|
203
|
+
foreignContrastSpecimen: true,
|
|
204
|
+
docs: {
|
|
205
|
+
description: {
|
|
206
|
+
story:
|
|
207
|
+
"FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY). The right column is the raw " +
|
|
208
|
+
"tamagui TextArea, rendered for contrast only: it ignores house knobs and field " +
|
|
209
|
+
"recipes by design. Probes asserting house rendering must skip this story.",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
194
213
|
render: () => {
|
|
195
214
|
const ComparisonExample = () => {
|
|
196
215
|
const [customVal, setCustomVal] = useState("");
|
|
@@ -171,6 +171,14 @@ export function TextArea({
|
|
|
171
171
|
|
|
172
172
|
const isControlled = value !== undefined;
|
|
173
173
|
|
|
174
|
+
// Count from the live value whenever an upstream source drives the field
|
|
175
|
+
// (controlled `value` prop or TanStack field state): the mount-only
|
|
176
|
+
// charCount state froze counts for values applied after first paint
|
|
177
|
+
// (create-mode seeding, setFieldValue). Uncontrolled standalone keeps the
|
|
178
|
+
// keystroke-fed local state (its defaultValue never changes underneath).
|
|
179
|
+
const countFrom = (textValue: string | undefined) =>
|
|
180
|
+
isControlled || !!(resolvedForm && name) ? (textValue ?? "").length : charCount;
|
|
181
|
+
|
|
174
182
|
const renderTextArea = (
|
|
175
183
|
textValue: string | undefined,
|
|
176
184
|
handleChange?: (text: string) => void,
|
|
@@ -178,7 +186,7 @@ export function TextArea({
|
|
|
178
186
|
hasError?: boolean,
|
|
179
187
|
) => (
|
|
180
188
|
<>
|
|
181
|
-
{showCount && countPosition.startsWith("top") && renderCount(
|
|
189
|
+
{showCount && countPosition.startsWith("top") && renderCount(countFrom(textValue))}
|
|
182
190
|
<YStack position="relative">
|
|
183
191
|
<InputParts.Box
|
|
184
192
|
height="auto"
|
|
@@ -232,7 +240,7 @@ export function TextArea({
|
|
|
232
240
|
</InputParts.Box>
|
|
233
241
|
{renderOverlay?.()}
|
|
234
242
|
</YStack>
|
|
235
|
-
{showCount && countPosition.startsWith("bottom") && renderCount(
|
|
243
|
+
{showCount && countPosition.startsWith("bottom") && renderCount(countFrom(textValue))}
|
|
236
244
|
</>
|
|
237
245
|
);
|
|
238
246
|
|
|
@@ -106,7 +106,7 @@ function AmPmColumn({
|
|
|
106
106
|
backgroundColor={isSelected ? formInputColors.background.focus : "transparent"}
|
|
107
107
|
hoverStyle={{ backgroundColor: formInputColors.background.focus }}
|
|
108
108
|
onPress={() => onChange(period)}
|
|
109
|
-
{...(knobProps.transition ? {
|
|
109
|
+
{...(knobProps.transition ? { transition: knobProps.transition } : undefined)}
|
|
110
110
|
>
|
|
111
111
|
<Text
|
|
112
112
|
fontSize="$3"
|
|
@@ -1,10 +1,52 @@
|
|
|
1
|
-
import { fireEvent, screen } from "@testing-library/react";
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
4
|
+
import {
|
|
5
|
+
createThemesBuilder,
|
|
6
|
+
defaultAccentTheme,
|
|
7
|
+
defaultBaseTheme,
|
|
8
|
+
defaultBuilderOptions,
|
|
9
|
+
} from "@multiplatform.one/theme";
|
|
10
|
+
import { configWithoutAnimations } from "@tamagui/config";
|
|
11
|
+
import { animationsCSS } from "@tamagui/config/v5-css";
|
|
12
|
+
import type { ReactElement } from "react";
|
|
13
|
+
import { TamaguiProvider, createTamagui } from "tamagui";
|
|
4
14
|
import { __resetDevWarnSeen } from "../../shared/devWarn";
|
|
5
15
|
import { Form } from "../../Form";
|
|
6
16
|
import { ToggleGroup } from "./index";
|
|
7
17
|
|
|
18
|
+
// The stock test themes carry no accent ramp, so token resolution for the
|
|
19
|
+
// LC-05 selected mark is invisible under renderWithProviders. Mount the house
|
|
20
|
+
// builder themes (the same ones apps run) to assert the real resolved fill —
|
|
21
|
+
// the pattern Progress.spec.tsx established.
|
|
22
|
+
const houseThemes = createThemesBuilder(
|
|
23
|
+
defaultBaseTheme,
|
|
24
|
+
defaultAccentTheme,
|
|
25
|
+
defaultBuilderOptions,
|
|
26
|
+
).themes() as Record<string, Record<string, string>>;
|
|
27
|
+
|
|
28
|
+
const houseConfig = createTamagui({
|
|
29
|
+
...configWithoutAnimations,
|
|
30
|
+
animations: animationsCSS,
|
|
31
|
+
themes: houseThemes as any,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function renderWithHouseThemes(ui: ReactElement) {
|
|
35
|
+
return render(
|
|
36
|
+
<TamaguiProvider config={houseConfig} defaultTheme="light" disableInjectCSS>
|
|
37
|
+
{ui}
|
|
38
|
+
</TamaguiProvider>,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* First three numeric groups (h, s, l) of an hsla() string — the digit prefix
|
|
44
|
+
* Tamagui embeds in the atomic class it generates for a concrete color value.
|
|
45
|
+
*/
|
|
46
|
+
function hslDigits(hsla: string): string {
|
|
47
|
+
return (hsla.match(/\d+/g) ?? []).slice(0, 3).join("");
|
|
48
|
+
}
|
|
49
|
+
|
|
8
50
|
const options = [
|
|
9
51
|
{ label: "Left", value: "left" },
|
|
10
52
|
{ label: "Center", value: "center" },
|
|
@@ -162,4 +204,25 @@ describe("ToggleGroup", () => {
|
|
|
162
204
|
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("bare-disabled"));
|
|
163
205
|
});
|
|
164
206
|
});
|
|
207
|
+
|
|
208
|
+
describe("selected mark (LC-05 ONE-EMPHASIS / D-01)", () => {
|
|
209
|
+
it("fills the selected segment with the accent-tinted surface, never the neutral tier", () => {
|
|
210
|
+
const { container } = renderWithHouseThemes(
|
|
211
|
+
<ToggleGroup label="Alignment" options={options} defaultValue="left" />,
|
|
212
|
+
);
|
|
213
|
+
// Segmented-control selection strength is pinned at TINTED (D-01
|
|
214
|
+
// ViewSwitcher rider): the accent ramp's surface tier in a light scheme
|
|
215
|
+
// is its palest step (accent11) — the same channel ViewSwitcher takes.
|
|
216
|
+
const tint = houseThemes.light.accent11;
|
|
217
|
+
expect(tint).toBeTruthy();
|
|
218
|
+
const selected = container.querySelector('[data-state="on"]') as HTMLElement;
|
|
219
|
+
const idle = container.querySelector('[data-state="off"]') as HTMLElement;
|
|
220
|
+
expect(selected).toBeTruthy();
|
|
221
|
+
expect(idle).toBeTruthy();
|
|
222
|
+
expect(selected.className).toContain(`_bg-hsla${hslDigits(tint)}`);
|
|
223
|
+
expect(idle.className).not.toContain(`_bg-hsla${hslDigits(tint)}`);
|
|
224
|
+
// Ink marks selection nowhere: the old neutral $color8 fill is gone.
|
|
225
|
+
expect(selected.className).not.toContain("_bg-color8");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
165
228
|
});
|
|
@@ -252,7 +252,26 @@ const alignmentOptions = [
|
|
|
252
252
|
{ label: "Right", value: "right" },
|
|
253
253
|
];
|
|
254
254
|
|
|
255
|
+
/**
|
|
256
|
+
* FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY): the right column renders the
|
|
257
|
+
* RAW tamagui ToggleGroup beside the house ToggleGroup strictly so the
|
|
258
|
+
* difference stays visible. Raw primitives here are by design and labeled —
|
|
259
|
+
* knob/contrast probes skip this story via `parameters.foreignContrastSpecimen`.
|
|
260
|
+
*/
|
|
255
261
|
export const Comparison: Story = {
|
|
262
|
+
name: "Foreign contrast specimen (raw tamagui)",
|
|
263
|
+
parameters: {
|
|
264
|
+
// Machine-readable exemption for knob/contrast probes and the LC-56 linter arm.
|
|
265
|
+
foreignContrastSpecimen: true,
|
|
266
|
+
docs: {
|
|
267
|
+
description: {
|
|
268
|
+
story:
|
|
269
|
+
"FOREIGN CONTRAST SPECIMEN (LC-56 STORY-HONESTY). The right column is the raw " +
|
|
270
|
+
"tamagui ToggleGroup, rendered for contrast only: it ignores house knobs and " +
|
|
271
|
+
"control recipes by design. Probes asserting house rendering must skip this story.",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
256
275
|
render: () => {
|
|
257
276
|
const ComparisonExample = () => {
|
|
258
277
|
const [customVal, setCustomVal] = useState("left");
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ensureFocusVisibleRing,
|
|
5
5
|
ensureKeyboardModalityTracking,
|
|
6
6
|
keyboardFocusRingProps,
|
|
7
|
+
useAccentTintedSurface,
|
|
7
8
|
useReadableTextOn,
|
|
8
9
|
wasKeyboardFocus,
|
|
9
10
|
} from "@multiplatform.one/theme";
|
|
@@ -30,6 +31,11 @@ import {
|
|
|
30
31
|
useResolvedValidators,
|
|
31
32
|
} from "../../shared/utils";
|
|
32
33
|
|
|
34
|
+
// NOTE: no `active` variant here — the selected fill is runtime-resolved
|
|
35
|
+
// (accent-tinted surface, LC-05/D-01), so it rides direct props re-applied
|
|
36
|
+
// through hover/press. A static variant would expand at its key position and
|
|
37
|
+
// paint its stale value OVER those direct props (the exact drift the LC-40
|
|
38
|
+
// wash comment below records for the disabled tier).
|
|
33
39
|
const ToggleItemFrame = styled(TamaguiToggleGroup.Item, {
|
|
34
40
|
cursor: "pointer",
|
|
35
41
|
userSelect: "none",
|
|
@@ -56,20 +62,6 @@ const ToggleItemFrame = styled(TamaguiToggleGroup.Item, {
|
|
|
56
62
|
},
|
|
57
63
|
},
|
|
58
64
|
},
|
|
59
|
-
active: {
|
|
60
|
-
true: {
|
|
61
|
-
backgroundColor: formButtonColors.border.active,
|
|
62
|
-
borderColor: formButtonColors.border.active,
|
|
63
|
-
hoverStyle: {
|
|
64
|
-
backgroundColor: formButtonColors.border.active,
|
|
65
|
-
borderColor: formButtonColors.border.active,
|
|
66
|
-
},
|
|
67
|
-
pressStyle: {
|
|
68
|
-
backgroundColor: formButtonColors.border.active,
|
|
69
|
-
borderColor: formButtonColors.border.active,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
65
|
} as const,
|
|
74
66
|
});
|
|
75
67
|
|
|
@@ -244,11 +236,19 @@ export function ToggleGroup({
|
|
|
244
236
|
id: idProp,
|
|
245
237
|
compact,
|
|
246
238
|
});
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
239
|
+
// LC-05 / D-01: selection is ONE accent language across every choice-control
|
|
240
|
+
// family — this selected segment filled with the neutral `$color8` tier, the
|
|
241
|
+
// catalog's last ink/neutral-marked selection surface. Segmented-control
|
|
242
|
+
// STRENGTH is pinned at TINTED (the D-01 ViewSwitcher rider — not the solid
|
|
243
|
+
// an accent CTA takes), so the fill is the accent ramp's surface tier
|
|
244
|
+
// mirrored by scheme, the same channel ViewSwitcher resolves. When the
|
|
245
|
+
// accent ramp is absent (tint sub-themes) the neutral tier stays as the
|
|
246
|
+
// declared fallback so selection never goes invisible.
|
|
247
|
+
// UX-018 / Axiom 15 kept: the label is the paper/ink anchor luminance-picked
|
|
248
|
+
// for the ACTUAL fill, never an assumed scheme token.
|
|
249
|
+
const selectedTint = useAccentTintedSurface();
|
|
250
|
+
const selectedFill = selectedTint ?? formButtonColors.border.active;
|
|
251
|
+
const onSelectedLabel = useReadableTextOn(selectedFill);
|
|
252
252
|
// Chromium drops keyboard modality when tamagui's group redirects focus to
|
|
253
253
|
// an item, so :focus-visible/focusVisibleStyle can miss — paint the ring
|
|
254
254
|
// manually on keyboard-origin focus (see theme keyboardFocusRing).
|
|
@@ -344,9 +344,13 @@ export function ToggleGroup({
|
|
|
344
344
|
const itemDisabled = Boolean(disabled || option.disabled);
|
|
345
345
|
const itemDescribedBy = option.disabled ? itemReasonIdFor(option.disabledReason) : undefined;
|
|
346
346
|
const isTabStop = index === tabStopIndex;
|
|
347
|
+
// The accent-tinted selected fill (border rides the fill, as the
|
|
348
|
+
// neutral chip's did) — re-applied through hover/press below so the
|
|
349
|
+
// neutral state layer never paints over the selection mark (the LC-05
|
|
350
|
+
// rule the checked Switch track and ViewSwitcher chip already follow).
|
|
347
351
|
const activeColors = {
|
|
348
|
-
backgroundColor:
|
|
349
|
-
borderColor:
|
|
352
|
+
backgroundColor: selectedFill,
|
|
353
|
+
borderColor: selectedFill,
|
|
350
354
|
};
|
|
351
355
|
|
|
352
356
|
return (
|
|
@@ -365,18 +369,16 @@ export function ToggleGroup({
|
|
|
365
369
|
paddingHorizontal="$2"
|
|
366
370
|
borderWidth={knobProps.borderRadius.borderWidth}
|
|
367
371
|
outlined={knobProps.outlined}
|
|
368
|
-
|
|
372
|
+
transition={knobProps.transition}
|
|
369
373
|
disabled={itemDisabled}
|
|
370
374
|
{...(itemDescribedBy ? { "aria-describedby": itemDescribedBy } : undefined)}
|
|
371
375
|
// LC-40 DISABLED-VISIBLE (was DG-ST-03 hand-roll): segments are
|
|
372
|
-
// text-bearing chrome — a disabled segment drops the live
|
|
373
|
-
// fill entirely
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
// option stays visible (Axiom 6), not aria-only.
|
|
376
|
+
// text-bearing chrome — a disabled segment drops the live selected
|
|
377
|
+
// fill entirely, so the wash owns the chrome and the label pins to
|
|
378
|
+
// the lowest AA tier below. A SELECTED disabled segment takes the
|
|
379
|
+
// recipe's stronger selected tier so the chosen option stays
|
|
380
|
+
// visible (Axiom 6), not aria-only.
|
|
378
381
|
{...(selected && !itemDisabled ? activeColors : undefined)}
|
|
379
|
-
{...(selected && !itemDisabled ? { active: true, activeStyle: activeColors } : undefined)}
|
|
380
382
|
{...(itemDisabled
|
|
381
383
|
? selected
|
|
382
384
|
? disabledState.selectedSurfaceKnobProps
|
|
@@ -15,7 +15,9 @@ const manyOptions = Array.from({ length: 60 }, (_, i) => ({
|
|
|
15
15
|
label: `Option ${i + 1}`,
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// Six entries: short enough that the floating list never scrolls (the story's
|
|
19
|
+
// point) while clearing the ≤5 `select-too-few-options` guardrail (DG-CTRL-01).
|
|
20
|
+
const fewOptions = Array.from({ length: 6 }, (_, i) => ({
|
|
19
21
|
value: `option-${i + 1}`,
|
|
20
22
|
label: `Option ${i + 1}`,
|
|
21
23
|
}));
|
|
@@ -45,7 +47,7 @@ export const ShortSelect: Story = {
|
|
|
45
47
|
<YStack maxWidth={320}>
|
|
46
48
|
<Select
|
|
47
49
|
id="fls-short"
|
|
48
|
-
label="
|
|
50
|
+
label="Six options"
|
|
49
51
|
options={fewOptions}
|
|
50
52
|
helperText="No arrows expected"
|
|
51
53
|
/>
|