@simplysm/solid 13.0.85 → 13.0.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -28
- package/dist/components/data/list/ListItem.d.ts.map +1 -1
- package/dist/components/data/list/ListItem.js +11 -4
- package/dist/components/data/list/ListItem.js.map +2 -2
- package/dist/components/data/list/ListItem.styles.d.ts +2 -0
- package/dist/components/data/list/ListItem.styles.d.ts.map +1 -1
- package/dist/components/data/list/ListItem.styles.js +11 -1
- package/dist/components/data/list/ListItem.styles.js.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheet.js +7 -0
- package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/features/data-select-button/DataSelectButton.d.ts.map +1 -1
- package/dist/components/features/data-select-button/DataSelectButton.js +30 -26
- package/dist/components/features/data-select-button/DataSelectButton.js.map +2 -2
- package/dist/components/features/permission-table/PermissionTable.js +5 -1
- package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
- package/dist/components/form-control/DropdownTrigger.styles.js +1 -1
- package/dist/components/form-control/combobox/Combobox.d.ts +19 -5
- package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
- package/dist/components/form-control/combobox/Combobox.js +2 -4
- package/dist/components/form-control/combobox/Combobox.js.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +2 -2
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.js +10 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
- package/dist/components/form-control/editor/RichTextEditor.d.ts +2 -2
- package/dist/components/form-control/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/components/form-control/editor/RichTextEditor.js +2 -2
- package/dist/components/form-control/editor/RichTextEditor.js.map +1 -1
- package/dist/components/form-control/field/DatePicker.d.ts +2 -2
- package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DatePicker.js.map +1 -1
- package/dist/components/form-control/field/DateTimePicker.d.ts +2 -2
- package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DateTimePicker.js.map +1 -1
- package/dist/components/form-control/field/Field.styles.d.ts +6 -7
- package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
- package/dist/components/form-control/field/Field.styles.js.map +1 -1
- package/dist/components/form-control/field/NumberInput.d.ts +2 -2
- package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
- package/dist/components/form-control/field/NumberInput.js.map +1 -1
- package/dist/components/form-control/field/TextInput.d.ts +2 -2
- package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
- package/dist/components/form-control/field/TextInput.js.map +1 -1
- package/dist/components/form-control/field/Textarea.d.ts +2 -2
- package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
- package/dist/components/form-control/field/Textarea.js.map +1 -1
- package/dist/components/form-control/field/TimePicker.d.ts +2 -2
- package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/TimePicker.js.map +1 -1
- package/dist/components/form-control/numpad/Numpad.d.ts.map +1 -1
- package/dist/components/form-control/numpad/Numpad.js +4 -17
- package/dist/components/form-control/numpad/Numpad.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +19 -6
- package/dist/components/form-control/select/Select.js.map +2 -2
- package/dist/components/form-control/state-preset/StatePreset.d.ts +1 -3
- package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
- package/dist/components/form-control/state-preset/StatePreset.js +69 -91
- package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
- package/dist/components/layout/FormGroup.js +1 -1
- package/dist/components/layout/FormGroup.js.map +1 -1
- package/dist/components/layout/FormTable.js +3 -3
- package/dist/components/layout/FormTable.js.map +1 -1
- package/dist/components/layout/sidebar/Sidebar.d.ts.map +1 -1
- package/dist/components/layout/sidebar/Sidebar.js +3 -6
- package/dist/components/layout/sidebar/Sidebar.js.map +2 -2
- package/dist/providers/i18n/locales/en.d.ts +2 -3
- package/dist/providers/i18n/locales/en.d.ts.map +1 -1
- package/dist/providers/i18n/locales/en.js +3 -4
- package/dist/providers/i18n/locales/en.js.map +1 -1
- package/dist/providers/i18n/locales/ko.d.ts +2 -3
- package/dist/providers/i18n/locales/ko.d.ts.map +1 -1
- package/dist/providers/i18n/locales/ko.js +3 -4
- package/dist/providers/i18n/locales/ko.js.map +1 -1
- package/docs/display-feedback.md +279 -0
- package/docs/features.md +357 -213
- package/docs/form-controls.md +261 -403
- package/docs/layout-data.md +386 -0
- package/docs/providers-hooks.md +411 -0
- package/package.json +5 -5
- package/src/components/data/list/ListItem.styles.ts +14 -2
- package/src/components/data/list/ListItem.tsx +13 -4
- package/src/components/features/crud-sheet/CrudSheet.tsx +8 -0
- package/src/components/features/data-select-button/DataSelectButton.tsx +39 -32
- package/src/components/features/permission-table/PermissionTable.tsx +1 -1
- package/src/components/form-control/DropdownTrigger.styles.ts +1 -1
- package/src/components/form-control/combobox/Combobox.tsx +42 -16
- package/src/components/form-control/date-range-picker/DateRangePicker.tsx +6 -4
- package/src/components/form-control/editor/RichTextEditor.tsx +5 -6
- package/src/components/form-control/field/DatePicker.tsx +3 -2
- package/src/components/form-control/field/DateTimePicker.tsx +3 -2
- package/src/components/form-control/field/Field.styles.ts +6 -8
- package/src/components/form-control/field/NumberInput.tsx +3 -2
- package/src/components/form-control/field/TextInput.tsx +3 -2
- package/src/components/form-control/field/Textarea.tsx +3 -2
- package/src/components/form-control/field/TimePicker.tsx +3 -2
- package/src/components/form-control/numpad/Numpad.tsx +16 -18
- package/src/components/form-control/select/Select.tsx +19 -5
- package/src/components/form-control/state-preset/StatePreset.tsx +32 -57
- package/src/components/layout/FormGroup.tsx +1 -1
- package/src/components/layout/FormTable.tsx +3 -3
- package/src/components/layout/sidebar/Sidebar.tsx +2 -3
- package/src/providers/i18n/locales/en.ts +2 -3
- package/src/providers/i18n/locales/ko.ts +2 -3
- package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +62 -7
- package/tests/components/form-control/combobox/Combobox.spec.tsx +3 -3
- package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +56 -0
- package/docs/data.md +0 -204
- package/docs/disclosure.md +0 -146
- package/docs/display.md +0 -125
- package/docs/feedback.md +0 -156
- package/docs/helpers.md +0 -173
- package/docs/hooks.md +0 -146
- package/docs/layout.md +0 -94
- package/docs/providers.md +0 -180
|
@@ -9,7 +9,6 @@ import { useNotification } from "../../feedback/notification/NotificationProvide
|
|
|
9
9
|
import { Icon } from "../../display/Icon";
|
|
10
10
|
import { bg, text } from "../../../styles/base.styles";
|
|
11
11
|
import { type ComponentSize, gap, pad } from "../../../styles/control.styles";
|
|
12
|
-
import { themeTokens } from "../../../styles/theme.styles";
|
|
13
12
|
import { Button } from "../Button";
|
|
14
13
|
import { useI18n } from "../../../providers/i18n/I18nProvider";
|
|
15
14
|
|
|
@@ -20,53 +19,33 @@ interface StatePresetItem<TValue> {
|
|
|
20
19
|
state: TValue;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
type StatePresetSize = ComponentSize;
|
|
24
|
-
|
|
25
22
|
export interface StatePresetProps<TValue> {
|
|
26
23
|
storageKey: string;
|
|
27
24
|
value: TValue;
|
|
28
25
|
onValueChange: (value: TValue) => void;
|
|
29
|
-
size?:
|
|
26
|
+
size?: ComponentSize;
|
|
30
27
|
class?: string;
|
|
31
28
|
style?: JSX.CSSProperties;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
// ── Style constants ──
|
|
35
32
|
|
|
36
|
-
const
|
|
33
|
+
const sizeClasses: Record<ComponentSize, string> = {
|
|
37
34
|
md: pad.md,
|
|
38
|
-
xs:
|
|
35
|
+
xs: pad.xs,
|
|
39
36
|
sm: pad.sm,
|
|
40
37
|
lg: pad.lg,
|
|
41
|
-
xl:
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const iconBtnSizeClasses: Record<StatePresetSize, string> = {
|
|
45
|
-
md: "p-0.5",
|
|
46
|
-
xs: "p-0",
|
|
47
|
-
sm: "p-0.5",
|
|
48
|
-
lg: "p-1",
|
|
49
|
-
xl: "p-1.5",
|
|
38
|
+
xl: pad.xl,
|
|
50
39
|
};
|
|
51
40
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
lg: "
|
|
57
|
-
xl: "
|
|
41
|
+
const iconSizeMap: Record<ComponentSize, string> = {
|
|
42
|
+
xs: "0.875em",
|
|
43
|
+
sm: "1em",
|
|
44
|
+
md: "1.25em",
|
|
45
|
+
lg: "1.5em",
|
|
46
|
+
xl: "1.75em",
|
|
58
47
|
};
|
|
59
48
|
|
|
60
|
-
const inputSizeClasses: Record<StatePresetSize, string> = {
|
|
61
|
-
md: clsx(pad.md, "w-24"),
|
|
62
|
-
xs: clsx("w-16", pad.xs, "text-sm"),
|
|
63
|
-
sm: clsx(pad.sm, "w-20"),
|
|
64
|
-
lg: clsx(pad.lg, "w-32"),
|
|
65
|
-
xl: clsx(pad.xl, "w-36 text-lg"),
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const iconSize = "0.85em";
|
|
69
|
-
|
|
70
49
|
// ── Component ──
|
|
71
50
|
|
|
72
51
|
function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element {
|
|
@@ -191,31 +170,29 @@ function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element
|
|
|
191
170
|
|
|
192
171
|
// ── Render ──
|
|
193
172
|
|
|
194
|
-
const
|
|
195
|
-
twMerge("rounded-full", iconBtnSizeClasses[local.size ?? "md"]);
|
|
173
|
+
const resolvedSize = () => local.size ?? "md";
|
|
196
174
|
|
|
197
175
|
return (
|
|
198
176
|
<div class={twMerge(clsx("inline-flex items-center", gap.lg, "flex-wrap"), local.class)} style={local.style}>
|
|
199
177
|
{/* Star button - add preset */}
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
starBtnSizeClasses[local.size ?? "md"],
|
|
205
|
-
)}
|
|
178
|
+
<Button
|
|
179
|
+
size={local.size}
|
|
180
|
+
variant="ghost"
|
|
181
|
+
class="rounded-full text-warning-500"
|
|
206
182
|
onClick={handleStartAdd}
|
|
207
183
|
title={i18n.t("statePreset.addPreset")}
|
|
208
184
|
>
|
|
209
|
-
|
|
210
|
-
|
|
185
|
+
{"\u200B"}
|
|
186
|
+
<Icon icon={IconStar} size={iconSizeMap[resolvedSize()]} />
|
|
187
|
+
</Button>
|
|
211
188
|
|
|
212
189
|
{/* Preset chips */}
|
|
213
190
|
<For each={presets()}>
|
|
214
191
|
{(preset, index) => (
|
|
215
192
|
<span
|
|
216
193
|
class={twMerge(
|
|
217
|
-
clsx("inline-flex items-center", gap.md, "rounded-full", bg.subtle, text.default, "cursor-default"),
|
|
218
|
-
|
|
194
|
+
clsx("inline-flex items-center", gap.md, "rounded-full border border-transparent", bg.subtle, text.default, "cursor-default"),
|
|
195
|
+
sizeClasses[resolvedSize()],
|
|
219
196
|
)}
|
|
220
197
|
>
|
|
221
198
|
<button
|
|
@@ -226,24 +203,22 @@ function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element
|
|
|
226
203
|
>
|
|
227
204
|
{preset.name}
|
|
228
205
|
</button>
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
class={resolvedIconBtnClass()}
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
class="cursor-pointer rounded-full opacity-50 hover:opacity-100 focus:outline-none"
|
|
233
209
|
onClick={() => handleOverwrite(index())}
|
|
234
210
|
title={i18n.t("statePreset.overwrite")}
|
|
235
211
|
>
|
|
236
|
-
<Icon icon={IconDeviceFloppy} size={
|
|
237
|
-
</
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
class={resolvedIconBtnClass()}
|
|
212
|
+
<Icon icon={IconDeviceFloppy} size={iconSizeMap[resolvedSize()]} />
|
|
213
|
+
</button>
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
class="cursor-pointer rounded-full opacity-50 hover:opacity-100 focus:outline-none"
|
|
242
217
|
onClick={() => handleDelete(index())}
|
|
243
218
|
title={i18n.t("statePreset.deletePreset")}
|
|
244
219
|
>
|
|
245
|
-
<Icon icon={IconX} size={
|
|
246
|
-
</
|
|
220
|
+
<Icon icon={IconX} size={iconSizeMap[resolvedSize()]} />
|
|
221
|
+
</button>
|
|
247
222
|
</span>
|
|
248
223
|
)}
|
|
249
224
|
</For>
|
|
@@ -257,8 +232,8 @@ function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element
|
|
|
257
232
|
}}
|
|
258
233
|
type="text"
|
|
259
234
|
class={twMerge(
|
|
260
|
-
clsx("rounded-full", bg.subtle, text.default, "border border-transparent focus:outline-none focus:ring-1 focus:ring-primary-400", text.placeholder),
|
|
261
|
-
|
|
235
|
+
clsx("inline-flex items-center rounded-full leading-normal", bg.subtle, text.default, "border border-transparent focus:outline-none focus:ring-1 focus:ring-primary-400", text.placeholder),
|
|
236
|
+
sizeClasses[resolvedSize()],
|
|
262
237
|
)}
|
|
263
238
|
placeholder={i18n.t("statePreset.namePlaceholder")}
|
|
264
239
|
autocomplete="one-time-code"
|
|
@@ -43,7 +43,7 @@ const FormGroupBase: ParentComponent<FormGroupProps> = (props) => {
|
|
|
43
43
|
const [local, rest] = splitProps(props, ["children", "class", "inline"]);
|
|
44
44
|
|
|
45
45
|
const getClassName = () => twMerge(
|
|
46
|
-
local.inline ? "inline-flex flex-row flex-wrap items-center gap-2" : "inline-flex flex-col gap-2",
|
|
46
|
+
local.inline ? "inline-flex flex-row flex-wrap items-center gap-x-2 gap-y-1" : "inline-flex flex-col gap-2",
|
|
47
47
|
local.class,
|
|
48
48
|
);
|
|
49
49
|
|
|
@@ -11,7 +11,7 @@ export interface FormTableItemProps extends JSX.TdHTMLAttributes<HTMLTableCellEl
|
|
|
11
11
|
|
|
12
12
|
const FormTableRow: ParentComponent<JSX.HTMLAttributes<HTMLTableRowElement>> = (props) => {
|
|
13
13
|
const [local, rest] = splitProps(props, ["children", "class"]);
|
|
14
|
-
return <tr class={twMerge("[
|
|
14
|
+
return <tr class={twMerge("[&>td:last-child]:pr-0 [&:last-child>*]:pb-0", local.class)} {...rest}>{local.children}</tr>;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const FormTableItem: ParentComponent<FormTableItemProps> = (props) => {
|
|
@@ -26,9 +26,9 @@ const FormTableItem: ParentComponent<FormTableItemProps> = (props) => {
|
|
|
26
26
|
return (
|
|
27
27
|
<>
|
|
28
28
|
<Show when={local.label}>
|
|
29
|
-
<th class="w-0 whitespace-nowrap pb-1
|
|
29
|
+
<th class="w-0 whitespace-nowrap pb-1 pr-2 text-right align-middle">{local.label}</th>
|
|
30
30
|
</Show>
|
|
31
|
-
<td class={twMerge("pb-1 pr-
|
|
31
|
+
<td class={twMerge("pb-1 pr-2 align-middle", local.class)} colspan={effectiveColspan()} {...rest}>
|
|
32
32
|
{local.children}
|
|
33
33
|
</td>
|
|
34
34
|
</>
|
|
@@ -216,7 +216,6 @@ const MenuContext = createContext<MenuContextValue>();
|
|
|
216
216
|
*/
|
|
217
217
|
const SidebarMenu: Component<SidebarMenuProps> = (props) => {
|
|
218
218
|
const [local, rest] = splitProps(props, ["menus", "class"]);
|
|
219
|
-
const i18n = useI18n();
|
|
220
219
|
|
|
221
220
|
const location = useLocation();
|
|
222
221
|
|
|
@@ -252,9 +251,9 @@ const SidebarMenu: Component<SidebarMenuProps> = (props) => {
|
|
|
252
251
|
return (
|
|
253
252
|
<MenuContext.Provider value={{ initialOpenItems }}>
|
|
254
253
|
<div {...rest} data-sidebar-menu class={getClassName()}>
|
|
255
|
-
<div class={clsx("px-
|
|
254
|
+
<div class={clsx("px-3.5 py-1 text-sm font-bold", text.muted, "uppercase tracking-wider")}>MENU</div>
|
|
256
255
|
<List inset>
|
|
257
|
-
<For each={local.menus}>{(menu) => <MenuItem menu={menu}
|
|
256
|
+
<For each={local.menus}>{(menu) => <MenuItem menu={menu} />}</For>
|
|
258
257
|
</List>
|
|
259
258
|
</div>
|
|
260
259
|
</MenuContext.Provider>
|
|
@@ -83,6 +83,8 @@ export default {
|
|
|
83
83
|
},
|
|
84
84
|
permissionTable: {
|
|
85
85
|
permissionItem: "Permission Item",
|
|
86
|
+
use: "Use",
|
|
87
|
+
edit: "Edit",
|
|
86
88
|
},
|
|
87
89
|
crudSheet: {
|
|
88
90
|
save: "Save",
|
|
@@ -180,9 +182,6 @@ export default {
|
|
|
180
182
|
numpad: {
|
|
181
183
|
enter: "ENT",
|
|
182
184
|
},
|
|
183
|
-
sidebarMenu: {
|
|
184
|
-
menu: "MENU",
|
|
185
|
-
},
|
|
186
185
|
validation: {
|
|
187
186
|
required: "This is a required field",
|
|
188
187
|
requiredField: "Required field",
|
|
@@ -83,6 +83,8 @@ export default {
|
|
|
83
83
|
},
|
|
84
84
|
permissionTable: {
|
|
85
85
|
permissionItem: "권한 항목",
|
|
86
|
+
use: "사용",
|
|
87
|
+
edit: "편집",
|
|
86
88
|
},
|
|
87
89
|
crudSheet: {
|
|
88
90
|
save: "저장",
|
|
@@ -180,9 +182,6 @@ export default {
|
|
|
180
182
|
numpad: {
|
|
181
183
|
enter: "입력",
|
|
182
184
|
},
|
|
183
|
-
sidebarMenu: {
|
|
184
|
-
menu: "메뉴",
|
|
185
|
-
},
|
|
186
185
|
validation: {
|
|
187
186
|
required: "필수 입력 항목입니다",
|
|
188
187
|
requiredField: "필수 항목",
|
|
@@ -395,9 +395,8 @@ describe("DataSelectButton", () => {
|
|
|
395
395
|
/>
|
|
396
396
|
));
|
|
397
397
|
|
|
398
|
-
const trigger = container.querySelector("[
|
|
399
|
-
expect(trigger.
|
|
400
|
-
expect(trigger.tabIndex).toBe(-1);
|
|
398
|
+
const trigger = container.querySelector("button[data-trigger]") as HTMLButtonElement;
|
|
399
|
+
expect(trigger.disabled).toBe(true);
|
|
401
400
|
});
|
|
402
401
|
|
|
403
402
|
it("sets required aria attribute", () => {
|
|
@@ -412,11 +411,67 @@ describe("DataSelectButton", () => {
|
|
|
412
411
|
/>
|
|
413
412
|
));
|
|
414
413
|
|
|
415
|
-
const trigger = container.querySelector("[
|
|
414
|
+
const trigger = container.querySelector("button[data-trigger]") as HTMLElement;
|
|
416
415
|
expect(trigger.getAttribute("aria-required")).toBe("true");
|
|
417
416
|
});
|
|
418
417
|
|
|
419
|
-
it("
|
|
418
|
+
it("trigger is a button element with aria-haspopup='dialog'", () => {
|
|
419
|
+
const load = createTestLoad();
|
|
420
|
+
const { container } = renderWithDialog(() => (
|
|
421
|
+
<DataSelectButton
|
|
422
|
+
load={load}
|
|
423
|
+
dialog={TestDialogComponent}
|
|
424
|
+
dialogProps={{ confirmKeys: [] }}
|
|
425
|
+
renderItem={(item: TestItem) => <span>{item.name}</span>}
|
|
426
|
+
/>
|
|
427
|
+
));
|
|
428
|
+
|
|
429
|
+
const trigger = container.querySelector("button[data-trigger]") as HTMLButtonElement;
|
|
430
|
+
expect(trigger).not.toBeNull();
|
|
431
|
+
expect(trigger.tagName).toBe("BUTTON");
|
|
432
|
+
expect(trigger.getAttribute("aria-haspopup")).toBe("dialog");
|
|
433
|
+
expect(trigger.getAttribute("type")).toBe("button");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("aria-expanded changes dynamically with dialog open state", async () => {
|
|
437
|
+
const load = createTestLoad();
|
|
438
|
+
|
|
439
|
+
const { container } = renderWithDialog(() => (
|
|
440
|
+
<DataSelectButton
|
|
441
|
+
load={load}
|
|
442
|
+
dialog={TestDialogComponent}
|
|
443
|
+
dialogProps={{ confirmKeys: [1] }}
|
|
444
|
+
renderItem={(item: TestItem) => <span>{item.name}</span>}
|
|
445
|
+
/>
|
|
446
|
+
));
|
|
447
|
+
|
|
448
|
+
const trigger = container.querySelector("button[data-trigger]") as HTMLButtonElement;
|
|
449
|
+
|
|
450
|
+
// Initially false
|
|
451
|
+
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
452
|
+
|
|
453
|
+
// Open dialog via search button
|
|
454
|
+
const searchBtn = container.querySelector("[data-search-button]") as HTMLButtonElement;
|
|
455
|
+
searchBtn.click();
|
|
456
|
+
|
|
457
|
+
// While dialog is open, aria-expanded should be true
|
|
458
|
+
await vi.waitFor(() => {
|
|
459
|
+
expect(trigger.getAttribute("aria-expanded")).toBe("true");
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Confirm dialog to close it
|
|
463
|
+
const confirmBtn = document.querySelector(
|
|
464
|
+
"[data-testid='dialog-confirm']",
|
|
465
|
+
) as HTMLButtonElement;
|
|
466
|
+
confirmBtn.click();
|
|
467
|
+
|
|
468
|
+
// After dialog closes, aria-expanded should be false again
|
|
469
|
+
await vi.waitFor(() => {
|
|
470
|
+
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("opens dialog on trigger click", async () => {
|
|
420
475
|
const load = createTestLoad();
|
|
421
476
|
const onValueChange = vi.fn();
|
|
422
477
|
|
|
@@ -430,8 +485,8 @@ describe("DataSelectButton", () => {
|
|
|
430
485
|
/>
|
|
431
486
|
));
|
|
432
487
|
|
|
433
|
-
const trigger = container.querySelector("[
|
|
434
|
-
trigger.
|
|
488
|
+
const trigger = container.querySelector("button[data-trigger]") as HTMLElement;
|
|
489
|
+
trigger.click();
|
|
435
490
|
|
|
436
491
|
await vi.waitFor(() => {
|
|
437
492
|
const confirmBtn = document.querySelector(
|
|
@@ -143,7 +143,7 @@ describe("Combobox component", () => {
|
|
|
143
143
|
onValueChange={handleChange}
|
|
144
144
|
allowsCustomValue
|
|
145
145
|
parseCustomValue={(text) => text}
|
|
146
|
-
renderValue={(v) => <>{v}</>}
|
|
146
|
+
renderValue={(v: string) => <>{v}</>}
|
|
147
147
|
/>
|
|
148
148
|
</I18nProvider></ConfigProvider>
|
|
149
149
|
));
|
|
@@ -178,7 +178,7 @@ describe("Combobox component", () => {
|
|
|
178
178
|
expect(handleChange).toHaveBeenCalledWith({ name: "테스트", custom: true });
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
it("
|
|
181
|
+
it("uses query text as value when allowsCustomValue is true without parseCustomValue", async () => {
|
|
182
182
|
const onValueChange = vi.fn();
|
|
183
183
|
const { container, getByRole } = render(() => (
|
|
184
184
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -196,7 +196,7 @@ describe("Combobox component", () => {
|
|
|
196
196
|
fireEvent.keyDown(getByRole("combobox"), { key: "Enter" });
|
|
197
197
|
|
|
198
198
|
await waitFor(() => {
|
|
199
|
-
expect(onValueChange).toHaveBeenCalledWith(
|
|
199
|
+
expect(onValueChange).toHaveBeenCalledWith("custom text");
|
|
200
200
|
});
|
|
201
201
|
});
|
|
202
202
|
});
|
|
@@ -115,6 +115,62 @@ describe("DateRangePicker component", () => {
|
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
describe("required prop propagation", () => {
|
|
119
|
+
it("propagates required to child DatePickers in range mode", () => {
|
|
120
|
+
const { container } = render(() => (
|
|
121
|
+
<ConfigProvider clientName="test"><I18nProvider>
|
|
122
|
+
<DateRangePicker periodType="range" required />
|
|
123
|
+
</I18nProvider></ConfigProvider>
|
|
124
|
+
));
|
|
125
|
+
const wrapper = container.querySelector("[data-date-range-picker]");
|
|
126
|
+
const dateFields = wrapper?.querySelectorAll("[data-date-field]");
|
|
127
|
+
|
|
128
|
+
// range mode has 2 DatePickers (from + to)
|
|
129
|
+
expect(dateFields?.length).toBe(2);
|
|
130
|
+
|
|
131
|
+
// Each DatePicker's hidden input should have the required validation message
|
|
132
|
+
dateFields?.forEach((field) => {
|
|
133
|
+
const hiddenInput = field.querySelector("input[aria-hidden='true']") as HTMLInputElement;
|
|
134
|
+
expect(hiddenInput).toBeTruthy();
|
|
135
|
+
expect(hiddenInput.validationMessage).toBe("This is a required field");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("propagates required to child DatePicker in day mode", () => {
|
|
140
|
+
const { container } = render(() => (
|
|
141
|
+
<ConfigProvider clientName="test"><I18nProvider>
|
|
142
|
+
<DateRangePicker periodType="day" required />
|
|
143
|
+
</I18nProvider></ConfigProvider>
|
|
144
|
+
));
|
|
145
|
+
const wrapper = container.querySelector("[data-date-range-picker]");
|
|
146
|
+
const dateFields = wrapper?.querySelectorAll("[data-date-field]");
|
|
147
|
+
|
|
148
|
+
// day mode has 1 DatePicker
|
|
149
|
+
expect(dateFields?.length).toBe(1);
|
|
150
|
+
|
|
151
|
+
const hiddenInput = dateFields?.[0].querySelector("input[aria-hidden='true']") as HTMLInputElement;
|
|
152
|
+
expect(hiddenInput).toBeTruthy();
|
|
153
|
+
expect(hiddenInput.validationMessage).toBe("This is a required field");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("propagates required to child DatePicker in month mode", () => {
|
|
157
|
+
const { container } = render(() => (
|
|
158
|
+
<ConfigProvider clientName="test"><I18nProvider>
|
|
159
|
+
<DateRangePicker periodType="month" required />
|
|
160
|
+
</I18nProvider></ConfigProvider>
|
|
161
|
+
));
|
|
162
|
+
const wrapper = container.querySelector("[data-date-range-picker]");
|
|
163
|
+
const dateFields = wrapper?.querySelectorAll("[data-date-field]");
|
|
164
|
+
|
|
165
|
+
// month mode has 1 DatePicker
|
|
166
|
+
expect(dateFields?.length).toBe(1);
|
|
167
|
+
|
|
168
|
+
const hiddenInput = dateFields?.[0].querySelector("input[aria-hidden='true']") as HTMLInputElement;
|
|
169
|
+
expect(hiddenInput).toBeTruthy();
|
|
170
|
+
expect(hiddenInput.validationMessage).toBe("This is a required field");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
118
174
|
describe("from change - 'range' mode", () => {
|
|
119
175
|
it("calls onToChange(from) when from > to", () => {
|
|
120
176
|
const onFromChange = vi.fn();
|
package/docs/data.md
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
# Data
|
|
2
|
-
|
|
3
|
-
## Table
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
interface TableProps extends JSX.HTMLAttributes<HTMLTableElement> {
|
|
7
|
-
inset?: boolean;
|
|
8
|
-
}
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Basic HTML table with consistent styling.
|
|
12
|
-
|
|
13
|
-
**Sub-components:** `Table.Row`, `Table.HeaderCell`, `Table.Cell`
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## List
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
interface ListProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
21
|
-
inset?: boolean;
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Vertical list with keyboard navigation (ArrowUp/Down, Home/End) and tree-view support (ArrowRight/Left to expand/collapse).
|
|
26
|
-
|
|
27
|
-
**Sub-component:** `List.Item`
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Pagination
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
interface PaginationProps extends JSX.HTMLAttributes<HTMLElement> {
|
|
35
|
-
page: number;
|
|
36
|
-
onPageChange?: (page: number) => void;
|
|
37
|
-
totalPageCount: number;
|
|
38
|
-
displayPageCount?: number;
|
|
39
|
-
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Page navigation control. `page` is 1-based. `displayPageCount` controls how many page numbers are visible at once.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## DataSheet
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
interface DataSheetProps<TItem> {
|
|
51
|
-
items?: TItem[];
|
|
52
|
-
storageKey?: string;
|
|
53
|
-
hideConfigBar?: boolean;
|
|
54
|
-
inset?: boolean;
|
|
55
|
-
contentStyle?: JSX.CSSProperties | string;
|
|
56
|
-
|
|
57
|
-
// Sorting
|
|
58
|
-
sorts?: SortingDef[];
|
|
59
|
-
onSortsChange?: (sorts: SortingDef[]) => void;
|
|
60
|
-
autoSort?: boolean;
|
|
61
|
-
|
|
62
|
-
// Pagination
|
|
63
|
-
page?: number;
|
|
64
|
-
onPageChange?: (page: number) => void;
|
|
65
|
-
totalPageCount?: number;
|
|
66
|
-
pageSize?: number;
|
|
67
|
-
displayPageCount?: number;
|
|
68
|
-
|
|
69
|
-
// Selection
|
|
70
|
-
selectionMode?: "single" | "multiple";
|
|
71
|
-
selection?: TItem[];
|
|
72
|
-
onSelectionChange?: (items: TItem[]) => void;
|
|
73
|
-
autoSelect?: boolean;
|
|
74
|
-
isItemSelectable?: (item: TItem) => boolean | string;
|
|
75
|
-
|
|
76
|
-
// Tree expansion
|
|
77
|
-
expandedItems?: TItem[];
|
|
78
|
-
onExpandedItemsChange?: (items: TItem[]) => void;
|
|
79
|
-
itemChildren?: (item: TItem, index: number) => TItem[] | undefined;
|
|
80
|
-
|
|
81
|
-
// Cell styling
|
|
82
|
-
cellClass?: (item: TItem, colKey: string) => string | undefined;
|
|
83
|
-
cellStyle?: (item: TItem, colKey: string) => string | undefined;
|
|
84
|
-
|
|
85
|
-
// Reordering
|
|
86
|
-
onItemsReorder?: (event: DataSheetReorderEvent<TItem>) => void;
|
|
87
|
-
|
|
88
|
-
class?: string;
|
|
89
|
-
children: JSX.Element;
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Advanced data grid with sorting, pagination, row selection, tree expansion, column reordering, and column configuration persistence.
|
|
94
|
-
|
|
95
|
-
- `storageKey` — persists column configuration (width, visibility, order) to sync storage
|
|
96
|
-
- `autoSort` — sorts items client-side without `onSortsChange`
|
|
97
|
-
- `autoSelect` — manages selection state internally
|
|
98
|
-
- `itemChildren` — enables tree-structured rows
|
|
99
|
-
|
|
100
|
-
**Sub-component:** `DataSheet.Column`
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
interface DataSheetColumnProps<TItem> {
|
|
104
|
-
key: string;
|
|
105
|
-
header?: string | string[];
|
|
106
|
-
headerContent?: () => JSX.Element;
|
|
107
|
-
headerStyle?: string;
|
|
108
|
-
summary?: () => JSX.Element;
|
|
109
|
-
tooltip?: string;
|
|
110
|
-
fixed?: boolean;
|
|
111
|
-
hidden?: boolean;
|
|
112
|
-
collapse?: boolean;
|
|
113
|
-
width?: string;
|
|
114
|
-
class?: string;
|
|
115
|
-
sortable?: boolean;
|
|
116
|
-
resizable?: boolean;
|
|
117
|
-
children: (ctx: DataSheetCellContext<TItem>) => JSX.Element;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
interface DataSheetCellContext<TItem> {
|
|
121
|
-
item: TItem;
|
|
122
|
-
index: number;
|
|
123
|
-
row: number;
|
|
124
|
-
depth: number;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
interface SortingDef {
|
|
128
|
-
key: string;
|
|
129
|
-
desc: boolean;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
interface DataSheetReorderEvent<TItem> {
|
|
133
|
-
item: TItem;
|
|
134
|
-
targetItem: TItem;
|
|
135
|
-
position: "before" | "after" | "inside";
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## Calendar
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
interface CalendarProps<TValue> extends Omit<JSX.HTMLAttributes<HTMLTableElement>, "children"> {
|
|
145
|
-
items: TValue[];
|
|
146
|
-
getItemDate: (item: TValue, index: number) => DateOnly;
|
|
147
|
-
renderItem: (item: TValue, index: number) => JSX.Element;
|
|
148
|
-
yearMonth?: DateOnly;
|
|
149
|
-
onYearMonthChange?: (value: DateOnly) => void;
|
|
150
|
-
weekStartDay?: number;
|
|
151
|
-
minDaysInFirstWeek?: number;
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Monthly calendar view that renders items on their corresponding dates. `getItemDate` maps items to dates. `renderItem` renders each item cell.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Kanban
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
interface KanbanProps<TCardValue, TLaneValue> extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children" | "onDrop"> {
|
|
163
|
-
onDrop?: (info: KanbanDropInfo<TLaneValue, TCardValue>) => void;
|
|
164
|
-
selectedValues?: TCardValue[];
|
|
165
|
-
onSelectedValuesChange?: (values: TCardValue[]) => void;
|
|
166
|
-
children?: JSX.Element;
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Kanban board with drag-and-drop card management.
|
|
171
|
-
|
|
172
|
-
**Sub-components:**
|
|
173
|
-
- `Kanban.Lane` — `{ value?: TLaneValue; busy?: boolean; collapsible?: boolean; collapsed?: boolean; onCollapsedChange?: (collapsed: boolean) => void }`
|
|
174
|
-
- `Kanban.Card` — `{ value?: TCardValue; draggable?: boolean; selectable?: boolean; contentClass?: string }`
|
|
175
|
-
- `Kanban.LaneTitle` — lane title slot
|
|
176
|
-
- `Kanban.LaneTools` — lane toolbar slot
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Usage Examples
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
import { DataSheet, Pagination } from "@simplysm/solid";
|
|
184
|
-
|
|
185
|
-
<DataSheet
|
|
186
|
-
items={data()}
|
|
187
|
-
sorts={sorts()}
|
|
188
|
-
onSortsChange={setSorts}
|
|
189
|
-
page={page()}
|
|
190
|
-
onPageChange={setPage}
|
|
191
|
-
totalPageCount={totalPages()}
|
|
192
|
-
selectionMode="multiple"
|
|
193
|
-
selection={selected()}
|
|
194
|
-
onSelectionChange={setSelected}
|
|
195
|
-
storageKey="my-table"
|
|
196
|
-
>
|
|
197
|
-
<DataSheet.Column key="name" header="Name" sortable>
|
|
198
|
-
{(ctx) => ctx.item.name}
|
|
199
|
-
</DataSheet.Column>
|
|
200
|
-
<DataSheet.Column key="age" header="Age" sortable width="80px">
|
|
201
|
-
{(ctx) => ctx.item.age}
|
|
202
|
-
</DataSheet.Column>
|
|
203
|
-
</DataSheet>
|
|
204
|
-
```
|