@simplysm/solid 13.0.0-beta.21 → 13.0.0-beta.23
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 +481 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -303,6 +303,17 @@ import { Select } from "@simplysm/solid";
|
|
|
303
303
|
// Multiple selection
|
|
304
304
|
<Select items={options} value={selected()} onValueChange={setSelected} multiple />
|
|
305
305
|
|
|
306
|
+
// Hierarchical items (children approach)
|
|
307
|
+
<Select value={item()} onValueChange={setItem} renderValue={(v) => v.name}>
|
|
308
|
+
<Select.Item value={parent}>
|
|
309
|
+
{parent.name}
|
|
310
|
+
<Select.Item.Children>
|
|
311
|
+
<Select.Item value={child1}>{child1.name}</Select.Item>
|
|
312
|
+
<Select.Item value={child2}>{child2.name}</Select.Item>
|
|
313
|
+
</Select.Item.Children>
|
|
314
|
+
</Select.Item>
|
|
315
|
+
</Select>
|
|
316
|
+
|
|
306
317
|
// With action buttons and header
|
|
307
318
|
<Select value={item()} onValueChange={setItem} renderValue={(v) => v.name}>
|
|
308
319
|
<Select.Header><div>Custom header</div></Select.Header>
|
|
@@ -329,6 +340,7 @@ import { Select } from "@simplysm/solid";
|
|
|
329
340
|
|
|
330
341
|
**Sub-components:**
|
|
331
342
|
- `Select.Item` -- Selection item
|
|
343
|
+
- `Select.Item.Children` -- Nested child items container (for hierarchical selection)
|
|
332
344
|
- `Select.Action` -- Right-side action button
|
|
333
345
|
- `Select.Header` -- Dropdown top custom area
|
|
334
346
|
- `Select.ItemTemplate` -- Item rendering template for items approach
|
|
@@ -741,7 +753,7 @@ import { Echarts } from "@simplysm/solid";
|
|
|
741
753
|
Sidebar navigation with responsive support (mobile overlay below 520px). Open/closed state is saved in localStorage.
|
|
742
754
|
|
|
743
755
|
```tsx
|
|
744
|
-
import { Sidebar } from "@simplysm/solid";
|
|
756
|
+
import { Sidebar, Topbar } from "@simplysm/solid";
|
|
745
757
|
|
|
746
758
|
<Sidebar.Container>
|
|
747
759
|
<Sidebar>
|
|
@@ -766,6 +778,27 @@ import { Sidebar } from "@simplysm/solid";
|
|
|
766
778
|
- `Sidebar.Menu` -- Menu items list (`menus: SidebarMenuItem[]`)
|
|
767
779
|
- `Sidebar.User` -- User info area
|
|
768
780
|
|
|
781
|
+
**SidebarMenuItem type:**
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
interface SidebarMenuItem {
|
|
785
|
+
title: string;
|
|
786
|
+
href?: string;
|
|
787
|
+
icon?: Component<IconProps>;
|
|
788
|
+
children?: SidebarMenuItem[];
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
**useSidebarContext hook:**
|
|
793
|
+
|
|
794
|
+
```tsx
|
|
795
|
+
import { useSidebarContext } from "@simplysm/solid";
|
|
796
|
+
|
|
797
|
+
const sidebar = useSidebarContext();
|
|
798
|
+
sidebar.toggle(); // current open/closed state
|
|
799
|
+
sidebar.setToggle(false); // programmatically close
|
|
800
|
+
```
|
|
801
|
+
|
|
769
802
|
---
|
|
770
803
|
|
|
771
804
|
#### Topbar
|
|
@@ -774,6 +807,24 @@ Top navigation bar. When used inside `Sidebar.Container`, a sidebar toggle butto
|
|
|
774
807
|
|
|
775
808
|
```tsx
|
|
776
809
|
import { Topbar } from "@simplysm/solid";
|
|
810
|
+
import { IconSettings, IconUser } from "@tabler/icons-solidjs";
|
|
811
|
+
|
|
812
|
+
const menuItems: TopbarMenuItem[] = [
|
|
813
|
+
{ title: "Settings", icon: IconSettings, href: "/settings" },
|
|
814
|
+
{
|
|
815
|
+
title: "Admin",
|
|
816
|
+
icon: IconUser,
|
|
817
|
+
children: [
|
|
818
|
+
{ title: "Users", href: "/admin/users" },
|
|
819
|
+
{ title: "Roles", href: "/admin/roles" },
|
|
820
|
+
],
|
|
821
|
+
},
|
|
822
|
+
];
|
|
823
|
+
|
|
824
|
+
const userMenus: TopbarUserMenu[] = [
|
|
825
|
+
{ title: "Profile", onClick: () => navigate("/profile") },
|
|
826
|
+
{ title: "Logout", onClick: handleLogout },
|
|
827
|
+
];
|
|
777
828
|
|
|
778
829
|
<Topbar>
|
|
779
830
|
<h1 class="text-lg font-bold">App Name</h1>
|
|
@@ -788,6 +839,26 @@ import { Topbar } from "@simplysm/solid";
|
|
|
788
839
|
- `Topbar.Menu` -- Menu items list
|
|
789
840
|
- `Topbar.User` -- User menu (dropdown)
|
|
790
841
|
|
|
842
|
+
**TopbarMenuItem type:**
|
|
843
|
+
|
|
844
|
+
```typescript
|
|
845
|
+
interface TopbarMenuItem {
|
|
846
|
+
title: string;
|
|
847
|
+
href?: string;
|
|
848
|
+
icon?: Component<IconProps>;
|
|
849
|
+
children?: TopbarMenuItem[]; // supports unlimited nesting
|
|
850
|
+
}
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
**TopbarUserMenu type:**
|
|
854
|
+
|
|
855
|
+
```typescript
|
|
856
|
+
interface TopbarUserMenu {
|
|
857
|
+
title: string;
|
|
858
|
+
onClick: () => void;
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
791
862
|
---
|
|
792
863
|
|
|
793
864
|
#### FormGroup
|
|
@@ -854,7 +925,90 @@ import { FormTable, TextInput, NumberInput } from "@simplysm/solid";
|
|
|
854
925
|
|
|
855
926
|
#### Kanban
|
|
856
927
|
|
|
857
|
-
Kanban board layout component.
|
|
928
|
+
Kanban board layout component with drag-and-drop cards, lane collapse, multi-select, and loading states.
|
|
929
|
+
|
|
930
|
+
```tsx
|
|
931
|
+
import { createSignal, For } from "solid-js";
|
|
932
|
+
import { Button, Icon, Kanban, type KanbanDropInfo } from "@simplysm/solid";
|
|
933
|
+
import { IconPlus } from "@tabler/icons-solidjs";
|
|
934
|
+
|
|
935
|
+
const [selected, setSelected] = createSignal<unknown[]>([]);
|
|
936
|
+
|
|
937
|
+
const handleDrop = (info: KanbanDropInfo) => {
|
|
938
|
+
// info.sourceValue: dragged card value
|
|
939
|
+
// info.targetLaneValue: target lane value
|
|
940
|
+
// info.targetCardValue: target card value (undefined if dropped on empty area)
|
|
941
|
+
// info.position: "before" | "after" | undefined
|
|
942
|
+
moveCard(info);
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
<div class="h-[500px]">
|
|
946
|
+
<Kanban
|
|
947
|
+
selectedValues={selected()}
|
|
948
|
+
onSelectedValuesChange={setSelected}
|
|
949
|
+
onDrop={handleDrop}
|
|
950
|
+
>
|
|
951
|
+
<For each={lanes()}>
|
|
952
|
+
{(lane) => (
|
|
953
|
+
<Kanban.Lane value={lane.id} collapsible busy={lane.loading}>
|
|
954
|
+
<Kanban.LaneTitle>
|
|
955
|
+
{lane.title} ({lane.cards.length})
|
|
956
|
+
</Kanban.LaneTitle>
|
|
957
|
+
<Kanban.LaneTools>
|
|
958
|
+
<Button size="sm" variant="ghost">
|
|
959
|
+
<Icon icon={IconPlus} />
|
|
960
|
+
</Button>
|
|
961
|
+
</Kanban.LaneTools>
|
|
962
|
+
<For each={lane.cards}>
|
|
963
|
+
{(card) => (
|
|
964
|
+
<Kanban.Card value={card.id} selectable draggable contentClass="p-2">
|
|
965
|
+
{card.title}
|
|
966
|
+
</Kanban.Card>
|
|
967
|
+
)}
|
|
968
|
+
</For>
|
|
969
|
+
</Kanban.Lane>
|
|
970
|
+
)}
|
|
971
|
+
</For>
|
|
972
|
+
</Kanban>
|
|
973
|
+
</div>
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
**Kanban Props:**
|
|
977
|
+
|
|
978
|
+
| Prop | Type | Default | Description |
|
|
979
|
+
|------|------|---------|-------------|
|
|
980
|
+
| `onDrop` | `(info: KanbanDropInfo) => void` | - | Drop event handler |
|
|
981
|
+
| `selectedValues` | `unknown[]` | - | Selected card values |
|
|
982
|
+
| `onSelectedValuesChange` | `(values: unknown[]) => void` | - | Selection change callback |
|
|
983
|
+
|
|
984
|
+
`KanbanDropInfo`: `{ sourceValue: unknown; targetLaneValue: unknown; targetCardValue: unknown | undefined; position: "before" | "after" | undefined }`
|
|
985
|
+
|
|
986
|
+
**Kanban.Lane Props:**
|
|
987
|
+
|
|
988
|
+
| Prop | Type | Default | Description |
|
|
989
|
+
|------|------|---------|-------------|
|
|
990
|
+
| `value` | `unknown` | - | Lane identifier |
|
|
991
|
+
| `busy` | `boolean` | - | Show loading bar |
|
|
992
|
+
| `collapsible` | `boolean` | - | Allow collapse/expand |
|
|
993
|
+
| `collapsed` | `boolean` | - | Collapsed state (controlled) |
|
|
994
|
+
| `onCollapsedChange` | `(collapsed: boolean) => void` | - | Collapse state callback |
|
|
995
|
+
|
|
996
|
+
**Kanban.Card Props:**
|
|
997
|
+
|
|
998
|
+
| Prop | Type | Default | Description |
|
|
999
|
+
|------|------|---------|-------------|
|
|
1000
|
+
| `value` | `unknown` | - | Card identifier |
|
|
1001
|
+
| `draggable` | `boolean` | `true` | Enable drag |
|
|
1002
|
+
| `selectable` | `boolean` | `false` | Enable selection |
|
|
1003
|
+
| `contentClass` | `string` | - | Card content class |
|
|
1004
|
+
|
|
1005
|
+
**Sub-components:**
|
|
1006
|
+
- `Kanban.Lane` -- Board lane/column
|
|
1007
|
+
- `Kanban.LaneTitle` -- Lane header title area
|
|
1008
|
+
- `Kanban.LaneTools` -- Lane header action buttons
|
|
1009
|
+
- `Kanban.Card` -- Draggable card
|
|
1010
|
+
|
|
1011
|
+
**Selection:** Shift+Click for multi-select, long press for single select. Lane header checkbox toggles all cards in the lane.
|
|
858
1012
|
|
|
859
1013
|
---
|
|
860
1014
|
|
|
@@ -889,19 +1043,20 @@ import { Table } from "@simplysm/solid";
|
|
|
889
1043
|
|
|
890
1044
|
#### DataSheet
|
|
891
1045
|
|
|
892
|
-
Advanced data table component. Supports sorting, pagination, row selection, tree expansion, column resize, column configuration, and
|
|
1046
|
+
Advanced data table component. Supports sorting, pagination, row selection, tree expansion, column resize, column configuration, drag-and-drop reordering, and persistent column settings.
|
|
893
1047
|
|
|
894
1048
|
```tsx
|
|
895
1049
|
import { DataSheet } from "@simplysm/solid";
|
|
896
1050
|
|
|
897
|
-
|
|
898
|
-
|
|
1051
|
+
// Basic usage
|
|
1052
|
+
<DataSheet items={users()} persistKey="user-table">
|
|
1053
|
+
<DataSheet.Column key="name" header="Name" sortable class="px-2 py-1">
|
|
899
1054
|
{({ item }) => <>{item.name}</>}
|
|
900
1055
|
</DataSheet.Column>
|
|
901
|
-
<DataSheet.Column key="age" header="Age" sortable width="80px">
|
|
1056
|
+
<DataSheet.Column key="age" header="Age" sortable width="80px" class="px-2 py-1">
|
|
902
1057
|
{({ item }) => <>{item.age}</>}
|
|
903
1058
|
</DataSheet.Column>
|
|
904
|
-
<DataSheet.Column key="email" header="Email">
|
|
1059
|
+
<DataSheet.Column key="email" header="Email" class="px-2 py-1">
|
|
905
1060
|
{({ item }) => <>{item.email}</>}
|
|
906
1061
|
</DataSheet.Column>
|
|
907
1062
|
</DataSheet>
|
|
@@ -909,9 +1064,10 @@ import { DataSheet } from "@simplysm/solid";
|
|
|
909
1064
|
// With pagination + sorting + selection
|
|
910
1065
|
<DataSheet
|
|
911
1066
|
items={data()}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1067
|
+
persistKey="data-table"
|
|
1068
|
+
pageIndex={pageIndex()}
|
|
1069
|
+
onPageIndexChange={setPageIndex}
|
|
1070
|
+
itemsPerPage={20}
|
|
915
1071
|
totalPageCount={totalPages()}
|
|
916
1072
|
sorts={sorts()}
|
|
917
1073
|
onSortsChange={setSorts}
|
|
@@ -921,26 +1077,66 @@ import { DataSheet } from "@simplysm/solid";
|
|
|
921
1077
|
>
|
|
922
1078
|
{/* columns */}
|
|
923
1079
|
</DataSheet>
|
|
1080
|
+
|
|
1081
|
+
// Tree structure with expansion
|
|
1082
|
+
<DataSheet
|
|
1083
|
+
items={treeData()}
|
|
1084
|
+
persistKey="tree-table"
|
|
1085
|
+
getChildren={(item) => item.children}
|
|
1086
|
+
expandedItems={expanded()}
|
|
1087
|
+
onExpandedItemsChange={setExpanded}
|
|
1088
|
+
>
|
|
1089
|
+
{/* columns */}
|
|
1090
|
+
</DataSheet>
|
|
1091
|
+
|
|
1092
|
+
// Auto-select on row click + drag reorder
|
|
1093
|
+
<DataSheet
|
|
1094
|
+
items={items()}
|
|
1095
|
+
persistKey="reorder-table"
|
|
1096
|
+
autoSelect="click"
|
|
1097
|
+
selectMode="single"
|
|
1098
|
+
selectedItems={selected()}
|
|
1099
|
+
onSelectedItemsChange={setSelected}
|
|
1100
|
+
onItemsReorder={(e) => {
|
|
1101
|
+
// e: { item: T, targetItem: T, position: "before" | "after" | "inside" }
|
|
1102
|
+
reorderItems(e);
|
|
1103
|
+
}}
|
|
1104
|
+
>
|
|
1105
|
+
{/* columns */}
|
|
1106
|
+
</DataSheet>
|
|
924
1107
|
```
|
|
925
1108
|
|
|
926
|
-
**DataSheet Props
|
|
1109
|
+
**DataSheet Props:**
|
|
927
1110
|
|
|
928
1111
|
| Prop | Type | Default | Description |
|
|
929
1112
|
|------|------|---------|-------------|
|
|
930
1113
|
| `items` | `T[]` | - | Data array |
|
|
931
|
-
| `
|
|
1114
|
+
| `persistKey` | `string` | - | Column configuration localStorage key |
|
|
1115
|
+
| `class` | `string` | - | CSS class |
|
|
1116
|
+
| `contentStyle` | `JSX.CSSProperties \| string` | - | Scroll area style |
|
|
932
1117
|
| `inset` | `boolean` | - | Inset style |
|
|
933
|
-
| `
|
|
1118
|
+
| `hideConfigBar` | `boolean` | - | Hide config bar and pagination |
|
|
1119
|
+
| `sorts` | `SortingDef[]` | - | Sort state (`{ key: string; desc: boolean }[]`) |
|
|
934
1120
|
| `onSortsChange` | `(sorts: SortingDef[]) => void` | - | Sort change callback |
|
|
935
1121
|
| `autoSort` | `boolean` | - | Client-side auto-sorting |
|
|
936
|
-
| `
|
|
937
|
-
| `
|
|
1122
|
+
| `pageIndex` | `number` | - | Current page index (0-based) |
|
|
1123
|
+
| `onPageIndexChange` | `(pageIndex: number) => void` | - | Page change callback |
|
|
938
1124
|
| `totalPageCount` | `number` | - | Total page count |
|
|
1125
|
+
| `itemsPerPage` | `number` | - | Items per page |
|
|
1126
|
+
| `displayPageCount` | `number` | - | Number of page buttons to display |
|
|
939
1127
|
| `selectMode` | `"single" \| "multiple"` | - | Selection mode |
|
|
940
1128
|
| `selectedItems` | `T[]` | - | Selected items |
|
|
941
1129
|
| `onSelectedItemsChange` | `(items: T[]) => void` | - | Selection change callback |
|
|
1130
|
+
| `autoSelect` | `"click"` | - | Auto-select row on click |
|
|
1131
|
+
| `isItemSelectable` | `(item: T) => boolean \| string` | - | Item selectability (string returns tooltip) |
|
|
942
1132
|
| `getChildren` | `(item: T, index: number) => T[] \| undefined` | - | Tree structure children getter |
|
|
943
|
-
| `
|
|
1133
|
+
| `expandedItems` | `T[]` | - | Expanded tree items |
|
|
1134
|
+
| `onExpandedItemsChange` | `(items: T[]) => void` | - | Expansion state change callback |
|
|
1135
|
+
| `cellClass` | `(item: T, colKey: string) => string \| undefined` | - | Dynamic cell class function |
|
|
1136
|
+
| `cellStyle` | `(item: T, colKey: string) => string \| undefined` | - | Dynamic cell style function |
|
|
1137
|
+
| `onItemsReorder` | `(event: DataSheetReorderEvent<T>) => void` | - | Drag reorder handler (shows drag handle when set) |
|
|
1138
|
+
|
|
1139
|
+
`DataSheetReorderEvent<T>`: `{ item: T; targetItem: T; position: "before" | "after" | "inside" }`
|
|
944
1140
|
|
|
945
1141
|
**DataSheet.Column Props:**
|
|
946
1142
|
|
|
@@ -949,12 +1145,16 @@ import { DataSheet } from "@simplysm/solid";
|
|
|
949
1145
|
| `key` | `string` | **(required)** | Column identifier key |
|
|
950
1146
|
| `header` | `string \| string[]` | - | Header text (array for multi-level headers) |
|
|
951
1147
|
| `headerContent` | `() => JSX.Element` | - | Custom header rendering |
|
|
1148
|
+
| `headerStyle` | `string` | - | Header style |
|
|
952
1149
|
| `summary` | `() => JSX.Element` | - | Summary row rendering |
|
|
953
|
-
| `
|
|
954
|
-
| `
|
|
955
|
-
| `
|
|
956
|
-
| `
|
|
957
|
-
| `
|
|
1150
|
+
| `tooltip` | `string` | - | Header tooltip |
|
|
1151
|
+
| `width` | `string` | - | Column width (e.g., `"100px"`, `"10rem"`) |
|
|
1152
|
+
| `class` | `string` | - | Cell CSS class |
|
|
1153
|
+
| `fixed` | `boolean` | `false` | Fixed column |
|
|
1154
|
+
| `hidden` | `boolean` | `false` | Hidden column |
|
|
1155
|
+
| `collapse` | `boolean` | `false` | Hidden in config modal |
|
|
1156
|
+
| `sortable` | `boolean` | `true` | Sortable |
|
|
1157
|
+
| `resizable` | `boolean` | `true` | Resizable |
|
|
958
1158
|
| `children` | `(ctx: { item: T, index: number, depth: number }) => JSX.Element` | **(required)** | Cell rendering function |
|
|
959
1159
|
|
|
960
1160
|
---
|
|
@@ -1051,7 +1251,71 @@ import { Calendar } from "@simplysm/solid";
|
|
|
1051
1251
|
|
|
1052
1252
|
#### PermissionTable
|
|
1053
1253
|
|
|
1054
|
-
|
|
1254
|
+
Hierarchical permission management table. Displays a tree of permission items with per-item checkboxes for each permission type. Supports cascading checks (parent toggles children) and permission dependencies (disabling the first permission disables the rest).
|
|
1255
|
+
|
|
1256
|
+
```tsx
|
|
1257
|
+
import { createSignal } from "solid-js";
|
|
1258
|
+
import { type PermissionItem, PermissionTable } from "@simplysm/solid";
|
|
1259
|
+
|
|
1260
|
+
const items: PermissionItem[] = [
|
|
1261
|
+
{
|
|
1262
|
+
title: "User Management",
|
|
1263
|
+
href: "/user",
|
|
1264
|
+
perms: ["use", "edit"],
|
|
1265
|
+
children: [
|
|
1266
|
+
{ title: "Permission Settings", href: "/user/permission", perms: ["use", "edit", "approve"] },
|
|
1267
|
+
{ title: "User List", href: "/user/list", perms: ["use", "edit"] },
|
|
1268
|
+
],
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
title: "Board",
|
|
1272
|
+
href: "/board",
|
|
1273
|
+
perms: ["use", "edit"],
|
|
1274
|
+
modules: ["community"], // only shown when "community" module is active
|
|
1275
|
+
children: [
|
|
1276
|
+
{ title: "Notice", href: "/board/notice", perms: ["use", "edit"] },
|
|
1277
|
+
{ title: "Free Board", href: "/board/free", perms: ["use"] },
|
|
1278
|
+
],
|
|
1279
|
+
},
|
|
1280
|
+
];
|
|
1281
|
+
|
|
1282
|
+
const [value, setValue] = createSignal<Record<string, boolean>>({});
|
|
1283
|
+
|
|
1284
|
+
// Basic usage
|
|
1285
|
+
<PermissionTable items={items} value={value()} onValueChange={setValue} />
|
|
1286
|
+
|
|
1287
|
+
// Filtered by module
|
|
1288
|
+
<PermissionTable items={items} value={value()} onValueChange={setValue} modules={["community"]} />
|
|
1289
|
+
|
|
1290
|
+
// Disabled
|
|
1291
|
+
<PermissionTable items={items} value={value()} disabled />
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
The `value` record uses keys in `"{href}/{perm}"` format (e.g., `{ "/user/use": true, "/user/edit": false }`).
|
|
1295
|
+
|
|
1296
|
+
**PermissionTable Props:**
|
|
1297
|
+
|
|
1298
|
+
| Prop | Type | Default | Description |
|
|
1299
|
+
|------|------|---------|-------------|
|
|
1300
|
+
| `items` | `PermissionItem<TModule>[]` | - | Permission tree structure |
|
|
1301
|
+
| `value` | `Record<string, boolean>` | - | Permission state record |
|
|
1302
|
+
| `onValueChange` | `(value: Record<string, boolean>) => void` | - | State change callback |
|
|
1303
|
+
| `modules` | `TModule[]` | - | Module filter (show only matching items) |
|
|
1304
|
+
| `disabled` | `boolean` | - | Disable all checkboxes |
|
|
1305
|
+
|
|
1306
|
+
**PermissionItem type:**
|
|
1307
|
+
|
|
1308
|
+
```typescript
|
|
1309
|
+
interface PermissionItem<TModule = string> {
|
|
1310
|
+
title: string; // Display text
|
|
1311
|
+
href?: string; // Permission path (used as value key prefix)
|
|
1312
|
+
modules?: TModule[]; // Modules this item belongs to
|
|
1313
|
+
perms?: string[]; // Permission types (e.g., ["use", "edit", "approve"])
|
|
1314
|
+
children?: PermissionItem<TModule>[]; // Child items
|
|
1315
|
+
}
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
**Cascading behavior:** Checking a parent checks all children. Unchecking `perms[0]` (base permission) automatically unchecks all other permissions for that item.
|
|
1055
1319
|
|
|
1056
1320
|
---
|
|
1057
1321
|
|
|
@@ -1125,7 +1389,9 @@ let triggerRef!: HTMLButtonElement;
|
|
|
1125
1389
|
|
|
1126
1390
|
#### Dialog
|
|
1127
1391
|
|
|
1128
|
-
Modal dialog component. Supports drag movement, resize, floating mode, and
|
|
1392
|
+
Modal dialog component. Supports drag movement, resize, floating mode, fullscreen mode, and programmatic opening via `useDialog`.
|
|
1393
|
+
|
|
1394
|
+
**Declarative usage:**
|
|
1129
1395
|
|
|
1130
1396
|
```tsx
|
|
1131
1397
|
import { Dialog, Button } from "@simplysm/solid";
|
|
@@ -1158,6 +1424,48 @@ const [open, setOpen] = createSignal(false);
|
|
|
1158
1424
|
</Dialog>
|
|
1159
1425
|
```
|
|
1160
1426
|
|
|
1427
|
+
**Programmatic usage with `useDialog`:**
|
|
1428
|
+
|
|
1429
|
+
```tsx
|
|
1430
|
+
import { useDialog, useDialogInstance, Button, TextInput } from "@simplysm/solid";
|
|
1431
|
+
import { createSignal } from "solid-js";
|
|
1432
|
+
|
|
1433
|
+
// Dialog content component
|
|
1434
|
+
function EditDialog() {
|
|
1435
|
+
const dialogInstance = useDialogInstance<string>();
|
|
1436
|
+
const [name, setName] = createSignal("");
|
|
1437
|
+
|
|
1438
|
+
return (
|
|
1439
|
+
<div class="p-4 space-y-4">
|
|
1440
|
+
<TextInput value={name()} onValueChange={setName} placeholder="Enter name" />
|
|
1441
|
+
<Button theme="primary" onClick={() => dialogInstance?.close(name())}>
|
|
1442
|
+
Save
|
|
1443
|
+
</Button>
|
|
1444
|
+
</div>
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// Opening dialog programmatically
|
|
1449
|
+
function MyPage() {
|
|
1450
|
+
const dialog = useDialog();
|
|
1451
|
+
|
|
1452
|
+
const handleOpen = async () => {
|
|
1453
|
+
const result = await dialog.show<string>(
|
|
1454
|
+
() => <EditDialog />,
|
|
1455
|
+
{ title: "Edit Name", widthPx: 400, closeOnBackdrop: true },
|
|
1456
|
+
);
|
|
1457
|
+
if (result != null) {
|
|
1458
|
+
// result is the value passed to dialogInstance.close()
|
|
1459
|
+
console.log("Saved:", result);
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
return <Button onClick={handleOpen}>Open Editor</Button>;
|
|
1464
|
+
}
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
**Dialog Props:**
|
|
1468
|
+
|
|
1161
1469
|
| Prop | Type | Default | Description |
|
|
1162
1470
|
|------|------|---------|-------------|
|
|
1163
1471
|
| `open` | `boolean` | - | Open state |
|
|
@@ -1177,9 +1485,24 @@ const [open, setOpen] = createSignal(false);
|
|
|
1177
1485
|
| `minHeightPx` | `number` | - | Minimum height (px) |
|
|
1178
1486
|
| `position` | `"bottom-right" \| "top-right"` | - | Fixed position |
|
|
1179
1487
|
| `headerAction` | `JSX.Element` | - | Header action area |
|
|
1488
|
+
| `headerStyle` | `JSX.CSSProperties \| string` | - | Header style |
|
|
1180
1489
|
| `canDeactivate` | `() => boolean` | - | Pre-close confirmation function |
|
|
1181
1490
|
| `onCloseComplete` | `() => void` | - | Post-close animation callback |
|
|
1182
1491
|
|
|
1492
|
+
**useDialog API:**
|
|
1493
|
+
|
|
1494
|
+
| Method | Signature | Description |
|
|
1495
|
+
|--------|-----------|-------------|
|
|
1496
|
+
| `show` | `<T>(factory: () => JSX.Element, options: DialogShowOptions) => Promise<T \| undefined>` | Open dialog, returns result on close |
|
|
1497
|
+
|
|
1498
|
+
`DialogShowOptions` accepts all Dialog props except `open`, `onOpenChange`, and `children`.
|
|
1499
|
+
|
|
1500
|
+
**useDialogInstance API:**
|
|
1501
|
+
|
|
1502
|
+
| Method | Signature | Description |
|
|
1503
|
+
|--------|-----------|-------------|
|
|
1504
|
+
| `close` | `(result?: T) => void` | Close dialog with optional return value |
|
|
1505
|
+
|
|
1183
1506
|
---
|
|
1184
1507
|
|
|
1185
1508
|
### Feedback
|
|
@@ -1336,6 +1659,43 @@ function MyComponent() {
|
|
|
1336
1659
|
**Sub-components:**
|
|
1337
1660
|
- `Print.Page` -- Explicit page breaks (auto-breaks if not used)
|
|
1338
1661
|
|
|
1662
|
+
**usePrintInstance (for async data in print content):**
|
|
1663
|
+
|
|
1664
|
+
Use `usePrintInstance` inside print content components when you need to load async data before rendering. Call `ready()` to signal that the content is ready to print.
|
|
1665
|
+
|
|
1666
|
+
```tsx
|
|
1667
|
+
import { usePrintInstance } from "@simplysm/solid";
|
|
1668
|
+
import { createResource, Show } from "solid-js";
|
|
1669
|
+
|
|
1670
|
+
function InvoicePrintContent(props: { invoiceId: number }) {
|
|
1671
|
+
const printInstance = usePrintInstance();
|
|
1672
|
+
const [invoice] = createResource(() => fetchInvoice(props.invoiceId));
|
|
1673
|
+
|
|
1674
|
+
createEffect(() => {
|
|
1675
|
+
if (invoice()) {
|
|
1676
|
+
printInstance?.ready(); // signal that content is ready
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
return (
|
|
1681
|
+
<Show when={invoice()}>
|
|
1682
|
+
{(inv) => (
|
|
1683
|
+
<Print>
|
|
1684
|
+
<Print.Page>
|
|
1685
|
+
<h1>Invoice #{inv().id}</h1>
|
|
1686
|
+
{/* invoice content */}
|
|
1687
|
+
</Print.Page>
|
|
1688
|
+
</Print>
|
|
1689
|
+
)}
|
|
1690
|
+
</Show>
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// Usage
|
|
1695
|
+
const { toPrinter } = usePrint();
|
|
1696
|
+
await toPrinter(() => <InvoicePrintContent invoiceId={123} />, { size: "A4", margin: "10mm" });
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1339
1699
|
---
|
|
1340
1700
|
|
|
1341
1701
|
## Context & Hooks
|
|
@@ -1481,35 +1841,98 @@ const navigate = useRouterLink();
|
|
|
1481
1841
|
|
|
1482
1842
|
### createAppStructure
|
|
1483
1843
|
|
|
1484
|
-
Utility for declaratively defining app structure (routing, menus, permissions).
|
|
1844
|
+
Utility for declaratively defining app structure (routing, menus, permissions). Takes a single options object.
|
|
1485
1845
|
|
|
1486
1846
|
```tsx
|
|
1487
1847
|
import { createAppStructure, type AppStructureItem } from "@simplysm/solid";
|
|
1848
|
+
import { IconHome, IconUsers } from "@tabler/icons-solidjs";
|
|
1488
1849
|
|
|
1489
1850
|
const items: AppStructureItem<string>[] = [
|
|
1490
1851
|
{
|
|
1491
1852
|
code: "home",
|
|
1492
1853
|
title: "Home",
|
|
1854
|
+
icon: IconHome,
|
|
1493
1855
|
component: HomePage,
|
|
1494
1856
|
perms: ["use"],
|
|
1495
1857
|
},
|
|
1496
1858
|
{
|
|
1497
1859
|
code: "admin",
|
|
1498
1860
|
title: "Admin",
|
|
1861
|
+
icon: IconUsers,
|
|
1499
1862
|
children: [
|
|
1500
1863
|
{ code: "users", title: "User Management", component: UsersPage, perms: ["use", "edit"] },
|
|
1864
|
+
{ code: "roles", title: "Role Management", component: RolesPage, perms: ["use"], isNotMenu: true },
|
|
1501
1865
|
],
|
|
1502
1866
|
},
|
|
1503
1867
|
];
|
|
1504
1868
|
|
|
1505
|
-
const structure = createAppStructure(
|
|
1506
|
-
|
|
1507
|
-
|
|
1869
|
+
const structure = createAppStructure({
|
|
1870
|
+
items,
|
|
1871
|
+
usableModules: () => activeModules(), // optional: filter by active modules
|
|
1872
|
+
permRecord: () => userPermissions(), // optional: user permission state
|
|
1508
1873
|
});
|
|
1509
1874
|
|
|
1510
|
-
// structure.routes
|
|
1511
|
-
// structure.usableMenus()
|
|
1512
|
-
// structure.
|
|
1875
|
+
// structure.routes -- Route array (pass to @solidjs/router)
|
|
1876
|
+
// structure.usableMenus() -- SidebarMenuItem[] for Sidebar.Menu
|
|
1877
|
+
// structure.usableFlatMenus() -- Flat menu list
|
|
1878
|
+
// structure.permRecord() -- Record<string, boolean> permission state
|
|
1879
|
+
```
|
|
1880
|
+
|
|
1881
|
+
**AppStructureItem types:**
|
|
1882
|
+
|
|
1883
|
+
```typescript
|
|
1884
|
+
// Group item (has children, no component)
|
|
1885
|
+
interface AppStructureGroupItem<TModule> {
|
|
1886
|
+
code: string;
|
|
1887
|
+
title: string;
|
|
1888
|
+
icon?: Component<IconProps>;
|
|
1889
|
+
modules?: TModule[];
|
|
1890
|
+
requiredModules?: TModule[];
|
|
1891
|
+
children: AppStructureItem<TModule>[];
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// Leaf item (has component, no children)
|
|
1895
|
+
interface AppStructureLeafItem<TModule> {
|
|
1896
|
+
code: string;
|
|
1897
|
+
title: string;
|
|
1898
|
+
icon?: Component<IconProps>;
|
|
1899
|
+
modules?: TModule[];
|
|
1900
|
+
requiredModules?: TModule[];
|
|
1901
|
+
component?: Component;
|
|
1902
|
+
perms?: ("use" | "edit")[];
|
|
1903
|
+
subPerms?: AppStructureSubPerm<TModule>[];
|
|
1904
|
+
isNotMenu?: boolean; // exclude from menu but include in routing
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
type AppStructureItem<TModule> = AppStructureGroupItem<TModule> | AppStructureLeafItem<TModule>;
|
|
1908
|
+
```
|
|
1909
|
+
|
|
1910
|
+
---
|
|
1911
|
+
|
|
1912
|
+
## Providers
|
|
1913
|
+
|
|
1914
|
+
### ServiceClientProvider
|
|
1915
|
+
|
|
1916
|
+
WebSocket client provider for RPC communication with `@simplysm/service-server`. Wraps `ServiceClient` from `@simplysm/service-client`.
|
|
1917
|
+
|
|
1918
|
+
```tsx
|
|
1919
|
+
import { ServiceClientProvider } from "@simplysm/solid";
|
|
1920
|
+
|
|
1921
|
+
<ServiceClientProvider url="ws://localhost:3000">
|
|
1922
|
+
<App />
|
|
1923
|
+
</ServiceClientProvider>
|
|
1924
|
+
```
|
|
1925
|
+
|
|
1926
|
+
### SharedDataProvider
|
|
1927
|
+
|
|
1928
|
+
Shared data provider for managing server-side data subscriptions. Works with `ServiceClientProvider` to provide reactive shared data across components.
|
|
1929
|
+
|
|
1930
|
+
```tsx
|
|
1931
|
+
import { SharedDataProvider, SharedDataChangeEvent } from "@simplysm/solid";
|
|
1932
|
+
|
|
1933
|
+
<SharedDataProvider>
|
|
1934
|
+
<App />
|
|
1935
|
+
</SharedDataProvider>
|
|
1513
1936
|
```
|
|
1514
1937
|
|
|
1515
1938
|
---
|
|
@@ -1605,6 +2028,34 @@ const className = twMerge(baseClass, props.class);
|
|
|
1605
2028
|
|
|
1606
2029
|
---
|
|
1607
2030
|
|
|
2031
|
+
## Helpers
|
|
2032
|
+
|
|
2033
|
+
### mergeStyles
|
|
2034
|
+
|
|
2035
|
+
Utility for merging inline style strings and `JSX.CSSProperties` objects.
|
|
2036
|
+
|
|
2037
|
+
```typescript
|
|
2038
|
+
import { mergeStyles } from "@simplysm/solid";
|
|
2039
|
+
|
|
2040
|
+
const style = mergeStyles("color: red", { fontSize: "14px" }, props.style);
|
|
2041
|
+
```
|
|
2042
|
+
|
|
2043
|
+
### splitSlots
|
|
2044
|
+
|
|
2045
|
+
Utility for splitting children into named slots based on component type.
|
|
2046
|
+
|
|
2047
|
+
```typescript
|
|
2048
|
+
import { splitSlots } from "@simplysm/solid";
|
|
2049
|
+
|
|
2050
|
+
const slots = splitSlots(props.children, {
|
|
2051
|
+
header: HeaderComponent,
|
|
2052
|
+
footer: FooterComponent,
|
|
2053
|
+
});
|
|
2054
|
+
// slots.header, slots.footer, slots.rest
|
|
2055
|
+
```
|
|
2056
|
+
|
|
2057
|
+
---
|
|
2058
|
+
|
|
1608
2059
|
## Demo
|
|
1609
2060
|
|
|
1610
2061
|
Check out real-world usage examples of all components in the `solid-demo` package:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/solid",
|
|
3
|
-
"version": "13.0.0-beta.
|
|
3
|
+
"version": "13.0.0-beta.23",
|
|
4
4
|
"description": "심플리즘 패키지 - SolidJS 라이브러리",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"solid-tiptap": "^0.8.0",
|
|
44
44
|
"tailwind-merge": "^3.4.0",
|
|
45
45
|
"tailwindcss": "^3.4.19",
|
|
46
|
-
"@simplysm/core-browser": "13.0.0-beta.
|
|
47
|
-
"@simplysm/core-common": "13.0.0-beta.
|
|
46
|
+
"@simplysm/core-browser": "13.0.0-beta.23",
|
|
47
|
+
"@simplysm/core-common": "13.0.0-beta.23"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@solidjs/testing-library": "^0.8.10"
|