@simplysm/solid 13.0.84 → 13.0.86
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/data/sheet/DataSheet.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.js +6 -9
- package/dist/components/data/sheet/DataSheet.js.map +2 -2
- package/dist/components/data/sheet/hooks/createDataSheetExpansion.d.ts.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetExpansion.js +15 -17
- package/dist/components/data/sheet/hooks/createDataSheetExpansion.js.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetReorder.d.ts.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetReorder.js +12 -12
- package/dist/components/data/sheet/hooks/createDataSheetReorder.js.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetSelection.d.ts.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetSelection.js +9 -3
- package/dist/components/data/sheet/hooks/createDataSheetSelection.js.map +1 -1
- package/dist/components/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +3 -21
- package/dist/components/disclosure/Dialog.js.map +2 -2
- package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
- package/dist/components/disclosure/Dropdown.js +1 -11
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/disclosure/Tabs.d.ts.map +1 -1
- package/dist/components/disclosure/Tabs.js +1 -3
- package/dist/components/disclosure/Tabs.js.map +2 -2
- package/dist/components/features/crud-detail/CrudDetail.js +103 -102
- package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
- package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheet.js +10 -5
- 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/feedback/busy/BusyContainer.d.ts.map +1 -1
- package/dist/components/feedback/busy/BusyContainer.js +1 -6
- package/dist/components/feedback/busy/BusyContainer.js.map +2 -2
- package/dist/components/form-control/DropdownTrigger.styles.js +1 -1
- package/dist/components/form-control/checkbox/SelectableBase.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/SelectableBase.js +2 -4
- package/dist/components/form-control/checkbox/SelectableBase.js.map +2 -2
- 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 +11 -3
- 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 -4
- package/dist/components/form-control/editor/RichTextEditor.js.map +2 -2
- 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 +7 -7
- package/dist/components/form-control/field/NumberInput.js.map +2 -2
- 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 +1 -3
- package/dist/components/form-control/field/Textarea.js.map +2 -2
- 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 +2 -0
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +29 -15
- 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 -95
- 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/components/layout/topbar/Topbar.js +1 -3
- package/dist/components/layout/topbar/Topbar.js.map +2 -2
- package/dist/hooks/createControllableStore.d.ts.map +1 -1
- package/dist/hooks/createControllableStore.js +8 -5
- package/dist/hooks/createControllableStore.js.map +1 -1
- package/dist/hooks/useLocalStorage.d.ts.map +1 -1
- package/dist/hooks/useLocalStorage.js +3 -2
- package/dist/hooks/useLocalStorage.js.map +1 -1
- package/dist/hooks/useSyncConfig.d.ts.map +1 -1
- package/dist/hooks/useSyncConfig.js +5 -4
- package/dist/hooks/useSyncConfig.js.map +1 -1
- 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/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.js +0 -1
- package/dist/providers/shared-data/SharedDataProvider.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/data/sheet/DataSheet.tsx +6 -10
- package/src/components/data/sheet/hooks/createDataSheetExpansion.ts +17 -18
- package/src/components/data/sheet/hooks/createDataSheetReorder.ts +12 -13
- package/src/components/data/sheet/hooks/createDataSheetSelection.ts +9 -3
- package/src/components/disclosure/Dialog.tsx +45 -59
- package/src/components/disclosure/Dropdown.tsx +4 -14
- package/src/components/disclosure/Tabs.tsx +12 -17
- package/src/components/features/crud-detail/CrudDetail.tsx +4 -4
- package/src/components/features/crud-sheet/CrudSheet.tsx +12 -5
- package/src/components/features/data-select-button/DataSelectButton.tsx +39 -32
- package/src/components/features/permission-table/PermissionTable.tsx +1 -1
- package/src/components/feedback/busy/BusyContainer.tsx +12 -18
- package/src/components/form-control/DropdownTrigger.styles.ts +1 -1
- package/src/components/form-control/checkbox/SelectableBase.tsx +10 -16
- package/src/components/form-control/combobox/Combobox.tsx +42 -16
- package/src/components/form-control/date-range-picker/DateRangePicker.tsx +7 -8
- package/src/components/form-control/editor/RichTextEditor.tsx +14 -16
- 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 +9 -10
- package/src/components/form-control/field/TextInput.tsx +3 -2
- package/src/components/form-control/field/Textarea.tsx +14 -12
- 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 +41 -13
- package/src/components/form-control/state-preset/StatePreset.tsx +39 -71
- 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/components/layout/topbar/Topbar.tsx +2 -2
- package/src/hooks/createControllableStore.ts +8 -4
- package/src/hooks/useLocalStorage.ts +3 -2
- package/src/hooks/useSyncConfig.ts +5 -4
- package/src/providers/i18n/locales/en.ts +2 -3
- package/src/providers/i18n/locales/ko.ts +2 -3
- package/src/providers/shared-data/SharedDataProvider.tsx +0 -1
- package/tests/components/features/crud-detail/CrudDetail.spec.tsx +49 -0
- 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/tests/components/form-control/select/SelectItem.spec.tsx +5 -0
- package/tests/providers/shared-data/SharedDataProvider.spec.tsx +0 -104
- 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
|
@@ -32,12 +32,16 @@ import { TextInput } from "../field/TextInput";
|
|
|
32
32
|
import { useI18n } from "../../../providers/i18n/I18nProvider";
|
|
33
33
|
import {
|
|
34
34
|
listItemBaseClass,
|
|
35
|
+
listItemSizeClasses,
|
|
35
36
|
listItemSelectedClass,
|
|
36
37
|
listItemDisabledClass,
|
|
37
38
|
listItemIndentGuideClass,
|
|
38
39
|
listItemContentClass,
|
|
39
40
|
getListItemSelectedIconClass,
|
|
41
|
+
listItemBasePadLeft,
|
|
42
|
+
LIST_ITEM_INDENT_SIZE,
|
|
40
43
|
} from "../../data/list/ListItem.styles";
|
|
44
|
+
import { useListContext } from "../../data/list/ListContext";
|
|
41
45
|
|
|
42
46
|
void ripple;
|
|
43
47
|
|
|
@@ -65,13 +69,15 @@ export interface SelectContextValue<TValue = unknown> {
|
|
|
65
69
|
|
|
66
70
|
/** Register item template */
|
|
67
71
|
setItemTemplate: (fn: ((...args: unknown[]) => JSX.Element) | undefined) => void;
|
|
72
|
+
|
|
73
|
+
/** Trigger size */
|
|
74
|
+
size: Accessor<ComponentSize>;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
export const SelectContext = createContext<SelectContextValue>();
|
|
71
|
-
const SelectCtx = SelectContext;
|
|
72
78
|
|
|
73
79
|
function useSelectContext<TValue = unknown>(): SelectContextValue<TValue> {
|
|
74
|
-
const context = useContext(
|
|
80
|
+
const context = useContext(SelectContext);
|
|
75
81
|
if (!context) {
|
|
76
82
|
throw new Error("useSelectContext can only be used inside Select component");
|
|
77
83
|
}
|
|
@@ -90,6 +96,7 @@ const [SelectActionSlot, createActionSlotAccessor] = createSlot<{ children: JSX.
|
|
|
90
96
|
|
|
91
97
|
const SelectAction = (props: SelectActionProps) => {
|
|
92
98
|
const [local, rest] = splitProps(props, ["children", "class"]);
|
|
99
|
+
const ctx = useSelectContext();
|
|
93
100
|
|
|
94
101
|
const handleClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> = (e) => {
|
|
95
102
|
if (typeof rest.onClick === "function") {
|
|
@@ -104,8 +111,17 @@ const SelectAction = (props: SelectActionProps) => {
|
|
|
104
111
|
<button
|
|
105
112
|
{...rest}
|
|
106
113
|
type="button"
|
|
114
|
+
use:ripple={!rest.disabled}
|
|
107
115
|
onClick={handleClick}
|
|
108
|
-
class={twMerge(
|
|
116
|
+
class={twMerge(
|
|
117
|
+
"inline-flex items-center",
|
|
118
|
+
pad[ctx.size()],
|
|
119
|
+
"border",
|
|
120
|
+
border.default,
|
|
121
|
+
groupFocusBorderClass,
|
|
122
|
+
themeTokens.base.hoverBg,
|
|
123
|
+
local.class,
|
|
124
|
+
)}
|
|
109
125
|
data-select-action
|
|
110
126
|
>
|
|
111
127
|
{local.children}
|
|
@@ -116,6 +132,8 @@ const SelectAction = (props: SelectActionProps) => {
|
|
|
116
132
|
|
|
117
133
|
//#endregion
|
|
118
134
|
|
|
135
|
+
const groupFocusBorderClass = "group-focus-within:border-primary-400 dark:group-focus-within:border-primary-400";
|
|
136
|
+
|
|
119
137
|
const selectAllBtnClass = clsx(
|
|
120
138
|
"text-primary-500",
|
|
121
139
|
"hover:text-primary-600 dark:hover:text-primary-400",
|
|
@@ -163,9 +181,11 @@ export interface SelectItemProps<TValue = unknown> extends Omit<
|
|
|
163
181
|
const SelectItemInner = <TValue,>(
|
|
164
182
|
props: SelectItemProps<TValue> & { children?: JSX.Element },
|
|
165
183
|
) => {
|
|
166
|
-
const [local, rest] = splitProps(props, ["children", "class", "value", "disabled"]);
|
|
184
|
+
const [local, rest] = splitProps(props, ["children", "class", "style", "value", "disabled"]);
|
|
167
185
|
|
|
168
186
|
const context = useSelectContext<TValue>();
|
|
187
|
+
const listContext = useListContext();
|
|
188
|
+
const level = listContext.level;
|
|
169
189
|
|
|
170
190
|
const [childrenSlot, ItemChildrenProvider] = createItemChildrenSlotAccessor();
|
|
171
191
|
const hasChildren = () => childrenSlot() !== undefined;
|
|
@@ -186,12 +206,18 @@ const SelectItemInner = <TValue,>(
|
|
|
186
206
|
const getClassName = () =>
|
|
187
207
|
twMerge(
|
|
188
208
|
listItemBaseClass,
|
|
209
|
+
listItemSizeClasses[context.size()],
|
|
189
210
|
isSelected() && listItemSelectedClass,
|
|
190
211
|
local.disabled && listItemDisabledClass,
|
|
191
212
|
local.class,
|
|
192
213
|
);
|
|
193
214
|
|
|
194
|
-
const
|
|
215
|
+
const indentPaddingLeft = level >= 1
|
|
216
|
+
? `${listItemBasePadLeft[context.size()] + level * LIST_ITEM_INDENT_SIZE}rem`
|
|
217
|
+
: undefined;
|
|
218
|
+
|
|
219
|
+
const getGuideLeft = () =>
|
|
220
|
+
`${listItemBasePadLeft[context.size()] + level * LIST_ITEM_INDENT_SIZE + LIST_ITEM_INDENT_SIZE * 0.5}rem`;
|
|
195
221
|
|
|
196
222
|
return (
|
|
197
223
|
<ItemChildrenProvider>
|
|
@@ -200,6 +226,7 @@ const SelectItemInner = <TValue,>(
|
|
|
200
226
|
type="button"
|
|
201
227
|
use:ripple={useRipple()}
|
|
202
228
|
class={getClassName()}
|
|
229
|
+
style={{ ...(local.style as JSX.CSSProperties), "padding-left": indentPaddingLeft }}
|
|
203
230
|
data-select-item
|
|
204
231
|
data-list-item
|
|
205
232
|
role="option"
|
|
@@ -209,15 +236,15 @@ const SelectItemInner = <TValue,>(
|
|
|
209
236
|
onClick={handleClick}
|
|
210
237
|
>
|
|
211
238
|
<Show when={context.multiple() && !hasChildren()}>
|
|
212
|
-
<Icon icon={IconCheck} class={
|
|
239
|
+
<Icon icon={IconCheck} class={getListItemSelectedIconClass(isSelected())} />
|
|
213
240
|
</Show>
|
|
214
241
|
<span class={listItemContentClass}>{local.children}</span>
|
|
215
242
|
</button>
|
|
216
243
|
<Show when={hasChildren()}>
|
|
217
244
|
<Collapse open={true}>
|
|
218
|
-
<div class="
|
|
219
|
-
<div class={listItemIndentGuideClass} />
|
|
220
|
-
<List inset
|
|
245
|
+
<div class="relative">
|
|
246
|
+
<div class={listItemIndentGuideClass} style={{ left: getGuideLeft() }} />
|
|
247
|
+
<List inset>
|
|
221
248
|
{childrenSlot()!.children}
|
|
222
249
|
</List>
|
|
223
250
|
</div>
|
|
@@ -440,6 +467,7 @@ const SelectInnerComponent = <TValue,>(props: SelectProps<TValue>) => {
|
|
|
440
467
|
toggleValue,
|
|
441
468
|
closeDropdown,
|
|
442
469
|
setItemTemplate,
|
|
470
|
+
size: () => local.size ?? "md",
|
|
443
471
|
};
|
|
444
472
|
|
|
445
473
|
// Trigger keyboard handling (only Enter/Space, ArrowUp/Down handled by Dropdown)
|
|
@@ -608,7 +636,7 @@ const SelectInnerComponent = <TValue,>(props: SelectProps<TValue>) => {
|
|
|
608
636
|
local.multiple === true && !local.hideSelectAll && local.items !== undefined;
|
|
609
637
|
|
|
610
638
|
return (
|
|
611
|
-
<div {...rest} data-select class={clsx("group", local.inset ? "flex" : "inline-flex")}>
|
|
639
|
+
<div {...rest} data-select class={clsx("group", local.inset ? "flex" : "inline-flex", "[&>[data-dropdown-trigger]]:flex")}>
|
|
612
640
|
<Dropdown disabled={local.disabled} open={open()} onOpenChange={setOpen} keyboardNav>
|
|
613
641
|
<Dropdown.Trigger>
|
|
614
642
|
<div
|
|
@@ -624,7 +652,7 @@ const SelectInnerComponent = <TValue,>(props: SelectProps<TValue>) => {
|
|
|
624
652
|
action() !== undefined &&
|
|
625
653
|
clsx(
|
|
626
654
|
"rounded-r-none border-r-0",
|
|
627
|
-
|
|
655
|
+
groupFocusBorderClass,
|
|
628
656
|
),
|
|
629
657
|
)}
|
|
630
658
|
style={local.style}
|
|
@@ -698,13 +726,13 @@ const SelectInnerComponent = <TValue,>(props: SelectProps<TValue>) => {
|
|
|
698
726
|
|
|
699
727
|
return (
|
|
700
728
|
<Invalid message={errorMsg()} variant="border" lazyValidation={local.lazyValidation}>
|
|
701
|
-
<
|
|
729
|
+
<SelectContext.Provider value={contextValue as SelectContextValue}>
|
|
702
730
|
<HeaderProvider>
|
|
703
731
|
<ActionProvider>
|
|
704
732
|
<SelectInnerRender>{local.children}</SelectInnerRender>
|
|
705
733
|
</ActionProvider>
|
|
706
734
|
</HeaderProvider>
|
|
707
|
-
</
|
|
735
|
+
</SelectContext.Provider>
|
|
708
736
|
</Invalid>
|
|
709
737
|
);
|
|
710
738
|
};
|
|
@@ -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",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const starBtnSizeClasses: Record<StatePresetSize, string> = {
|
|
53
|
-
md: "p-1",
|
|
54
|
-
xs: "p-0",
|
|
55
|
-
sm: "p-0.5",
|
|
56
|
-
lg: "p-1.5",
|
|
57
|
-
xl: "p-2",
|
|
38
|
+
xl: pad.xl,
|
|
58
39
|
};
|
|
59
40
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
lg:
|
|
65
|
-
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",
|
|
66
47
|
};
|
|
67
48
|
|
|
68
|
-
const iconSize = "0.85em";
|
|
69
|
-
|
|
70
49
|
// ── Component ──
|
|
71
50
|
|
|
72
51
|
function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element {
|
|
@@ -191,43 +170,31 @@ function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element
|
|
|
191
170
|
|
|
192
171
|
// ── Render ──
|
|
193
172
|
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
const resolvedChipClass = () => twMerge(
|
|
197
|
-
clsx("inline-flex items-center", gap.md, "rounded-full", bg.subtle, text.default, "cursor-default"),
|
|
198
|
-
chipSizeClasses[local.size ?? "md"],
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const resolvedIconBtnClass = () =>
|
|
202
|
-
twMerge("rounded-full", iconBtnSizeClasses[local.size ?? "md"]);
|
|
203
|
-
|
|
204
|
-
const resolvedStarBtnClass = () =>
|
|
205
|
-
twMerge(
|
|
206
|
-
clsx("inline-flex cursor-pointer items-center justify-center rounded-full text-warning-500 transition-colors focus:outline-none", themeTokens.warning.hoverBg),
|
|
207
|
-
starBtnSizeClasses[local.size ?? "md"],
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
const resolvedInputClass = () => twMerge(
|
|
211
|
-
clsx("rounded-full", bg.subtle, text.default, "border border-transparent focus:outline-none focus:ring-1 focus:ring-primary-400", text.placeholder),
|
|
212
|
-
inputSizeClasses[local.size ?? "md"],
|
|
213
|
-
);
|
|
173
|
+
const resolvedSize = () => local.size ?? "md";
|
|
214
174
|
|
|
215
175
|
return (
|
|
216
|
-
<div class={
|
|
176
|
+
<div class={twMerge(clsx("inline-flex items-center", gap.lg, "flex-wrap"), local.class)} style={local.style}>
|
|
217
177
|
{/* Star button - add preset */}
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
178
|
+
<Button
|
|
179
|
+
size={local.size}
|
|
180
|
+
variant="ghost"
|
|
181
|
+
class="rounded-full text-warning-500"
|
|
221
182
|
onClick={handleStartAdd}
|
|
222
183
|
title={i18n.t("statePreset.addPreset")}
|
|
223
184
|
>
|
|
224
|
-
|
|
225
|
-
|
|
185
|
+
{"\u200B"}
|
|
186
|
+
<Icon icon={IconStar} size={iconSizeMap[resolvedSize()]} />
|
|
187
|
+
</Button>
|
|
226
188
|
|
|
227
189
|
{/* Preset chips */}
|
|
228
190
|
<For each={presets()}>
|
|
229
191
|
{(preset, index) => (
|
|
230
|
-
<span
|
|
192
|
+
<span
|
|
193
|
+
class={twMerge(
|
|
194
|
+
clsx("inline-flex items-center", gap.md, "rounded-full border border-transparent", bg.subtle, text.default, "cursor-default"),
|
|
195
|
+
sizeClasses[resolvedSize()],
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
231
198
|
<button
|
|
232
199
|
type="button"
|
|
233
200
|
class="cursor-pointer hover:underline focus:outline-none"
|
|
@@ -236,24 +203,22 @@ function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element
|
|
|
236
203
|
>
|
|
237
204
|
{preset.name}
|
|
238
205
|
</button>
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
class={resolvedIconBtnClass()}
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
class="cursor-pointer rounded-full opacity-50 hover:opacity-100 focus:outline-none"
|
|
243
209
|
onClick={() => handleOverwrite(index())}
|
|
244
210
|
title={i18n.t("statePreset.overwrite")}
|
|
245
211
|
>
|
|
246
|
-
<Icon icon={IconDeviceFloppy} size={
|
|
247
|
-
</
|
|
248
|
-
<
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
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"
|
|
252
217
|
onClick={() => handleDelete(index())}
|
|
253
218
|
title={i18n.t("statePreset.deletePreset")}
|
|
254
219
|
>
|
|
255
|
-
<Icon icon={IconX} size={
|
|
256
|
-
</
|
|
220
|
+
<Icon icon={IconX} size={iconSizeMap[resolvedSize()]} />
|
|
221
|
+
</button>
|
|
257
222
|
</span>
|
|
258
223
|
)}
|
|
259
224
|
</For>
|
|
@@ -266,7 +231,10 @@ function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element
|
|
|
266
231
|
requestAnimationFrame(() => el.focus());
|
|
267
232
|
}}
|
|
268
233
|
type="text"
|
|
269
|
-
class={
|
|
234
|
+
class={twMerge(
|
|
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()],
|
|
237
|
+
)}
|
|
270
238
|
placeholder={i18n.t("statePreset.namePlaceholder")}
|
|
271
239
|
autocomplete="one-time-code"
|
|
272
240
|
value={inputValue()}
|
|
@@ -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>
|
|
@@ -133,8 +133,8 @@ const TopbarInner: ParentComponent<TopbarProps> = (props) => {
|
|
|
133
133
|
return (
|
|
134
134
|
<header {...rest} data-topbar class={getClassName()}>
|
|
135
135
|
<Show when={sidebarContext}>
|
|
136
|
-
<Button variant="ghost" onClick={handleToggle}
|
|
137
|
-
<Icon icon={IconMenu2}
|
|
136
|
+
<Button variant="ghost" onClick={handleToggle} aria-label={i18n.t("topbar.toggleSidebar")}>
|
|
137
|
+
<Icon icon={IconMenu2} />
|
|
138
138
|
</Button>
|
|
139
139
|
</Show>
|
|
140
140
|
{local.children}
|
|
@@ -39,10 +39,14 @@ export function createControllableStore<TValue extends object>(options: {
|
|
|
39
39
|
|
|
40
40
|
// Wrap setter with a function wrapper to add onChange notification
|
|
41
41
|
const wrappedSet = ((...args: any[]) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (options.onChange() != null) {
|
|
43
|
+
const before = obj.clone(unwrap(store));
|
|
44
|
+
(rawSet as any)(...args);
|
|
45
|
+
if (!obj.equal(before, unwrap(store))) {
|
|
46
|
+
options.onChange()!(obj.clone(unwrap(store)));
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
(rawSet as any)(...args);
|
|
46
50
|
}
|
|
47
51
|
}) as SetStoreFunction<TValue>;
|
|
48
52
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Accessor, createSignal } from "solid-js";
|
|
2
|
+
import { json } from "@simplysm/core-common";
|
|
2
3
|
import { useConfig } from "../providers/ConfigContext";
|
|
3
4
|
|
|
4
5
|
type StorageSetter<TValue> = (
|
|
@@ -43,7 +44,7 @@ export function useLocalStorage<TValue>(
|
|
|
43
44
|
try {
|
|
44
45
|
const item = localStorage.getItem(prefixedKey);
|
|
45
46
|
if (item !== null) {
|
|
46
|
-
storedValue =
|
|
47
|
+
storedValue = json.parse<TValue>(item);
|
|
47
48
|
}
|
|
48
49
|
} catch {
|
|
49
50
|
// Use initial value on JSON parse failure
|
|
@@ -67,7 +68,7 @@ export function useLocalStorage<TValue>(
|
|
|
67
68
|
if (resolved === undefined) {
|
|
68
69
|
localStorage.removeItem(prefixedKey);
|
|
69
70
|
} else {
|
|
70
|
-
localStorage.setItem(prefixedKey,
|
|
71
|
+
localStorage.setItem(prefixedKey, json.stringify(resolved));
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
return resolved;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Accessor, type Setter, createEffect, createSignal, untrack } from "solid-js";
|
|
2
|
+
import { json } from "@simplysm/core-common";
|
|
2
3
|
import { useConfig } from "../providers/ConfigContext";
|
|
3
4
|
import { useSyncStorage } from "../providers/SyncStorageProvider";
|
|
4
5
|
|
|
@@ -49,7 +50,7 @@ export function useSyncConfig<TValue>(
|
|
|
49
50
|
try {
|
|
50
51
|
const stored = localStorage.getItem(prefixedKey);
|
|
51
52
|
if (stored !== null && writeVersion === versionBefore) {
|
|
52
|
-
setValue(() =>
|
|
53
|
+
setValue(() => json.parse<TValue>(stored));
|
|
53
54
|
}
|
|
54
55
|
} catch {
|
|
55
56
|
// Ignore parse errors, keep default value
|
|
@@ -62,14 +63,14 @@ export function useSyncConfig<TValue>(
|
|
|
62
63
|
try {
|
|
63
64
|
const stored = await currentAdapter.getItem(prefixedKey);
|
|
64
65
|
if (stored !== null && writeVersion === versionBefore) {
|
|
65
|
-
setValue(() =>
|
|
66
|
+
setValue(() => json.parse<TValue>(stored));
|
|
66
67
|
}
|
|
67
68
|
} catch {
|
|
68
69
|
// Fall back to localStorage on error
|
|
69
70
|
try {
|
|
70
71
|
const stored = localStorage.getItem(prefixedKey);
|
|
71
72
|
if (stored !== null && writeVersion === versionBefore) {
|
|
72
|
-
setValue(() =>
|
|
73
|
+
setValue(() => json.parse<TValue>(stored));
|
|
73
74
|
}
|
|
74
75
|
} catch {
|
|
75
76
|
// Ignore parse errors
|
|
@@ -84,7 +85,7 @@ export function useSyncConfig<TValue>(
|
|
|
84
85
|
createEffect(() => {
|
|
85
86
|
if (!ready()) return; // Don't save until storage has been read
|
|
86
87
|
const currentValue = value();
|
|
87
|
-
const serialized =
|
|
88
|
+
const serialized = json.stringify(currentValue);
|
|
88
89
|
|
|
89
90
|
// Read adapter untracked to avoid re-running save effect when adapter changes
|
|
90
91
|
const currentAdapter = untrack(() => syncStorageCtx?.adapter());
|
|
@@ -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: "필수 항목",
|
|
@@ -5,6 +5,7 @@ import { CrudDetailTools } from "../../../../src/components/features/crud-detail
|
|
|
5
5
|
import { CrudDetailBefore } from "../../../../src/components/features/crud-detail/CrudDetailBefore";
|
|
6
6
|
import { CrudDetailAfter } from "../../../../src/components/features/crud-detail/CrudDetailAfter";
|
|
7
7
|
import { CrudDetail } from "../../../../src/components/features/crud-detail/CrudDetail";
|
|
8
|
+
import { Dialog } from "../../../../src/components/disclosure/Dialog";
|
|
8
9
|
import { ConfigContext, ConfigProvider } from "../../../../src/providers/ConfigContext";
|
|
9
10
|
import { NotificationProvider } from "../../../../src/components/feedback/notification/NotificationProvider";
|
|
10
11
|
import { Topbar } from "../../../../src/components/layout/topbar/Topbar";
|
|
@@ -474,3 +475,51 @@ describe("CrudDetail button layout by mode", () => {
|
|
|
474
475
|
expect(container.textContent).toContain("커스텀도구");
|
|
475
476
|
});
|
|
476
477
|
});
|
|
478
|
+
|
|
479
|
+
describe("CrudDetail dialog mode layout", () => {
|
|
480
|
+
beforeEach(() => {
|
|
481
|
+
localStorage.setItem("test.i18n-locale", JSON.stringify("en"));
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
afterEach(() => {
|
|
485
|
+
localStorage.removeItem("test.i18n-locale");
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it("bottom bar's direct parent should not have gap-2 class", async () => {
|
|
489
|
+
render(() => (
|
|
490
|
+
<ConfigProvider clientName="test"><I18nProvider>
|
|
491
|
+
<TestWrapper>
|
|
492
|
+
<Dialog open={true}>
|
|
493
|
+
<Dialog.Header>Test</Dialog.Header>
|
|
494
|
+
<CrudDetail<TestData>
|
|
495
|
+
load={() =>
|
|
496
|
+
Promise.resolve({
|
|
497
|
+
data: { id: 1, name: "홍길동" },
|
|
498
|
+
info: { isNew: false, isDeleted: false },
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
submit={() => Promise.resolve(true)}
|
|
502
|
+
close={() => {}}
|
|
503
|
+
>
|
|
504
|
+
{(ctx) => <div>{ctx.data.name}</div>}
|
|
505
|
+
</CrudDetail>
|
|
506
|
+
</Dialog>
|
|
507
|
+
</TestWrapper>
|
|
508
|
+
</I18nProvider></ConfigProvider>
|
|
509
|
+
));
|
|
510
|
+
|
|
511
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
512
|
+
|
|
513
|
+
// Find the bottom bar via Confirm button
|
|
514
|
+
const confirmBtn = Array.from(document.querySelectorAll("button")).find(
|
|
515
|
+
(btn) => btn.textContent.includes("Confirm"),
|
|
516
|
+
);
|
|
517
|
+
expect(confirmBtn).toBeTruthy();
|
|
518
|
+
|
|
519
|
+
const bottomBar = confirmBtn!.closest(".border-t");
|
|
520
|
+
expect(bottomBar).toBeTruthy();
|
|
521
|
+
|
|
522
|
+
// Bottom bar's direct parent should NOT have gap-2 class
|
|
523
|
+
expect(bottomBar!.parentElement!.classList.contains("gap-2")).toBe(false);
|
|
524
|
+
});
|
|
525
|
+
});
|