@astryxdesign/core 0.4.2 → 0.4.3-canary.00bed29
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/CHANGELOG.md +45 -0
- package/README.md +79 -45
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +3 -19
- package/dist/Banner/Banner.d.ts +9 -2
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +84 -33
- package/dist/Banner/index.d.ts +14 -12
- package/dist/Banner/index.d.ts.map +1 -1
- package/dist/Banner/index.js +10 -8
- package/dist/BottomSheet/BottomSheet.d.ts +66 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheet.js +392 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +60 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetPanel.js +398 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +61 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.js +410 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +21 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.js +13 -0
- package/dist/BottomSheet/index.d.ts +11 -0
- package/dist/BottomSheet/index.d.ts.map +1 -0
- package/dist/BottomSheet/index.js +12 -0
- package/dist/BottomSheet/snapOffsets.d.ts +72 -0
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -0
- package/dist/BottomSheet/snapOffsets.js +130 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts +35 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +1 -0
- package/dist/BottomSheet/useMobileKeyboard.js +373 -0
- package/dist/BottomSheet/useSheetGestures.d.ts +180 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -0
- package/dist/BottomSheet/useSheetGestures.js +942 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +3 -4
- package/dist/Chat/ChatTokenizedText.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts +38 -4
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +98 -31
- package/dist/ComplexSelector/index.d.ts +2 -2
- package/dist/ComplexSelector/index.d.ts.map +1 -1
- package/dist/ComplexSelector/index.js +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +9 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +31 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +9 -3
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +55 -12
- package/dist/MobileNav/MobileNav.d.ts +12 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +102 -30
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +21 -7
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +9 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +4 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +2 -4
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +14 -3
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +5 -1
- package/dist/Table/columnUtils.d.ts.map +1 -1
- package/dist/Table/columnUtils.js +4 -1
- package/dist/TextArea/TextArea.d.ts +6 -3
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +17 -6
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +8 -1
- package/dist/Tokenizer/Tokenizer.js +11 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +4 -1
- package/dist/TreeList/TreeList.js +2 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +5 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +10 -1
- package/dist/astryx.css +25 -2
- package/dist/hooks/index.d.ts +8 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +8 -1
- package/dist/hooks/useFocusTrap.d.ts +0 -11
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +1 -11
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +30 -7
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +36 -47
- package/dist/theme/expandColorScale.d.ts +17 -2
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +63 -33
- package/dist/theme/mergeComponents.d.ts +20 -0
- package/dist/theme/mergeComponents.d.ts.map +1 -0
- package/dist/theme/mergeComponents.js +56 -0
- package/dist/theme/onMediaTokens.d.ts +6 -1
- package/dist/theme/onMediaTokens.d.ts.map +1 -1
- package/dist/theme/onMediaTokens.js +11 -3
- package/dist/utils/characters.d.ts +27 -0
- package/dist/utils/characters.d.ts.map +1 -0
- package/dist/utils/characters.js +83 -0
- package/dist/utils/ime.d.ts +5 -0
- package/dist/utils/ime.d.ts.map +1 -0
- package/dist/utils/ime.js +63 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/locales/en.json +48 -0
- package/locales/pseudo.json +36 -0
- package/package.json +9 -8
- package/src/Avatar/Avatar.test.tsx +6 -1
- package/src/Avatar/Avatar.tsx +3 -21
- package/src/Banner/Banner.doc.mjs +12 -10
- package/src/Banner/Banner.test.tsx +108 -0
- package/src/Banner/Banner.tsx +124 -38
- package/src/Banner/index.ts +15 -13
- package/src/BottomSheet/BottomSheet.doc.mjs +266 -0
- package/src/BottomSheet/BottomSheet.test.tsx +2114 -0
- package/src/BottomSheet/BottomSheet.tsx +545 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +240 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +547 -0
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +219 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +850 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +628 -0
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +51 -0
- package/src/BottomSheet/index.ts +15 -0
- package/src/BottomSheet/snapOffsets.test.ts +187 -0
- package/src/BottomSheet/snapOffsets.ts +159 -0
- package/src/BottomSheet/useMobileKeyboard.ts +499 -0
- package/src/BottomSheet/useSheetGestures.test.ts +1014 -0
- package/src/BottomSheet/useSheetGestures.ts +1407 -0
- package/src/Chat/ChatComposerInput.tsx +3 -4
- package/src/Chat/ChatTokenizedText.tsx +1 -1
- package/src/ComplexSelector/ComplexSelector.doc.mjs +55 -6
- package/src/ComplexSelector/ComplexSelector.test.tsx +197 -6
- package/src/ComplexSelector/ComplexSelector.tsx +156 -28
- package/src/ComplexSelector/index.ts +3 -1
- package/src/ContextMenu/ContextMenu.tsx +4 -2
- package/src/DateInput/DateInput.test.tsx +21 -0
- package/src/DateInput/DateInput.tsx +9 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +4 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +137 -0
- package/src/DateTimeInput/DateTimeInput.tsx +39 -2
- package/src/Dialog/Dialog.tsx +2 -1
- package/src/FileInput/FileInput.test.tsx +53 -9
- package/src/Lightbox/Lightbox.test.tsx +49 -28
- package/src/Lightbox/Lightbox.tsx +7 -3
- package/src/Markdown/parser.test.ts +53 -0
- package/src/Markdown/parser.ts +53 -12
- package/src/MobileNav/MobileNav.tsx +128 -37
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +426 -0
- package/src/MobileNav/MobileNavCloseTiming.test.ts +58 -0
- package/src/MobileNav/MobileNavCloseVisibility.test.tsx +172 -0
- package/src/MultiSelector/MultiSelector.test.tsx +195 -51
- package/src/MultiSelector/MultiSelector.tsx +22 -8
- package/src/NumberInput/NumberInput.test.tsx +18 -0
- package/src/NumberInput/NumberInput.tsx +9 -1
- package/src/PowerSearch/PowerSearch.test.tsx +48 -3
- package/src/PowerSearch/PowerSearch.tsx +4 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/PowerSearch/formatFilterValue.test.ts +22 -0
- package/src/PowerSearch/formatFilterValue.ts +2 -4
- package/src/Selector/Selector.test.tsx +85 -26
- package/src/Selector/Selector.tsx +13 -4
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +7 -0
- package/src/Table/Table.test.tsx +6 -0
- package/src/Table/columnUtils.ts +3 -1
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +72 -0
- package/src/TextArea/TextArea.tsx +26 -8
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +8 -0
- package/src/Tokenizer/Tokenizer.test.tsx +50 -32
- package/src/Tokenizer/Tokenizer.tsx +5 -5
- package/src/Tooltip/useTooltip.tsx +4 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/TreeList/TreeList.tsx +1 -1
- package/src/Typeahead/BaseTypeahead.tsx +10 -3
- package/src/Typeahead/Typeahead.test.tsx +116 -7
- package/src/Typeahead/Typeahead.tsx +10 -1
- package/src/__tests__/stubMatchMedia.test.ts +79 -0
- package/src/__tests__/stubMatchMedia.ts +95 -0
- package/src/hooks/index.ts +9 -5
- package/src/hooks/useFocusTrap.ts +1 -14
- package/src/hooks/useInputStatusIcon.test.tsx +19 -0
- package/src/hooks/useInputStatusIcon.tsx +7 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +127 -0
- package/src/theme/defineTheme.ts +70 -55
- package/src/theme/expandColorScale.test.ts +138 -0
- package/src/theme/expandColorScale.ts +89 -44
- package/src/theme/extensibleAxes.test.ts +365 -0
- package/src/theme/mergeComponents.ts +59 -0
- package/src/theme/onMediaTokens.ts +9 -2
- package/src/utils/characters.test.ts +141 -0
- package/src/utils/characters.ts +88 -0
- package/src/utils/ime.test.ts +32 -0
- package/src/utils/ime.ts +69 -0
- package/src/utils/index.ts +4 -0
- package/dist/astryx.umd.js +0 -147
- package/dist/astryx.umd.js.map +0 -7
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file ComplexSelector.tsx
|
|
7
|
-
* @input Uses React, StyleX, Field, usePopover
|
|
8
|
-
* @output Exports
|
|
7
|
+
* @input Uses React, StyleX, Field, Icon slots, Layer positioning, and usePopover
|
|
8
|
+
* @output Exports a rich-selector shell with exact token-sized input and ghost triggers, plus an imperative open/close handle
|
|
9
9
|
* @position Core implementation; consumed by index.ts
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update:
|
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
* - /packages/core/src/ComplexSelector/ComplexSelector.test.tsx
|
|
14
14
|
* - /packages/core/src/ComplexSelector/index.ts
|
|
15
15
|
* - /apps/storybook/stories/ComplexSelector.stories.tsx
|
|
16
|
+
* - /packages/cli/assets/templates/blocks/components/ComplexSelector/ (showcase blocks)
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import React, {
|
|
19
20
|
useCallback,
|
|
20
21
|
useId,
|
|
22
|
+
useImperativeHandle,
|
|
21
23
|
useOptimistic,
|
|
24
|
+
useRef,
|
|
22
25
|
useTransition,
|
|
23
26
|
type ReactNode,
|
|
24
27
|
} from 'react';
|
|
@@ -26,22 +29,24 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
26
29
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
27
30
|
import type {BaseProps} from '../BaseProps';
|
|
28
31
|
import {Field, inputWrapperStyles, type FieldStatusVariant} from '../Field';
|
|
29
|
-
import {Icon} from '../Icon';
|
|
32
|
+
import {Icon, renderIconSlot, type IconType} from '../Icon';
|
|
30
33
|
import {Spinner} from '../Spinner';
|
|
31
34
|
import {useTranslator} from '../i18n';
|
|
32
35
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
36
|
+
import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
|
|
33
37
|
import {usePopover} from '../Popover/usePopover';
|
|
34
38
|
import {
|
|
35
39
|
colorVars,
|
|
36
40
|
durationVars,
|
|
37
41
|
easeVars,
|
|
42
|
+
fontWeightVars,
|
|
38
43
|
radiusVars,
|
|
39
44
|
sizeVars,
|
|
40
45
|
spacingVars,
|
|
41
46
|
typographyVars,
|
|
42
47
|
typeScaleVars,
|
|
43
48
|
} from '../theme/tokens.stylex';
|
|
44
|
-
import {mergeProps} from '../utils';
|
|
49
|
+
import {isRenderable, mergeProps} from '../utils';
|
|
45
50
|
import {composeEventHandlers} from '../utils/composeEventHandlers';
|
|
46
51
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
47
52
|
import type {SizeValue} from '../utils/types';
|
|
@@ -99,6 +104,39 @@ const styles = stylex.create({
|
|
|
99
104
|
placeholder: {
|
|
100
105
|
color: colorVars['--color-text-secondary'],
|
|
101
106
|
},
|
|
107
|
+
triggerGhost: {
|
|
108
|
+
width: 'auto',
|
|
109
|
+
borderWidth: 0,
|
|
110
|
+
backgroundColor: 'transparent',
|
|
111
|
+
backgroundImage: {
|
|
112
|
+
default: null,
|
|
113
|
+
':hover': {
|
|
114
|
+
'@media (hover: hover)': `linear-gradient(${colorVars['--color-overlay-hover']}, ${colorVars['--color-overlay-hover']})`,
|
|
115
|
+
},
|
|
116
|
+
':active': `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`,
|
|
117
|
+
},
|
|
118
|
+
boxShadow: {
|
|
119
|
+
default: 'none',
|
|
120
|
+
':hover:not(:focus-within)': {
|
|
121
|
+
'@media (hover: hover)': 'none',
|
|
122
|
+
},
|
|
123
|
+
':focus-within': 'none',
|
|
124
|
+
},
|
|
125
|
+
fontWeight: fontWeightVars['--font-weight-medium'],
|
|
126
|
+
transitionProperty:
|
|
127
|
+
'background-image, background-color, color, opacity, transform',
|
|
128
|
+
transform: {
|
|
129
|
+
default: 'scale(1)',
|
|
130
|
+
':active': 'scale(0.98)',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
triggerGhostDisabled: {
|
|
134
|
+
backgroundImage: 'none',
|
|
135
|
+
transform: {
|
|
136
|
+
default: 'none',
|
|
137
|
+
':active': 'none',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
102
140
|
// Only what Icon does not already provide: `sm` gives the 16px box and
|
|
103
141
|
// `color="secondary"` the color, but the glyph still must not shrink inside
|
|
104
142
|
// the flex trigger.
|
|
@@ -129,19 +167,21 @@ const styles = stylex.create({
|
|
|
129
167
|
padding: spacingVars['--spacing-3'],
|
|
130
168
|
},
|
|
131
169
|
sm: {
|
|
132
|
-
|
|
170
|
+
height: sizeVars['--size-element-sm'],
|
|
133
171
|
},
|
|
134
172
|
md: {
|
|
135
|
-
|
|
173
|
+
height: sizeVars['--size-element-md'],
|
|
136
174
|
},
|
|
137
175
|
lg: {
|
|
138
|
-
|
|
176
|
+
height: sizeVars['--size-element-lg'],
|
|
139
177
|
},
|
|
140
178
|
disabled: {
|
|
141
179
|
cursor: 'not-allowed',
|
|
142
180
|
},
|
|
143
181
|
});
|
|
144
182
|
|
|
183
|
+
export type ComplexSelectorVariant = 'input' | 'ghost';
|
|
184
|
+
|
|
145
185
|
export type ComplexSelectorSize = 'sm' | 'md' | 'lg';
|
|
146
186
|
|
|
147
187
|
export interface ComplexSelectorRenderState {
|
|
@@ -155,6 +195,25 @@ export interface ComplexSelectorRenderState {
|
|
|
155
195
|
contentId: string;
|
|
156
196
|
}
|
|
157
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Imperative control surface for ComplexSelector, accessed via the `handleRef`
|
|
200
|
+
* prop. Methods drive the same popover machinery as the built-in trigger, so
|
|
201
|
+
* they respect focus restoration, light dismiss, and Escape. Prefer these
|
|
202
|
+
* callbacks over mirroring open state in the parent — the selector owns its
|
|
203
|
+
* visibility, and imperative calls avoid the focus-management pitfalls of
|
|
204
|
+
* syncing an external `isOpen` prop.
|
|
205
|
+
*/
|
|
206
|
+
export interface ComplexSelectorHandle {
|
|
207
|
+
/** Open the selector surface. No-op when disabled or already open. */
|
|
208
|
+
open(): void;
|
|
209
|
+
/** Close the selector surface. Restores focus to the trigger. */
|
|
210
|
+
close(): void;
|
|
211
|
+
/** Toggle the selector surface open or closed. */
|
|
212
|
+
toggle(): void;
|
|
213
|
+
/** Whether the selector surface is currently open. Reads live state. */
|
|
214
|
+
isOpen(): boolean;
|
|
215
|
+
}
|
|
216
|
+
|
|
158
217
|
export interface ComplexSelectorStatus {
|
|
159
218
|
type: 'warning' | 'error' | 'success';
|
|
160
219
|
message?: string;
|
|
@@ -203,10 +262,22 @@ export interface ComplexSelectorProps<Value> extends Omit<
|
|
|
203
262
|
labelTooltip?: string;
|
|
204
263
|
/** Trigger and field size. */
|
|
205
264
|
size?: ComplexSelectorSize;
|
|
265
|
+
/** Visual trigger style. Ghost matches toolbar buttons. */
|
|
266
|
+
variant?: ComplexSelectorVariant;
|
|
267
|
+
/** Icon displayed at the start of the trigger. */
|
|
268
|
+
startIcon?: ReactNode | IconType;
|
|
206
269
|
/** Width of the field. */
|
|
207
270
|
width?: SizeValue;
|
|
208
271
|
/** Popup placement. */
|
|
209
|
-
placement?:
|
|
272
|
+
placement?: LayerPlacement;
|
|
273
|
+
/** Popup alignment along the placement axis. */
|
|
274
|
+
alignment?: LayerAlignment;
|
|
275
|
+
/**
|
|
276
|
+
* Imperative handle for programmatic open/close control. Exposes open,
|
|
277
|
+
* close, toggle, and the isOpen query. Use this instead of mirroring open
|
|
278
|
+
* state in the parent — the selector owns its visibility.
|
|
279
|
+
*/
|
|
280
|
+
handleRef?: React.Ref<ComplexSelectorHandle>;
|
|
210
281
|
/** StyleX styles for the popup content container. */
|
|
211
282
|
contentXstyle?: StyleXStyles;
|
|
212
283
|
/** Test ID for the trigger container. */
|
|
@@ -258,8 +329,12 @@ export function ComplexSelector<Value>({
|
|
|
258
329
|
statusVariant = 'attached',
|
|
259
330
|
labelTooltip,
|
|
260
331
|
size = 'md',
|
|
332
|
+
variant = 'input',
|
|
333
|
+
startIcon,
|
|
261
334
|
width,
|
|
262
335
|
placement = 'below',
|
|
336
|
+
alignment = 'start',
|
|
337
|
+
handleRef,
|
|
263
338
|
contentXstyle,
|
|
264
339
|
xstyle,
|
|
265
340
|
className,
|
|
@@ -270,6 +345,10 @@ export function ComplexSelector<Value>({
|
|
|
270
345
|
}: ComplexSelectorProps<Value>) {
|
|
271
346
|
const t = useTranslator();
|
|
272
347
|
const placeholder = placeholderFromProps ?? t('@astryx.selector.placeholder');
|
|
348
|
+
const effectiveStatusVariant =
|
|
349
|
+
variant === 'ghost' && statusVariant === 'attached'
|
|
350
|
+
? 'detached'
|
|
351
|
+
: statusVariant;
|
|
273
352
|
|
|
274
353
|
const triggerId = useId();
|
|
275
354
|
const labelId = useId();
|
|
@@ -284,24 +363,67 @@ export function ComplexSelector<Value>({
|
|
|
284
363
|
.filter((id): id is string => id != null)
|
|
285
364
|
.join(' ') || undefined;
|
|
286
365
|
|
|
366
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
367
|
+
const lastHideTimeRef = useRef(0);
|
|
368
|
+
|
|
287
369
|
const [isPending, startTransition] = useTransition();
|
|
288
370
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
289
371
|
const isBusy = isLoading || isPending;
|
|
290
372
|
|
|
373
|
+
const handlePopoverHide = useCallback(() => {
|
|
374
|
+
lastHideTimeRef.current = Date.now();
|
|
375
|
+
triggerRef.current?.focus();
|
|
376
|
+
}, []);
|
|
377
|
+
|
|
291
378
|
const popover = usePopover({
|
|
292
379
|
dialogLabel: label,
|
|
293
380
|
hasCloseButton: false,
|
|
294
381
|
hasAutoFocus: true,
|
|
295
|
-
// The popup's theme target belongs on the SURFACE — the element painting
|
|
296
|
-
// background, radius and elevation — which `usePopover` owns. Rendered on
|
|
297
|
-
// the content box below it, a theme's background or radius rule paints the
|
|
298
|
-
// wrong box.
|
|
299
382
|
surfaceTarget: 'complex-selector-popup',
|
|
300
|
-
onHide:
|
|
301
|
-
document.getElementById(triggerId)?.focus();
|
|
302
|
-
},
|
|
383
|
+
onHide: handlePopoverHide,
|
|
303
384
|
});
|
|
304
385
|
|
|
386
|
+
const isOpen = popover.isOpen;
|
|
387
|
+
|
|
388
|
+
const handleTriggerClick = useCallback(() => {
|
|
389
|
+
if (isDisabled || Date.now() - lastHideTimeRef.current < 50) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (popover.isOpen) {
|
|
393
|
+
popover.hide();
|
|
394
|
+
} else {
|
|
395
|
+
popover.show();
|
|
396
|
+
}
|
|
397
|
+
}, [isDisabled, popover]);
|
|
398
|
+
|
|
399
|
+
const close = useCallback(() => {
|
|
400
|
+
popover.hide();
|
|
401
|
+
}, [popover]);
|
|
402
|
+
|
|
403
|
+
useImperativeHandle(
|
|
404
|
+
handleRef,
|
|
405
|
+
() => ({
|
|
406
|
+
open: () => {
|
|
407
|
+
if (!isDisabled) {
|
|
408
|
+
popover.show();
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
close: () => popover.hide(),
|
|
412
|
+
toggle: () => {
|
|
413
|
+
if (isDisabled) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (popover.isOpen) {
|
|
417
|
+
popover.hide();
|
|
418
|
+
} else {
|
|
419
|
+
popover.show();
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
isOpen: () => popover.isOpen,
|
|
423
|
+
}),
|
|
424
|
+
[isDisabled, popover],
|
|
425
|
+
);
|
|
426
|
+
|
|
305
427
|
const commitValue = useCallback(
|
|
306
428
|
(nextValue: Value) => {
|
|
307
429
|
onChange?.(nextValue);
|
|
@@ -317,10 +439,15 @@ export function ComplexSelector<Value>({
|
|
|
317
439
|
|
|
318
440
|
const triggerContent = triggerLabel ?? placeholder;
|
|
319
441
|
|
|
442
|
+
const startIconSlot = renderIconSlot(startIcon, {
|
|
443
|
+
size: 'sm',
|
|
444
|
+
color: 'secondary',
|
|
445
|
+
});
|
|
446
|
+
|
|
320
447
|
const content = (
|
|
321
448
|
<div id={contentId} {...stylex.props(styles.content, contentXstyle)}>
|
|
322
|
-
{children(optimisticValue, commitValue,
|
|
323
|
-
isOpen
|
|
449
|
+
{children(optimisticValue, commitValue, close, {
|
|
450
|
+
isOpen,
|
|
324
451
|
isBusy,
|
|
325
452
|
triggerId,
|
|
326
453
|
contentId,
|
|
@@ -334,13 +461,10 @@ export function ComplexSelector<Value>({
|
|
|
334
461
|
ref={popover.triggerRef}
|
|
335
462
|
data-testid={testId}
|
|
336
463
|
{...props}
|
|
337
|
-
onClick={composeEventHandlers(onClickProp,
|
|
338
|
-
if (!isDisabled) {
|
|
339
|
-
popover.toggle();
|
|
340
|
-
}
|
|
341
|
-
})}
|
|
464
|
+
onClick={composeEventHandlers(onClickProp, handleTriggerClick)}
|
|
342
465
|
{...mergeProps(
|
|
343
466
|
themeProps('complex-selector', {
|
|
467
|
+
variant,
|
|
344
468
|
size,
|
|
345
469
|
status: status?.type ?? null,
|
|
346
470
|
}),
|
|
@@ -353,7 +477,9 @@ export function ComplexSelector<Value>({
|
|
|
353
477
|
// matched a mouse click on the trigger and drew the outline for
|
|
354
478
|
// pointer users too. `focusWithin` here is `:has(:focus-visible)`.
|
|
355
479
|
focusOutlineStyles.focusWithin,
|
|
480
|
+
variant === 'ghost' && styles.triggerGhost,
|
|
356
481
|
isDisabled && inputWrapperStyles.disabled,
|
|
482
|
+
variant === 'ghost' && isDisabled && styles.triggerGhostDisabled,
|
|
357
483
|
isDisabled && styles.disabled,
|
|
358
484
|
triggerLabel == null && styles.placeholder,
|
|
359
485
|
xstyle,
|
|
@@ -361,11 +487,13 @@ export function ComplexSelector<Value>({
|
|
|
361
487
|
className,
|
|
362
488
|
style,
|
|
363
489
|
)}>
|
|
490
|
+
{isRenderable(startIconSlot) && startIconSlot}
|
|
364
491
|
<button
|
|
492
|
+
ref={triggerRef}
|
|
365
493
|
id={triggerId}
|
|
366
494
|
type="button"
|
|
367
495
|
aria-haspopup="dialog"
|
|
368
|
-
aria-expanded={
|
|
496
|
+
aria-expanded={isOpen}
|
|
369
497
|
aria-controls={contentId}
|
|
370
498
|
aria-describedby={ariaDescribedBy}
|
|
371
499
|
aria-labelledby={labelId}
|
|
@@ -374,7 +502,7 @@ export function ComplexSelector<Value>({
|
|
|
374
502
|
aria-busy={isBusy || undefined}
|
|
375
503
|
disabled={isDisabled}
|
|
376
504
|
onKeyDown={event => {
|
|
377
|
-
if (event.key === 'ArrowDown' && !
|
|
505
|
+
if (event.key === 'ArrowDown' && !isOpen && !isDisabled) {
|
|
378
506
|
event.preventDefault();
|
|
379
507
|
popover.show();
|
|
380
508
|
}
|
|
@@ -394,17 +522,17 @@ export function ComplexSelector<Value>({
|
|
|
394
522
|
xstyle={[
|
|
395
523
|
styles.triggerIcon,
|
|
396
524
|
styles.triggerIconRotation,
|
|
397
|
-
|
|
525
|
+
isOpen && styles.triggerIconOpen,
|
|
398
526
|
]}
|
|
399
527
|
{...themeProps('complex-selector-indicator-icon', {
|
|
400
|
-
state:
|
|
528
|
+
state: isOpen ? 'expanded' : 'collapsed',
|
|
401
529
|
})}
|
|
402
530
|
/>
|
|
403
531
|
</div>
|
|
404
532
|
|
|
405
533
|
{popover.render(content, {
|
|
406
534
|
placement,
|
|
407
|
-
alignment
|
|
535
|
+
alignment,
|
|
408
536
|
offset: spacingVars['--spacing-1'],
|
|
409
537
|
xstyle: [styles.popover, layerAnimations[placement]],
|
|
410
538
|
})}
|
|
@@ -431,7 +559,7 @@ export function ComplexSelector<Value>({
|
|
|
431
559
|
}
|
|
432
560
|
: undefined
|
|
433
561
|
}
|
|
434
|
-
statusVariant={
|
|
562
|
+
statusVariant={effectiveStatusVariant}
|
|
435
563
|
labelTooltip={labelTooltip}
|
|
436
564
|
width={width}>
|
|
437
565
|
{selectorContent}
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file index.ts
|
|
7
|
-
* @output Exports ComplexSelector and types
|
|
7
|
+
* @output Exports ComplexSelector and its trigger-size contract types
|
|
8
8
|
* @position Public API entry point
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export {
|
|
12
12
|
ComplexSelector,
|
|
13
|
+
type ComplexSelectorHandle,
|
|
13
14
|
type ComplexSelectorProps,
|
|
14
15
|
type ComplexSelectorRenderState,
|
|
15
16
|
type ComplexSelectorSize,
|
|
16
17
|
type ComplexSelectorStatus,
|
|
18
|
+
type ComplexSelectorVariant,
|
|
17
19
|
} from './ComplexSelector';
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
easeVars,
|
|
64
64
|
shadowVars,
|
|
65
65
|
} from '../theme/tokens.stylex';
|
|
66
|
-
import {mergeProps, mergeRefs} from '../utils';
|
|
66
|
+
import {mergeProps, mergeRefs, isImeKeyEvent} from '../utils';
|
|
67
67
|
import type {BaseProps} from '../BaseProps';
|
|
68
68
|
import type {StyleXStyles} from '../theme/types';
|
|
69
69
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -334,7 +334,9 @@ export function ContextMenu({
|
|
|
334
334
|
if (e.key !== 'Escape') {
|
|
335
335
|
return;
|
|
336
336
|
}
|
|
337
|
-
if (e
|
|
337
|
+
if (isImeKeyEvent(e)) {
|
|
338
|
+
// Ignore Escape that is committing/cancelling an IME composition;
|
|
339
|
+
// see utils/ime.ts for why.
|
|
338
340
|
return;
|
|
339
341
|
}
|
|
340
342
|
e.preventDefault();
|
|
@@ -536,6 +536,27 @@ describe('DateInput', () => {
|
|
|
536
536
|
expect(onChange).toHaveBeenCalledWith('2026-03-15');
|
|
537
537
|
});
|
|
538
538
|
|
|
539
|
+
it('does not commit on a composing Enter (IME)', () => {
|
|
540
|
+
const onChange = vi.fn();
|
|
541
|
+
render(<DateInput label="Date" onChange={onChange} />);
|
|
542
|
+
|
|
543
|
+
const input = screen.getByRole('combobox');
|
|
544
|
+
fireEvent.change(input, {target: {value: '03/15/2026'}});
|
|
545
|
+
onChange.mockClear();
|
|
546
|
+
|
|
547
|
+
// The composing keydown (isComposing / legacy keyCode 229) that commits an
|
|
548
|
+
// IME candidate fires before compositionend; it must not be read as
|
|
549
|
+
// "commit the typed date".
|
|
550
|
+
fireEvent.keyDown(input, {key: 'Enter', isComposing: true});
|
|
551
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
552
|
+
fireEvent.keyDown(input, {key: 'Enter', keyCode: 229});
|
|
553
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
554
|
+
|
|
555
|
+
// A real, non-composing Enter still commits.
|
|
556
|
+
fireEvent.keyDown(input, {key: 'Enter'});
|
|
557
|
+
expect(onChange).toHaveBeenCalledWith('2026-03-15');
|
|
558
|
+
});
|
|
559
|
+
|
|
539
560
|
// --- Arrow-down opens calendar popover ---
|
|
540
561
|
|
|
541
562
|
// Note: Tests involving popover rendering (show/hide with calendar)
|
|
@@ -60,7 +60,7 @@ import {useCalendarConstraints} from '../Calendar/hooks';
|
|
|
60
60
|
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
61
61
|
import {usePopover} from '../Popover';
|
|
62
62
|
import {useTooltip} from '../Tooltip';
|
|
63
|
-
import {getInputARIA, parseDateInput} from '../utils';
|
|
63
|
+
import {getInputARIA, isImeKeyEvent, parseDateInput} from '../utils';
|
|
64
64
|
import {
|
|
65
65
|
plainDateFromISO,
|
|
66
66
|
plainDateToISO,
|
|
@@ -627,6 +627,14 @@ export function DateInput({
|
|
|
627
627
|
// Handle keyboard events on input
|
|
628
628
|
const handleInputKeyDown = useCallback(
|
|
629
629
|
(e: React.KeyboardEvent) => {
|
|
630
|
+
// An in-progress IME composition uses Enter to commit the candidate and
|
|
631
|
+
// Escape to cancel it; that composing keydown fires before
|
|
632
|
+
// compositionend, so without this guard a Korean/Japanese/Chinese user
|
|
633
|
+
// committing a syllable with Enter would instead commit the pending date
|
|
634
|
+
// (or Escape would close the calendar mid-composition). See utils/ime.ts.
|
|
635
|
+
if (isImeKeyEvent(e.nativeEvent)) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
630
638
|
if (e.key === 'Escape' && popover.isOpen) {
|
|
631
639
|
e.preventDefault();
|
|
632
640
|
popover.hide();
|
|
@@ -204,6 +204,8 @@ export const docs = {
|
|
|
204
204
|
className: 'astryx-date-time-input-time-segment',
|
|
205
205
|
visualProps: ['size', 'status'],
|
|
206
206
|
},
|
|
207
|
+
{className: 'astryx-date-time-input-toggle-icon', states: ['state']},
|
|
208
|
+
{className: 'astryx-date-time-input-clock-icon'},
|
|
207
209
|
],
|
|
208
210
|
},
|
|
209
211
|
usage: {
|
|
@@ -508,6 +510,8 @@ export const docsZh = {
|
|
|
508
510
|
className: 'astryx-date-time-input-time-segment',
|
|
509
511
|
visualProps: ['size', 'status'],
|
|
510
512
|
},
|
|
513
|
+
{className: 'astryx-date-time-input-toggle-icon', states: ['state']},
|
|
514
|
+
{className: 'astryx-date-time-input-clock-icon'},
|
|
511
515
|
],
|
|
512
516
|
},
|
|
513
517
|
};
|
|
@@ -15,6 +15,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
15
15
|
import {getButton, queryButton} from '../__tests__/fastRoleQueries';
|
|
16
16
|
import {DateTimeInput} from './DateTimeInput';
|
|
17
17
|
import type {ISODateTimeString} from './DateTimeInput';
|
|
18
|
+
import {Icon} from '../Icon';
|
|
18
19
|
import {defineTheme} from '../theme/defineTheme';
|
|
19
20
|
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
20
21
|
import {InternationalizationProvider} from '../i18n';
|
|
@@ -96,6 +97,48 @@ describe('DateTimeInput', () => {
|
|
|
96
97
|
expect(screen.getByLabelText('Meeting time')).toBeInTheDocument();
|
|
97
98
|
});
|
|
98
99
|
|
|
100
|
+
it('does not commit the date on a composing Enter (IME)', () => {
|
|
101
|
+
const onChange = vi.fn();
|
|
102
|
+
render(<DateTimeInput label="Meeting" onChange={onChange} />);
|
|
103
|
+
const dateInput = screen.getByRole('combobox');
|
|
104
|
+
fireEvent.change(dateInput, {target: {value: '03/15/2026'}});
|
|
105
|
+
onChange.mockClear();
|
|
106
|
+
|
|
107
|
+
// The composing keydown (isComposing / legacy keyCode 229) that commits an
|
|
108
|
+
// IME candidate must not be read as "commit the typed date".
|
|
109
|
+
fireEvent.keyDown(dateInput, {key: 'Enter', isComposing: true});
|
|
110
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
111
|
+
fireEvent.keyDown(dateInput, {key: 'Enter', keyCode: 229});
|
|
112
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
113
|
+
|
|
114
|
+
// A real, non-composing Enter still commits.
|
|
115
|
+
fireEvent.keyDown(dateInput, {key: 'Enter'});
|
|
116
|
+
expect(onChange).toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('does not step the time on a composing ArrowUp (IME)', () => {
|
|
120
|
+
const onChange = vi.fn();
|
|
121
|
+
render(
|
|
122
|
+
<DateTimeInput
|
|
123
|
+
label="Meeting"
|
|
124
|
+
value={'2026-03-15T14:30' as ISODateTimeString}
|
|
125
|
+
onChange={onChange}
|
|
126
|
+
/>,
|
|
127
|
+
);
|
|
128
|
+
const timeInput = screen.getByLabelText('Meeting time');
|
|
129
|
+
|
|
130
|
+
// An IME candidate window navigates with the arrows; a composing ArrowUp
|
|
131
|
+
// must not step the time value.
|
|
132
|
+
fireEvent.keyDown(timeInput, {key: 'ArrowUp', isComposing: true});
|
|
133
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
134
|
+
fireEvent.keyDown(timeInput, {key: 'ArrowUp', keyCode: 229});
|
|
135
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
136
|
+
|
|
137
|
+
// A real, non-composing ArrowUp still steps the time by one minute.
|
|
138
|
+
fireEvent.keyDown(timeInput, {key: 'ArrowUp'});
|
|
139
|
+
expect(onChange).toHaveBeenCalledWith('2026-03-15T14:31');
|
|
140
|
+
});
|
|
141
|
+
|
|
99
142
|
it('displays formatted date in date input when value is provided', () => {
|
|
100
143
|
render(
|
|
101
144
|
<DateTimeInput
|
|
@@ -1063,6 +1106,100 @@ describe('DateTimeInput', () => {
|
|
|
1063
1106
|
expect(css).toContain('block-size: var(--size-element-lg)');
|
|
1064
1107
|
expect(css).toContain('padding-inline: var(--spacing-4)');
|
|
1065
1108
|
});
|
|
1109
|
+
|
|
1110
|
+
it('renders the toggle-icon target on the calendar glyph with open/closed state', async () => {
|
|
1111
|
+
const {container} = render(
|
|
1112
|
+
<DateTimeInput label="Meeting" onChange={() => {}} />,
|
|
1113
|
+
);
|
|
1114
|
+
// The stable target lands on the icon element itself (not the button),
|
|
1115
|
+
// so a theme can restyle just this glyph — mirroring
|
|
1116
|
+
// `date-input-toggle-icon`.
|
|
1117
|
+
const icon = container.querySelector(
|
|
1118
|
+
'.astryx-date-time-input-toggle-icon',
|
|
1119
|
+
);
|
|
1120
|
+
expect(icon).not.toBeNull();
|
|
1121
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
1122
|
+
expect(icon).toHaveAttribute('data-state', 'collapsed');
|
|
1123
|
+
|
|
1124
|
+
fireEvent.click(getButton('Open calendar'));
|
|
1125
|
+
await waitFor(() => {
|
|
1126
|
+
expect(
|
|
1127
|
+
container.querySelector('.astryx-date-time-input-toggle-icon'),
|
|
1128
|
+
).toHaveAttribute('data-state', 'expanded');
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
it('renders the clock-icon target on the leading time glyph', () => {
|
|
1133
|
+
const {container} = render(
|
|
1134
|
+
<DateTimeInput label="Meeting" onChange={() => {}} />,
|
|
1135
|
+
);
|
|
1136
|
+
const icon = container.querySelector(
|
|
1137
|
+
'.astryx-date-time-input-clock-icon',
|
|
1138
|
+
);
|
|
1139
|
+
expect(icon).not.toBeNull();
|
|
1140
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
it('leaves both leading glyphs byte-identical to a plain secondary/sm icon by default', () => {
|
|
1144
|
+
// The targets are purely additive: the stable target class and its
|
|
1145
|
+
// reflected state add nothing to the render until a theme targets them.
|
|
1146
|
+
// Guard that by diffing each glyph's StyleX classes against a standalone
|
|
1147
|
+
// secondary/sm icon, excluding only the additive target/state classes.
|
|
1148
|
+
const {container} = render(
|
|
1149
|
+
<DateTimeInput label="Meeting" onChange={() => {}} />,
|
|
1150
|
+
);
|
|
1151
|
+
const calendarIcon = container.querySelector(
|
|
1152
|
+
'.astryx-date-time-input-toggle-icon',
|
|
1153
|
+
) as HTMLElement;
|
|
1154
|
+
const clockIcon = container.querySelector(
|
|
1155
|
+
'.astryx-date-time-input-clock-icon',
|
|
1156
|
+
) as HTMLElement;
|
|
1157
|
+
|
|
1158
|
+
const {container: refContainer} = render(
|
|
1159
|
+
<Icon icon="calendar" size="sm" color="secondary" />,
|
|
1160
|
+
);
|
|
1161
|
+
const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
|
|
1162
|
+
|
|
1163
|
+
const themeTargetClasses = new Set([
|
|
1164
|
+
'astryx-date-time-input-toggle-icon',
|
|
1165
|
+
'astryx-date-time-input-clock-icon',
|
|
1166
|
+
'collapsed',
|
|
1167
|
+
'expanded',
|
|
1168
|
+
]);
|
|
1169
|
+
const styleClasses = (el: HTMLElement) =>
|
|
1170
|
+
el.className
|
|
1171
|
+
.split(' ')
|
|
1172
|
+
.filter(c => !themeTargetClasses.has(c))
|
|
1173
|
+
.sort();
|
|
1174
|
+
|
|
1175
|
+
expect(styleClasses(calendarIcon)).toEqual(styleClasses(refIcon));
|
|
1176
|
+
expect(styleClasses(clockIcon)).toEqual(styleClasses(refIcon));
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
it('exposes the icon targets so a theme reaches their size and per-state color', () => {
|
|
1180
|
+
// jsdom cannot resolve the @layer cascade, so the generated CSS is what
|
|
1181
|
+
// proves a same-element theme rule can reach these glyphs and win over
|
|
1182
|
+
// the icon's own base size/color.
|
|
1183
|
+
const theme = defineTheme({
|
|
1184
|
+
name: 'date-time-input-icon-targets-test',
|
|
1185
|
+
components: {
|
|
1186
|
+
'date-time-input-toggle-icon': {
|
|
1187
|
+
base: {width: '14px', height: '14px', fontSize: '14px'},
|
|
1188
|
+
'state:expanded': {color: 'var(--color-icon-primary)'},
|
|
1189
|
+
},
|
|
1190
|
+
'date-time-input-clock-icon': {
|
|
1191
|
+
base: {width: '14px', height: '14px', fontSize: '14px'},
|
|
1192
|
+
},
|
|
1193
|
+
},
|
|
1194
|
+
});
|
|
1195
|
+
const css = generateThemeTestCSS(theme);
|
|
1196
|
+
|
|
1197
|
+
expect(css).toContain('.astryx-date-time-input-toggle-icon {');
|
|
1198
|
+
expect(css).toContain('.astryx-date-time-input-toggle-icon.expanded');
|
|
1199
|
+
expect(css).toContain('.astryx-date-time-input-clock-icon {');
|
|
1200
|
+
expect(css).toContain('width: 14px');
|
|
1201
|
+
expect(css).toContain('color: var(--color-icon-primary)');
|
|
1202
|
+
});
|
|
1066
1203
|
});
|
|
1067
1204
|
});
|
|
1068
1205
|
|