@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.
Files changed (2) hide show
  1. package/README.md +481 -30
  2. 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 row reordering.
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
- <DataSheet items={users()} key="user-table">
898
- <DataSheet.Column key="name" header="Name" sortable>
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
- key="data-table"
913
- page={page()}
914
- onPageChange={setPage}
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 (key items):**
1109
+ **DataSheet Props:**
927
1110
 
928
1111
  | Prop | Type | Default | Description |
929
1112
  |------|------|---------|-------------|
930
1113
  | `items` | `T[]` | - | Data array |
931
- | `key` | `string` | - | Column configuration storage key |
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
- | `sorts` | `SortingDef[]` | - | Sort definition |
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
- | `page` | `number` | - | Current page (0-based) |
937
- | `onPageChange` | `(page: number) => void` | - | Page change callback |
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
- | `hideConfigBar` | `boolean` | - | Hide configuration bar |
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
- | `width` | `string` | - | Column width |
954
- | `fixed` | `boolean` | - | Fixed column |
955
- | `hidden` | `boolean` | - | Hidden column |
956
- | `sortable` | `boolean` | - | Sortable |
957
- | `resizable` | `boolean` | - | Resizable |
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
- Permission management table component.
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 fullscreen mode.
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(items, {
1506
- modules: () => activeModules(),
1507
- basePath: "/app",
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 -- Route array (pass to Route component)
1511
- // structure.usableMenus() -- Sidebar menu array
1512
- // structure.permRecord() -- Permission record (Record<string, boolean>)
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.21",
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.21",
47
- "@simplysm/core-common": "13.0.0-beta.21"
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"