@mindlogic-ai/logician-ui 5.0.0-alpha.14 → 5.0.0-alpha.16
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/components/Avatar/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar/Avatar.js +4 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Avatar/Avatar.mjs +4 -1
- package/dist/components/Avatar/Avatar.mjs.map +1 -1
- package/dist/components/Select/Combobox.d.ts +1 -1
- package/dist/components/Select/Combobox.d.ts.map +1 -1
- package/dist/components/Select/Combobox.js +9 -2
- package/dist/components/Select/Combobox.js.map +1 -1
- package/dist/components/Select/Combobox.mjs +10 -3
- package/dist/components/Select/Combobox.mjs.map +1 -1
- package/dist/components/Select/Select.d.ts +1 -1
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.js +14 -2
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/Select.mjs +15 -3
- package/dist/components/Select/Select.mjs.map +1 -1
- package/dist/components/Select/Select.styles.d.ts +96 -0
- package/dist/components/Select/Select.styles.d.ts.map +1 -1
- package/dist/components/Select/Select.styles.js +75 -0
- package/dist/components/Select/Select.styles.js.map +1 -1
- package/dist/components/Select/Select.styles.mjs +74 -1
- package/dist/components/Select/Select.styles.mjs.map +1 -1
- package/dist/translations/Defaults.translations.json.js +10 -1
- package/dist/translations/Defaults.translations.json.js.map +1 -1
- package/dist/translations/Defaults.translations.json.mjs +10 -2
- package/dist/translations/Defaults.translations.json.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Avatar/Avatar.recipe.test.tsx +5 -0
- package/src/components/Avatar/Avatar.tsx +8 -2
- package/src/components/Select/Combobox.tsx +28 -3
- package/src/components/Select/Select.clearTrigger.test.tsx +76 -0
- package/src/components/Select/Select.stories.tsx +81 -0
- package/src/components/Select/Select.styles.ts +81 -0
- package/src/components/Select/Select.tsx +28 -2
- package/src/components/Select/Select.usage.mdx +63 -0
- package/src/translations/Defaults.translations.json +7 -0
|
@@ -35,9 +35,26 @@ const fieldBase = {
|
|
|
35
35
|
fontWeight: 'semibold',
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Room for the × and its divider at the end of the value, while the × shows.
|
|
40
|
+
*
|
|
41
|
+
* The indicator group overlays the value. Chakra keeps the value off the
|
|
42
|
+
* chevron with `maxW: 80%` on a Select (too little in a narrow filter) and a
|
|
43
|
+
* 1.5× height padding on a Combobox, and neither counts the × with its
|
|
44
|
+
* divider. This reserves the whole group instead: padding on both sides, the
|
|
45
|
+
* × (`8`) with its `me` (`1`), the group `gap` (`1`) and the chevron (`1em`),
|
|
46
|
+
* so a long value ends in an ellipsis before the ×.
|
|
47
|
+
*/
|
|
48
|
+
const clearTriggerRoom = (paddingVar) => ({
|
|
49
|
+
'[data-part=control]:has([data-part=clear-trigger]:not([hidden])) > &': {
|
|
50
|
+
pe: `calc(var(${paddingVar}) * 2 + 3.5em)`,
|
|
51
|
+
'& [data-part=value-text]': { maxW: 'none' },
|
|
52
|
+
},
|
|
53
|
+
});
|
|
38
54
|
/** Select trigger — the clickable control. */
|
|
39
55
|
const triggerStyles = {
|
|
40
56
|
...fieldBase,
|
|
57
|
+
...clearTriggerRoom('--select-trigger-padding-x'),
|
|
41
58
|
cursor: 'pointer',
|
|
42
59
|
_open: {
|
|
43
60
|
borderColor: 'primary.main',
|
|
@@ -76,6 +93,7 @@ const ghostTriggerStyles = {
|
|
|
76
93
|
/** Combobox text input — the typeable control. */
|
|
77
94
|
const inputStyles = {
|
|
78
95
|
...fieldBase,
|
|
96
|
+
...clearTriggerRoom('--combobox-input-padding-x'),
|
|
79
97
|
};
|
|
80
98
|
/** Dropdown panel for both Select and Combobox. */
|
|
81
99
|
const contentStyles = {
|
|
@@ -112,6 +130,61 @@ const itemStyles = {
|
|
|
112
130
|
const indicatorStyles = {
|
|
113
131
|
color: 'fg.muted',
|
|
114
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Clear (×) button for both Select and Combobox.
|
|
135
|
+
*
|
|
136
|
+
* Chakra's recipe gives this part a colour and no size, and its default icon
|
|
137
|
+
* is `1em`, so a bare `ClearTrigger` drew a 21px × next to a 14px chevron. The
|
|
138
|
+
* × is the secondary action, so it is smaller (`3.5`, 12px at 14px text) and
|
|
139
|
+
* lighter (`fg.icon`) than the chevron, on an `8` box. `& svg` rather than
|
|
140
|
+
* `_icon` so the size also wins over an icon passed with its own `boxSize`.
|
|
141
|
+
*
|
|
142
|
+
* On a touch screen the hit area widens to 44px, toward the value only: the
|
|
143
|
+
* chevron sits to the right and has to keep opening the list. The height stays
|
|
144
|
+
* the control's own height (`heightVar`), so the area never reaches the field
|
|
145
|
+
* above or below. Every control height is over the 24px KWCAG minimum.
|
|
146
|
+
*/
|
|
147
|
+
const clearTriggerStyles = (heightVar) => ({
|
|
148
|
+
position: 'relative',
|
|
149
|
+
display: 'inline-flex',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
justifyContent: 'center',
|
|
152
|
+
boxSize: '8',
|
|
153
|
+
me: '1',
|
|
154
|
+
color: 'fg.icon',
|
|
155
|
+
cursor: 'pointer',
|
|
156
|
+
_hover: { color: 'fg.default' },
|
|
157
|
+
// The divider marks where opening starts: it sits in the `me` gap, takes no
|
|
158
|
+
// clicks, and everything right of it belongs to the chevron.
|
|
159
|
+
_after: {
|
|
160
|
+
content: '""',
|
|
161
|
+
position: 'absolute',
|
|
162
|
+
insetEnd: '-1',
|
|
163
|
+
top: '50%',
|
|
164
|
+
transform: 'translateY(-50%)',
|
|
165
|
+
h: '4',
|
|
166
|
+
borderEndWidth: '1px',
|
|
167
|
+
borderColor: 'border.subtle',
|
|
168
|
+
pointerEvents: 'none',
|
|
169
|
+
},
|
|
170
|
+
// zag hides the × with the `hidden` attribute while nothing is selected,
|
|
171
|
+
// and the `display` above would otherwise override it.
|
|
172
|
+
_hidden: { display: 'none' },
|
|
173
|
+
'& svg': { width: '3.5', height: '3.5' },
|
|
174
|
+
'@media (pointer: coarse)': {
|
|
175
|
+
'&::before': {
|
|
176
|
+
content: '""',
|
|
177
|
+
position: 'absolute',
|
|
178
|
+
insetEnd: 0,
|
|
179
|
+
top: '50%',
|
|
180
|
+
transform: 'translateY(-50%)',
|
|
181
|
+
width: '44px',
|
|
182
|
+
height: `var(${heightVar})`,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
const selectClearTriggerStyles = clearTriggerStyles('--select-trigger-height');
|
|
187
|
+
const comboboxClearTriggerStyles = clearTriggerStyles('--combobox-input-height');
|
|
115
188
|
|
|
116
|
-
export { contentStyles, ghostContentStyles, ghostTriggerStyles, indicatorStyles, inputStyles, itemStyles, triggerStyles };
|
|
189
|
+
export { comboboxClearTriggerStyles, contentStyles, ghostContentStyles, ghostTriggerStyles, indicatorStyles, inputStyles, itemStyles, selectClearTriggerStyles, triggerStyles };
|
|
117
190
|
//# sourceMappingURL=Select.styles.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.styles.mjs","sources":["../../../src/components/Select/Select.styles.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAAA;;;;;;;;AAQG;AAIH,MAAM,SAAS,GAAG;AAChB,IAAA,eAAe,EAAE,mCAAmC;AACpD,IAAA,eAAe,EAAE,mCAAmC;CACrD;AAED,MAAM,SAAS,GAAG;AAChB,IAAA,GAAG,SAAS;AACZ,IAAA,EAAE,EAAE,YAAY;AAChB,IAAA,KAAK,EAAE,YAAY;AACnB,IAAA,WAAW,EAAE,gBAAgB;AAC7B,IAAA,MAAM,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE;AAClC,IAAA,MAAM,EAAE;AACN,QAAA,WAAW,EAAE,cAAc;AAC3B,QAAA,SAAS,EAAE,6CAA6C;AACzD,KAAA;AACD,IAAA,QAAQ,EAAE;AACR,QAAA,WAAW,EAAE,cAAc;AAC3B,QAAA,MAAM,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE;AACvC,QAAA,MAAM,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE;AACxC,KAAA;AACD,IAAA,SAAS,EAAE;AACT,QAAA,GAAG,eAAe;AAClB,QAAA,MAAM,EAAE,aAAa;AACrB,QAAA,UAAU,EAAE,UAAU;AACvB,KAAA;CACF;AAED;AACO,MAAM,aAAa,GAAG;AAC3B,IAAA,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"Select.styles.mjs","sources":["../../../src/components/Select/Select.styles.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -241,6 +241,13 @@ var close = {
|
|
|
241
241
|
ja: "閉じる",
|
|
242
242
|
es: "Cerrar"
|
|
243
243
|
};
|
|
244
|
+
var clear_selection = {
|
|
245
|
+
ko: "선택 지우기",
|
|
246
|
+
en: "Clear selection",
|
|
247
|
+
zh: "清除选择",
|
|
248
|
+
ja: "選択をクリア",
|
|
249
|
+
es: "Borrar selección"
|
|
250
|
+
};
|
|
244
251
|
var defaultTranslations = {
|
|
245
252
|
get_started: get_started,
|
|
246
253
|
next: next,
|
|
@@ -282,11 +289,13 @@ var defaultTranslations = {
|
|
|
282
289
|
downloading: downloading,
|
|
283
290
|
processing: processing,
|
|
284
291
|
deleting: deleting,
|
|
285
|
-
close: close
|
|
292
|
+
close: close,
|
|
293
|
+
clear_selection: clear_selection
|
|
286
294
|
};
|
|
287
295
|
|
|
288
296
|
exports.anonymous = anonymous;
|
|
289
297
|
exports.clear = clear;
|
|
298
|
+
exports.clear_selection = clear_selection;
|
|
290
299
|
exports.close = close;
|
|
291
300
|
exports.component = component;
|
|
292
301
|
exports.copied = copied;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Defaults.translations.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Defaults.translations.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -237,6 +237,13 @@ var close = {
|
|
|
237
237
|
ja: "閉じる",
|
|
238
238
|
es: "Cerrar"
|
|
239
239
|
};
|
|
240
|
+
var clear_selection = {
|
|
241
|
+
ko: "선택 지우기",
|
|
242
|
+
en: "Clear selection",
|
|
243
|
+
zh: "清除选择",
|
|
244
|
+
ja: "選択をクリア",
|
|
245
|
+
es: "Borrar selección"
|
|
246
|
+
};
|
|
240
247
|
var defaultTranslations = {
|
|
241
248
|
get_started: get_started,
|
|
242
249
|
next: next,
|
|
@@ -278,8 +285,9 @@ var defaultTranslations = {
|
|
|
278
285
|
downloading: downloading,
|
|
279
286
|
processing: processing,
|
|
280
287
|
deleting: deleting,
|
|
281
|
-
close: close
|
|
288
|
+
close: close,
|
|
289
|
+
clear_selection: clear_selection
|
|
282
290
|
};
|
|
283
291
|
|
|
284
|
-
export { anonymous, clear, close, component, copied, copy, copy_error_details, defaultTranslations as default, deleting, download, downloading, error_boundary_default_message, error_boundary_subtitle, error_boundary_title, error_details_copied, error_id, get_started, go_next_page_button, month_range_placeholder, next, page, pagination_items_per_page, pagination_range_text, previous, processing, refresh_page, see_less, see_more, support_help_text, support_information, time, try_again, unknown, upload_your_file, user_id };
|
|
292
|
+
export { anonymous, clear, clear_selection, close, component, copied, copy, copy_error_details, defaultTranslations as default, deleting, download, downloading, error_boundary_default_message, error_boundary_subtitle, error_boundary_title, error_details_copied, error_id, get_started, go_next_page_button, month_range_placeholder, next, page, pagination_items_per_page, pagination_range_text, previous, processing, refresh_page, see_less, see_more, support_help_text, support_information, time, try_again, unknown, upload_your_file, user_id };
|
|
285
293
|
//# sourceMappingURL=Defaults.translations.json.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Defaults.translations.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Defaults.translations.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -133,6 +133,11 @@ describe('Avatar.Group max', () => {
|
|
|
133
133
|
expect(roots).toHaveLength(4);
|
|
134
134
|
const counter = roots[3];
|
|
135
135
|
expect(counter.textContent).toBe('+2');
|
|
136
|
+
// Phrasing content, so `<Avatar.Group as="span">` can sit in a button.
|
|
137
|
+
expect(counter.tagName).toBe('SPAN');
|
|
138
|
+
expect(counter.querySelector('[data-part="fallback"]')?.tagName).toBe(
|
|
139
|
+
'SPAN'
|
|
140
|
+
);
|
|
136
141
|
expect(declarations(counter)).toContain(
|
|
137
142
|
'--chakra-colors-color-palette-surface:var(--chakra-colors-category-neutral-surface)'
|
|
138
143
|
);
|
|
@@ -158,8 +158,14 @@ const AvatarGroup = forwardRef<HTMLDivElement, AvatarGroupProps>(
|
|
|
158
158
|
<ChakraAvatarGroupTyped ref={ref} {...rest}>
|
|
159
159
|
{visible}
|
|
160
160
|
{overflow > 0 && (
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
// Spans so a group inside a button (a popover trigger) stays phrasing
|
|
162
|
+
// content; the recipe sets the display, so nothing else changes.
|
|
163
|
+
<AvatarRoot
|
|
164
|
+
as="span"
|
|
165
|
+
colorPalette="category.neutral"
|
|
166
|
+
variant="subtle"
|
|
167
|
+
>
|
|
168
|
+
<AvatarFallback as="span">+{overflow}</AvatarFallback>
|
|
163
169
|
</AvatarRoot>
|
|
164
170
|
)}
|
|
165
171
|
</ChakraAvatarGroupTyped>
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { forwardRef } from 'react';
|
|
2
2
|
import { Combobox as ChakraCombobox } from '@chakra-ui/react';
|
|
3
3
|
|
|
4
|
+
import { useTranslate } from '@/hooks/useTranslate';
|
|
4
5
|
import { noZoomOnFocus } from '@/theme/formControls';
|
|
5
6
|
import { mergeCss } from '@/utils/mergeCss';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import { CloseIcon } from '../Icon';
|
|
9
|
+
import {
|
|
10
|
+
comboboxClearTriggerStyles,
|
|
11
|
+
contentStyles,
|
|
12
|
+
inputStyles,
|
|
13
|
+
itemStyles,
|
|
14
|
+
} from './Select.styles';
|
|
8
15
|
|
|
9
16
|
const Input = forwardRef<HTMLInputElement, ChakraCombobox.InputProps>(
|
|
10
17
|
function ComboboxInput({ css, ...props }, ref) {
|
|
@@ -47,10 +54,28 @@ const Item = forwardRef<HTMLDivElement, ChakraCombobox.ItemProps>(
|
|
|
47
54
|
}
|
|
48
55
|
);
|
|
49
56
|
|
|
57
|
+
/** Same × as `Select.ClearTrigger`; see there. */
|
|
58
|
+
const ClearTrigger = forwardRef<
|
|
59
|
+
HTMLButtonElement,
|
|
60
|
+
ChakraCombobox.ClearTriggerProps
|
|
61
|
+
>(function ComboboxClearTrigger({ css, children, ...props }, ref) {
|
|
62
|
+
const translate = useTranslate();
|
|
63
|
+
return (
|
|
64
|
+
<ChakraCombobox.ClearTrigger
|
|
65
|
+
ref={ref}
|
|
66
|
+
aria-label={translate('clear_selection') as string}
|
|
67
|
+
{...props}
|
|
68
|
+
css={mergeCss(comboboxClearTriggerStyles, css)}
|
|
69
|
+
>
|
|
70
|
+
{children ?? <CloseIcon aria-hidden />}
|
|
71
|
+
</ChakraCombobox.ClearTrigger>
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
50
75
|
/**
|
|
51
76
|
* Compound Combobox primitives — Chakra's `Combobox` namespace with the
|
|
52
77
|
* Logician design-system styling baked into the visual parts (`Input`,
|
|
53
|
-
* `Content`, `Item`). Use these directly for full compositional control;
|
|
78
|
+
* `Content`, `Item`, `ClearTrigger`). Use these directly for full compositional control;
|
|
54
79
|
* use `ComboboxField` for the common single-select case.
|
|
55
80
|
*/
|
|
56
81
|
export const Combobox = {
|
|
@@ -62,7 +87,7 @@ export const Combobox = {
|
|
|
62
87
|
Input,
|
|
63
88
|
Trigger: ChakraCombobox.Trigger,
|
|
64
89
|
IndicatorGroup: ChakraCombobox.IndicatorGroup,
|
|
65
|
-
ClearTrigger
|
|
90
|
+
ClearTrigger,
|
|
66
91
|
Positioner: ChakraCombobox.Positioner,
|
|
67
92
|
Content,
|
|
68
93
|
Empty: ChakraCombobox.Empty,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ChakraProvider, createListCollection } from '@chakra-ui/react';
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { system } from '../../theme';
|
|
6
|
+
import { Select } from './Select';
|
|
7
|
+
|
|
8
|
+
const collection = createListCollection({
|
|
9
|
+
items: [
|
|
10
|
+
{ label: 'React', value: 'react' },
|
|
11
|
+
{ label: 'Vue', value: 'vue' },
|
|
12
|
+
],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const renderClearable = (
|
|
16
|
+
clearTrigger: React.ReactNode,
|
|
17
|
+
onValueChange = vi.fn(),
|
|
18
|
+
defaultValue = ['react']
|
|
19
|
+
) =>
|
|
20
|
+
render(
|
|
21
|
+
<ChakraProvider value={system}>
|
|
22
|
+
<Select.Root
|
|
23
|
+
collection={collection}
|
|
24
|
+
defaultValue={defaultValue}
|
|
25
|
+
onValueChange={onValueChange}
|
|
26
|
+
>
|
|
27
|
+
<Select.HiddenSelect />
|
|
28
|
+
<Select.Control>
|
|
29
|
+
<Select.Trigger>
|
|
30
|
+
<Select.ValueText />
|
|
31
|
+
</Select.Trigger>
|
|
32
|
+
<Select.IndicatorGroup>
|
|
33
|
+
{clearTrigger}
|
|
34
|
+
<Select.Indicator />
|
|
35
|
+
</Select.IndicatorGroup>
|
|
36
|
+
</Select.Control>
|
|
37
|
+
</Select.Root>
|
|
38
|
+
</ChakraProvider>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
describe('Select.ClearTrigger', () => {
|
|
42
|
+
it('names itself in the library language instead of "Clear value"', () => {
|
|
43
|
+
renderClearable(<Select.ClearTrigger />);
|
|
44
|
+
|
|
45
|
+
const button = screen.getByRole('button', { name: '선택 지우기' });
|
|
46
|
+
expect(button.querySelector('svg')).not.toBeNull();
|
|
47
|
+
expect(screen.queryByRole('button', { name: 'Clear value' })).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("keeps the caller's aria-label and still clears the value", async () => {
|
|
51
|
+
const onValueChange = vi.fn();
|
|
52
|
+
renderClearable(
|
|
53
|
+
<Select.ClearTrigger aria-label="조직 선택 지우기" />,
|
|
54
|
+
onValueChange
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
fireEvent.click(screen.getByRole('button', { name: '조직 선택 지우기' }));
|
|
58
|
+
|
|
59
|
+
await waitFor(() =>
|
|
60
|
+
expect(onValueChange).toHaveBeenCalledWith(
|
|
61
|
+
expect.objectContaining({ value: [] })
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('stays hidden while nothing is selected', () => {
|
|
67
|
+
renderClearable(<Select.ClearTrigger />, vi.fn(), []);
|
|
68
|
+
|
|
69
|
+
const button = document.querySelector<HTMLElement>(
|
|
70
|
+
'[data-part="clear-trigger"]'
|
|
71
|
+
);
|
|
72
|
+
expect(button).not.toBeNull();
|
|
73
|
+
expect(button).toHaveAttribute('hidden');
|
|
74
|
+
expect(getComputedStyle(button!).display).toBe('none');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
contentStyles,
|
|
23
23
|
indicatorStyles,
|
|
24
24
|
itemStyles,
|
|
25
|
+
selectClearTriggerStyles,
|
|
25
26
|
triggerStyles,
|
|
26
27
|
} from './Select.styles';
|
|
27
28
|
import { SelectField } from './SelectField';
|
|
@@ -198,6 +199,76 @@ export const MultiSelect = () => (
|
|
|
198
199
|
</Box>
|
|
199
200
|
);
|
|
200
201
|
|
|
202
|
+
/**
|
|
203
|
+
* An optional field the member can empty again. `ClearTrigger` goes before
|
|
204
|
+
* the chevron and shows only while something is selected. It draws its own
|
|
205
|
+
* icon and is named 「선택 지우기」 in the provider's language; pass
|
|
206
|
+
* `aria-label` to say what is cleared. A divider separates it from the
|
|
207
|
+
* chevron, and a long value ends in an ellipsis before it. On a touch screen
|
|
208
|
+
* its hit area widens to 44px toward the value, at the field's own height.
|
|
209
|
+
*/
|
|
210
|
+
const longValueCollection = createListCollection({
|
|
211
|
+
items: [
|
|
212
|
+
{
|
|
213
|
+
label: 'Engineering / Platform / Developer Experience',
|
|
214
|
+
value: 'dx',
|
|
215
|
+
},
|
|
216
|
+
{ label: 'Design', value: 'design' },
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
export const Clearable = () => (
|
|
221
|
+
<Stack maxW="320px" gap={4}>
|
|
222
|
+
<Box maxW="180px">
|
|
223
|
+
<Select.Root collection={longValueCollection} defaultValue={['dx']}>
|
|
224
|
+
<Select.HiddenSelect />
|
|
225
|
+
<Select.Label>Team (180px)</Select.Label>
|
|
226
|
+
<Select.Control>
|
|
227
|
+
<Select.Trigger>
|
|
228
|
+
<Select.ValueText placeholder="Select a team" />
|
|
229
|
+
</Select.Trigger>
|
|
230
|
+
<Select.IndicatorGroup>
|
|
231
|
+
<Select.ClearTrigger />
|
|
232
|
+
<Select.Indicator />
|
|
233
|
+
</Select.IndicatorGroup>
|
|
234
|
+
</Select.Control>
|
|
235
|
+
</Select.Root>
|
|
236
|
+
</Box>
|
|
237
|
+
{(['xs', 'sm', 'md', 'lg'] as const).map((size) => (
|
|
238
|
+
<Select.Root
|
|
239
|
+
key={size}
|
|
240
|
+
size={size}
|
|
241
|
+
collection={frameworkCollection}
|
|
242
|
+
defaultValue={['react']}
|
|
243
|
+
>
|
|
244
|
+
<Select.HiddenSelect />
|
|
245
|
+
<Select.Label>{`Framework (${size})`}</Select.Label>
|
|
246
|
+
<Select.Control>
|
|
247
|
+
<Select.Trigger>
|
|
248
|
+
<Select.ValueText placeholder="Select a framework" />
|
|
249
|
+
</Select.Trigger>
|
|
250
|
+
<Select.IndicatorGroup>
|
|
251
|
+
<Select.ClearTrigger />
|
|
252
|
+
<Select.Indicator />
|
|
253
|
+
</Select.IndicatorGroup>
|
|
254
|
+
</Select.Control>
|
|
255
|
+
<Portal>
|
|
256
|
+
<Select.Positioner>
|
|
257
|
+
<Select.Content>
|
|
258
|
+
{frameworkCollection.items.map((item) => (
|
|
259
|
+
<Select.Item item={item} key={item.value}>
|
|
260
|
+
<Select.ItemText>{item.label}</Select.ItemText>
|
|
261
|
+
<Select.ItemIndicator />
|
|
262
|
+
</Select.Item>
|
|
263
|
+
))}
|
|
264
|
+
</Select.Content>
|
|
265
|
+
</Select.Positioner>
|
|
266
|
+
</Portal>
|
|
267
|
+
</Select.Root>
|
|
268
|
+
))}
|
|
269
|
+
</Stack>
|
|
270
|
+
);
|
|
271
|
+
|
|
201
272
|
/* -------------------------------------------------------------------------
|
|
202
273
|
* Custom children
|
|
203
274
|
*
|
|
@@ -950,6 +1021,16 @@ export const UsageTokens: UsageStory = {
|
|
|
950
1021
|
token: indicatorStyles.color,
|
|
951
1022
|
use: <T en="Chevron" ko="펼침 화살표" />,
|
|
952
1023
|
},
|
|
1024
|
+
{
|
|
1025
|
+
token: selectClearTriggerStyles.color,
|
|
1026
|
+
use: <T en="Clear (×) button" ko="선택 지우기(×) 버튼" />,
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
token: selectClearTriggerStyles._hover.color,
|
|
1030
|
+
use: (
|
|
1031
|
+
<T en="Clear button on hover" ko="마우스를 올렸을 때 지우기 버튼" />
|
|
1032
|
+
),
|
|
1033
|
+
},
|
|
953
1034
|
{
|
|
954
1035
|
token: contentStyles.bg,
|
|
955
1036
|
use: <T en="List background" ko="목록 배경" />,
|
|
@@ -37,9 +37,27 @@ const fieldBase = {
|
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Room for the × and its divider at the end of the value, while the × shows.
|
|
42
|
+
*
|
|
43
|
+
* The indicator group overlays the value. Chakra keeps the value off the
|
|
44
|
+
* chevron with `maxW: 80%` on a Select (too little in a narrow filter) and a
|
|
45
|
+
* 1.5× height padding on a Combobox, and neither counts the × with its
|
|
46
|
+
* divider. This reserves the whole group instead: padding on both sides, the
|
|
47
|
+
* × (`8`) with its `me` (`1`), the group `gap` (`1`) and the chevron (`1em`),
|
|
48
|
+
* so a long value ends in an ellipsis before the ×.
|
|
49
|
+
*/
|
|
50
|
+
const clearTriggerRoom = (paddingVar: string) => ({
|
|
51
|
+
'[data-part=control]:has([data-part=clear-trigger]:not([hidden])) > &': {
|
|
52
|
+
pe: `calc(var(${paddingVar}) * 2 + 3.5em)`,
|
|
53
|
+
'& [data-part=value-text]': { maxW: 'none' },
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
40
57
|
/** Select trigger — the clickable control. */
|
|
41
58
|
export const triggerStyles = {
|
|
42
59
|
...fieldBase,
|
|
60
|
+
...clearTriggerRoom('--select-trigger-padding-x'),
|
|
43
61
|
cursor: 'pointer',
|
|
44
62
|
_open: {
|
|
45
63
|
borderColor: 'primary.main',
|
|
@@ -80,6 +98,7 @@ export const ghostTriggerStyles = {
|
|
|
80
98
|
/** Combobox text input — the typeable control. */
|
|
81
99
|
export const inputStyles = {
|
|
82
100
|
...fieldBase,
|
|
101
|
+
...clearTriggerRoom('--combobox-input-padding-x'),
|
|
83
102
|
};
|
|
84
103
|
|
|
85
104
|
/** Dropdown panel for both Select and Combobox. */
|
|
@@ -120,3 +139,65 @@ export const itemStyles = {
|
|
|
120
139
|
export const indicatorStyles = {
|
|
121
140
|
color: 'fg.muted',
|
|
122
141
|
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Clear (×) button for both Select and Combobox.
|
|
145
|
+
*
|
|
146
|
+
* Chakra's recipe gives this part a colour and no size, and its default icon
|
|
147
|
+
* is `1em`, so a bare `ClearTrigger` drew a 21px × next to a 14px chevron. The
|
|
148
|
+
* × is the secondary action, so it is smaller (`3.5`, 12px at 14px text) and
|
|
149
|
+
* lighter (`fg.icon`) than the chevron, on an `8` box. `& svg` rather than
|
|
150
|
+
* `_icon` so the size also wins over an icon passed with its own `boxSize`.
|
|
151
|
+
*
|
|
152
|
+
* On a touch screen the hit area widens to 44px, toward the value only: the
|
|
153
|
+
* chevron sits to the right and has to keep opening the list. The height stays
|
|
154
|
+
* the control's own height (`heightVar`), so the area never reaches the field
|
|
155
|
+
* above or below. Every control height is over the 24px KWCAG minimum.
|
|
156
|
+
*/
|
|
157
|
+
const clearTriggerStyles = (heightVar: string) => ({
|
|
158
|
+
position: 'relative',
|
|
159
|
+
display: 'inline-flex',
|
|
160
|
+
alignItems: 'center',
|
|
161
|
+
justifyContent: 'center',
|
|
162
|
+
boxSize: '8',
|
|
163
|
+
me: '1',
|
|
164
|
+
color: 'fg.icon',
|
|
165
|
+
cursor: 'pointer',
|
|
166
|
+
_hover: { color: 'fg.default' },
|
|
167
|
+
// The divider marks where opening starts: it sits in the `me` gap, takes no
|
|
168
|
+
// clicks, and everything right of it belongs to the chevron.
|
|
169
|
+
_after: {
|
|
170
|
+
content: '""',
|
|
171
|
+
position: 'absolute',
|
|
172
|
+
insetEnd: '-1',
|
|
173
|
+
top: '50%',
|
|
174
|
+
transform: 'translateY(-50%)',
|
|
175
|
+
h: '4',
|
|
176
|
+
borderEndWidth: '1px',
|
|
177
|
+
borderColor: 'border.subtle',
|
|
178
|
+
pointerEvents: 'none',
|
|
179
|
+
},
|
|
180
|
+
// zag hides the × with the `hidden` attribute while nothing is selected,
|
|
181
|
+
// and the `display` above would otherwise override it.
|
|
182
|
+
_hidden: { display: 'none' },
|
|
183
|
+
'& svg': { width: '3.5', height: '3.5' },
|
|
184
|
+
'@media (pointer: coarse)': {
|
|
185
|
+
'&::before': {
|
|
186
|
+
content: '""',
|
|
187
|
+
position: 'absolute',
|
|
188
|
+
insetEnd: 0,
|
|
189
|
+
top: '50%',
|
|
190
|
+
transform: 'translateY(-50%)',
|
|
191
|
+
width: '44px',
|
|
192
|
+
height: `var(${heightVar})`,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export const selectClearTriggerStyles = clearTriggerStyles(
|
|
198
|
+
'--select-trigger-height'
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
export const comboboxClearTriggerStyles = clearTriggerStyles(
|
|
202
|
+
'--combobox-input-height'
|
|
203
|
+
);
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { forwardRef } from 'react';
|
|
2
2
|
import { Select as ChakraSelect } from '@chakra-ui/react';
|
|
3
3
|
|
|
4
|
+
import { useTranslate } from '@/hooks/useTranslate';
|
|
4
5
|
import { mergeCss } from '@/utils/mergeCss';
|
|
5
6
|
|
|
7
|
+
import { CloseIcon } from '../Icon';
|
|
6
8
|
import {
|
|
7
9
|
contentStyles,
|
|
8
10
|
ghostTriggerStyles,
|
|
9
11
|
indicatorStyles,
|
|
10
12
|
itemStyles,
|
|
13
|
+
selectClearTriggerStyles,
|
|
11
14
|
triggerStyles,
|
|
12
15
|
} from './Select.styles';
|
|
13
16
|
import type { SelectVariant } from './Select.types';
|
|
@@ -67,10 +70,33 @@ const Indicator = forwardRef<HTMLDivElement, ChakraSelect.IndicatorProps>(
|
|
|
67
70
|
}
|
|
68
71
|
);
|
|
69
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The × that empties the selection. Draws `CloseIcon` when no children are
|
|
75
|
+
* passed, and names itself in the provider's language: Chakra's own name is
|
|
76
|
+
* the English "Clear value" in every locale. Pass `aria-label` to say what is
|
|
77
|
+
* being cleared ("조직 선택 지우기").
|
|
78
|
+
*/
|
|
79
|
+
const ClearTrigger = forwardRef<
|
|
80
|
+
HTMLButtonElement,
|
|
81
|
+
ChakraSelect.ClearTriggerProps
|
|
82
|
+
>(function SelectClearTrigger({ css, children, ...props }, ref) {
|
|
83
|
+
const translate = useTranslate();
|
|
84
|
+
return (
|
|
85
|
+
<ChakraSelect.ClearTrigger
|
|
86
|
+
ref={ref}
|
|
87
|
+
aria-label={translate('clear_selection') as string}
|
|
88
|
+
{...props}
|
|
89
|
+
css={mergeCss(selectClearTriggerStyles, css)}
|
|
90
|
+
>
|
|
91
|
+
{children ?? <CloseIcon aria-hidden />}
|
|
92
|
+
</ChakraSelect.ClearTrigger>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
70
96
|
/**
|
|
71
97
|
* Compound Select primitives — Chakra's `Select` namespace with the Logician
|
|
72
98
|
* design-system styling baked into the visual parts (`Trigger`, `Content`,
|
|
73
|
-
* `Item`, `Indicator`). Use these directly for full compositional control
|
|
99
|
+
* `Item`, `Indicator`, `ClearTrigger`). Use these directly for full compositional control
|
|
74
100
|
* (multi-select, grouped options, custom layouts); use `SelectField` for the
|
|
75
101
|
* common single-select case.
|
|
76
102
|
*/
|
|
@@ -84,7 +110,7 @@ export const Select = {
|
|
|
84
110
|
ValueText: ChakraSelect.ValueText,
|
|
85
111
|
IndicatorGroup: ChakraSelect.IndicatorGroup,
|
|
86
112
|
Indicator,
|
|
87
|
-
ClearTrigger
|
|
113
|
+
ClearTrigger,
|
|
88
114
|
HiddenSelect: ChakraSelect.HiddenSelect,
|
|
89
115
|
Positioner: ChakraSelect.Positioner,
|
|
90
116
|
Content,
|