@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.
Files changed (180) hide show
  1. package/README.md +143 -28
  2. package/dist/components/data/list/ListItem.d.ts.map +1 -1
  3. package/dist/components/data/list/ListItem.js +11 -4
  4. package/dist/components/data/list/ListItem.js.map +2 -2
  5. package/dist/components/data/list/ListItem.styles.d.ts +2 -0
  6. package/dist/components/data/list/ListItem.styles.d.ts.map +1 -1
  7. package/dist/components/data/list/ListItem.styles.js +11 -1
  8. package/dist/components/data/list/ListItem.styles.js.map +1 -1
  9. package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
  10. package/dist/components/data/sheet/DataSheet.js +6 -9
  11. package/dist/components/data/sheet/DataSheet.js.map +2 -2
  12. package/dist/components/data/sheet/hooks/createDataSheetExpansion.d.ts.map +1 -1
  13. package/dist/components/data/sheet/hooks/createDataSheetExpansion.js +15 -17
  14. package/dist/components/data/sheet/hooks/createDataSheetExpansion.js.map +1 -1
  15. package/dist/components/data/sheet/hooks/createDataSheetReorder.d.ts.map +1 -1
  16. package/dist/components/data/sheet/hooks/createDataSheetReorder.js +12 -12
  17. package/dist/components/data/sheet/hooks/createDataSheetReorder.js.map +1 -1
  18. package/dist/components/data/sheet/hooks/createDataSheetSelection.d.ts.map +1 -1
  19. package/dist/components/data/sheet/hooks/createDataSheetSelection.js +9 -3
  20. package/dist/components/data/sheet/hooks/createDataSheetSelection.js.map +1 -1
  21. package/dist/components/disclosure/Dialog.d.ts.map +1 -1
  22. package/dist/components/disclosure/Dialog.js +3 -21
  23. package/dist/components/disclosure/Dialog.js.map +2 -2
  24. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  25. package/dist/components/disclosure/Dropdown.js +1 -11
  26. package/dist/components/disclosure/Dropdown.js.map +2 -2
  27. package/dist/components/disclosure/Tabs.d.ts.map +1 -1
  28. package/dist/components/disclosure/Tabs.js +1 -3
  29. package/dist/components/disclosure/Tabs.js.map +2 -2
  30. package/dist/components/features/crud-detail/CrudDetail.js +103 -102
  31. package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
  32. package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
  33. package/dist/components/features/crud-sheet/CrudSheet.js +10 -5
  34. package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
  35. package/dist/components/features/data-select-button/DataSelectButton.d.ts.map +1 -1
  36. package/dist/components/features/data-select-button/DataSelectButton.js +30 -26
  37. package/dist/components/features/data-select-button/DataSelectButton.js.map +2 -2
  38. package/dist/components/features/permission-table/PermissionTable.js +5 -1
  39. package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
  40. package/dist/components/feedback/busy/BusyContainer.d.ts.map +1 -1
  41. package/dist/components/feedback/busy/BusyContainer.js +1 -6
  42. package/dist/components/feedback/busy/BusyContainer.js.map +2 -2
  43. package/dist/components/form-control/DropdownTrigger.styles.js +1 -1
  44. package/dist/components/form-control/checkbox/SelectableBase.d.ts.map +1 -1
  45. package/dist/components/form-control/checkbox/SelectableBase.js +2 -4
  46. package/dist/components/form-control/checkbox/SelectableBase.js.map +2 -2
  47. package/dist/components/form-control/combobox/Combobox.d.ts +19 -5
  48. package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
  49. package/dist/components/form-control/combobox/Combobox.js +2 -4
  50. package/dist/components/form-control/combobox/Combobox.js.map +1 -1
  51. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +2 -2
  52. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
  53. package/dist/components/form-control/date-range-picker/DateRangePicker.js +11 -3
  54. package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
  55. package/dist/components/form-control/editor/RichTextEditor.d.ts +2 -2
  56. package/dist/components/form-control/editor/RichTextEditor.d.ts.map +1 -1
  57. package/dist/components/form-control/editor/RichTextEditor.js +2 -4
  58. package/dist/components/form-control/editor/RichTextEditor.js.map +2 -2
  59. package/dist/components/form-control/field/DatePicker.d.ts +2 -2
  60. package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
  61. package/dist/components/form-control/field/DatePicker.js.map +1 -1
  62. package/dist/components/form-control/field/DateTimePicker.d.ts +2 -2
  63. package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
  64. package/dist/components/form-control/field/DateTimePicker.js.map +1 -1
  65. package/dist/components/form-control/field/Field.styles.d.ts +6 -7
  66. package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
  67. package/dist/components/form-control/field/Field.styles.js.map +1 -1
  68. package/dist/components/form-control/field/NumberInput.d.ts +2 -2
  69. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  70. package/dist/components/form-control/field/NumberInput.js +7 -7
  71. package/dist/components/form-control/field/NumberInput.js.map +2 -2
  72. package/dist/components/form-control/field/TextInput.d.ts +2 -2
  73. package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
  74. package/dist/components/form-control/field/TextInput.js.map +1 -1
  75. package/dist/components/form-control/field/Textarea.d.ts +2 -2
  76. package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
  77. package/dist/components/form-control/field/Textarea.js +1 -3
  78. package/dist/components/form-control/field/Textarea.js.map +2 -2
  79. package/dist/components/form-control/field/TimePicker.d.ts +2 -2
  80. package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
  81. package/dist/components/form-control/field/TimePicker.js.map +1 -1
  82. package/dist/components/form-control/numpad/Numpad.d.ts.map +1 -1
  83. package/dist/components/form-control/numpad/Numpad.js +4 -17
  84. package/dist/components/form-control/numpad/Numpad.js.map +2 -2
  85. package/dist/components/form-control/select/Select.d.ts +2 -0
  86. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  87. package/dist/components/form-control/select/Select.js +29 -15
  88. package/dist/components/form-control/select/Select.js.map +2 -2
  89. package/dist/components/form-control/state-preset/StatePreset.d.ts +1 -3
  90. package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
  91. package/dist/components/form-control/state-preset/StatePreset.js +69 -95
  92. package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
  93. package/dist/components/layout/FormGroup.js +1 -1
  94. package/dist/components/layout/FormGroup.js.map +1 -1
  95. package/dist/components/layout/FormTable.js +3 -3
  96. package/dist/components/layout/FormTable.js.map +1 -1
  97. package/dist/components/layout/sidebar/Sidebar.d.ts.map +1 -1
  98. package/dist/components/layout/sidebar/Sidebar.js +3 -6
  99. package/dist/components/layout/sidebar/Sidebar.js.map +2 -2
  100. package/dist/components/layout/topbar/Topbar.js +1 -3
  101. package/dist/components/layout/topbar/Topbar.js.map +2 -2
  102. package/dist/hooks/createControllableStore.d.ts.map +1 -1
  103. package/dist/hooks/createControllableStore.js +8 -5
  104. package/dist/hooks/createControllableStore.js.map +1 -1
  105. package/dist/hooks/useLocalStorage.d.ts.map +1 -1
  106. package/dist/hooks/useLocalStorage.js +3 -2
  107. package/dist/hooks/useLocalStorage.js.map +1 -1
  108. package/dist/hooks/useSyncConfig.d.ts.map +1 -1
  109. package/dist/hooks/useSyncConfig.js +5 -4
  110. package/dist/hooks/useSyncConfig.js.map +1 -1
  111. package/dist/providers/i18n/locales/en.d.ts +2 -3
  112. package/dist/providers/i18n/locales/en.d.ts.map +1 -1
  113. package/dist/providers/i18n/locales/en.js +3 -4
  114. package/dist/providers/i18n/locales/en.js.map +1 -1
  115. package/dist/providers/i18n/locales/ko.d.ts +2 -3
  116. package/dist/providers/i18n/locales/ko.d.ts.map +1 -1
  117. package/dist/providers/i18n/locales/ko.js +3 -4
  118. package/dist/providers/i18n/locales/ko.js.map +1 -1
  119. package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
  120. package/dist/providers/shared-data/SharedDataProvider.js +0 -1
  121. package/dist/providers/shared-data/SharedDataProvider.js.map +1 -1
  122. package/docs/display-feedback.md +279 -0
  123. package/docs/features.md +357 -213
  124. package/docs/form-controls.md +261 -403
  125. package/docs/layout-data.md +386 -0
  126. package/docs/providers-hooks.md +411 -0
  127. package/package.json +5 -5
  128. package/src/components/data/list/ListItem.styles.ts +14 -2
  129. package/src/components/data/list/ListItem.tsx +13 -4
  130. package/src/components/data/sheet/DataSheet.tsx +6 -10
  131. package/src/components/data/sheet/hooks/createDataSheetExpansion.ts +17 -18
  132. package/src/components/data/sheet/hooks/createDataSheetReorder.ts +12 -13
  133. package/src/components/data/sheet/hooks/createDataSheetSelection.ts +9 -3
  134. package/src/components/disclosure/Dialog.tsx +45 -59
  135. package/src/components/disclosure/Dropdown.tsx +4 -14
  136. package/src/components/disclosure/Tabs.tsx +12 -17
  137. package/src/components/features/crud-detail/CrudDetail.tsx +4 -4
  138. package/src/components/features/crud-sheet/CrudSheet.tsx +12 -5
  139. package/src/components/features/data-select-button/DataSelectButton.tsx +39 -32
  140. package/src/components/features/permission-table/PermissionTable.tsx +1 -1
  141. package/src/components/feedback/busy/BusyContainer.tsx +12 -18
  142. package/src/components/form-control/DropdownTrigger.styles.ts +1 -1
  143. package/src/components/form-control/checkbox/SelectableBase.tsx +10 -16
  144. package/src/components/form-control/combobox/Combobox.tsx +42 -16
  145. package/src/components/form-control/date-range-picker/DateRangePicker.tsx +7 -8
  146. package/src/components/form-control/editor/RichTextEditor.tsx +14 -16
  147. package/src/components/form-control/field/DatePicker.tsx +3 -2
  148. package/src/components/form-control/field/DateTimePicker.tsx +3 -2
  149. package/src/components/form-control/field/Field.styles.ts +6 -8
  150. package/src/components/form-control/field/NumberInput.tsx +9 -10
  151. package/src/components/form-control/field/TextInput.tsx +3 -2
  152. package/src/components/form-control/field/Textarea.tsx +14 -12
  153. package/src/components/form-control/field/TimePicker.tsx +3 -2
  154. package/src/components/form-control/numpad/Numpad.tsx +16 -18
  155. package/src/components/form-control/select/Select.tsx +41 -13
  156. package/src/components/form-control/state-preset/StatePreset.tsx +39 -71
  157. package/src/components/layout/FormGroup.tsx +1 -1
  158. package/src/components/layout/FormTable.tsx +3 -3
  159. package/src/components/layout/sidebar/Sidebar.tsx +2 -3
  160. package/src/components/layout/topbar/Topbar.tsx +2 -2
  161. package/src/hooks/createControllableStore.ts +8 -4
  162. package/src/hooks/useLocalStorage.ts +3 -2
  163. package/src/hooks/useSyncConfig.ts +5 -4
  164. package/src/providers/i18n/locales/en.ts +2 -3
  165. package/src/providers/i18n/locales/ko.ts +2 -3
  166. package/src/providers/shared-data/SharedDataProvider.tsx +0 -1
  167. package/tests/components/features/crud-detail/CrudDetail.spec.tsx +49 -0
  168. package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +62 -7
  169. package/tests/components/form-control/combobox/Combobox.spec.tsx +3 -3
  170. package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +56 -0
  171. package/tests/components/form-control/select/SelectItem.spec.tsx +5 -0
  172. package/tests/providers/shared-data/SharedDataProvider.spec.tsx +0 -104
  173. package/docs/data.md +0 -204
  174. package/docs/disclosure.md +0 -146
  175. package/docs/display.md +0 -125
  176. package/docs/feedback.md +0 -156
  177. package/docs/helpers.md +0 -173
  178. package/docs/hooks.md +0 -146
  179. package/docs/layout.md +0 -94
  180. package/docs/providers.md +0 -180
@@ -57,7 +57,8 @@ export function createDataSheetSelection<TItem>(
57
57
  const selectableItems = displayItems()
58
58
  .map((flat) => flat.item)
59
59
  .filter((item) => getItemSelectable(item) === true);
60
- const isAllSelected = selectableItems.every((item) => selection().includes(item));
60
+ const selectionSet = new Set(selection());
61
+ const isAllSelected = selectableItems.every((item) => selectionSet.has(item));
61
62
  setSelection(isAllSelected ? [] : selectableItems);
62
63
  }
63
64
 
@@ -74,13 +75,18 @@ export function createDataSheetSelection<TItem>(
74
75
  .filter((item) => getItemSelectable(item) === true);
75
76
 
76
77
  if (lastClickAction() === "select") {
78
+ const selectionSet = new Set(selection());
77
79
  const newItems = [...selection()];
78
80
  for (const item of rangeItems) {
79
- if (!newItems.includes(item)) newItems.push(item);
81
+ if (!selectionSet.has(item)) {
82
+ newItems.push(item);
83
+ selectionSet.add(item);
84
+ }
80
85
  }
81
86
  setSelection(newItems);
82
87
  } else {
83
- setSelection(selection().filter((item) => !rangeItems.includes(item)));
88
+ const rangeSet = new Set(rangeItems);
89
+ setSelection(selection().filter((item) => !rangeSet.has(item)));
84
90
  }
85
91
  }
86
92
 
@@ -478,70 +478,38 @@ const DialogInner: ParentComponent<DialogProps> = (props) => {
478
478
  return style;
479
479
  };
480
480
 
481
- // Animation class
482
- const animationClass = () => {
483
- const base = clsx("transition-[opacity,transform]", "duration-200", "ease-out");
484
- if (animating()) {
485
- return clsx(base, "translate-y-0 opacity-100");
486
- } else {
487
- return clsx(base, "-translate-y-4 opacity-0");
488
- }
489
- };
490
-
491
- // Wrapper class
492
- const wrapperClass = () =>
493
- // eslint-disable-next-line tailwindcss/enforces-shorthand -- inset-0 not supported in Chrome 84
494
- clsx(
495
- "fixed bottom-0 left-0 right-0 top-0",
496
- "flex flex-col items-center",
497
- local.mode !== "fill" && "pt-[calc(3rem+0.5rem)]",
498
- local.mode === "float" && "pointer-events-none",
499
- );
500
-
501
- // Backdrop class
502
- const backdropClass = () =>
503
- // eslint-disable-next-line tailwindcss/enforces-shorthand -- inset-0 not supported in Chrome 84
504
- clsx(
505
- "absolute bottom-0 left-0 right-0 top-0",
506
- "bg-black/30",
507
- "dark:bg-black/50",
508
- "transition-opacity",
509
- "duration-200",
510
- "ease-out",
511
- animating() ? "opacity-100" : "opacity-0",
512
- );
513
-
514
- // Dialog class
515
- const dialogBaseClass = () =>
516
- clsx(
517
- "relative",
518
- "mx-auto",
519
- "w-fit min-w-[200px]",
520
- bg.surface,
521
- local.mode === "float"
522
- ? clsx("shadow-md dark:shadow-black/30", "border", border.subtle)
523
- : "shadow-2xl dark:shadow-black/40",
524
- local.mode === "fill" ? "rounded-none border-none" : "rounded-lg",
525
- "overflow-hidden",
526
- "flex flex-col",
527
- "focus:outline-none",
528
- local.mode === "float" && "pointer-events-auto",
529
- animationClass(),
530
- );
531
-
532
- // Header class
533
- const headerClass = () =>
534
- clsx("flex items-center gap-2", "px-3 py-1", "select-none", "border-b", border.subtle);
535
-
536
481
  return (
537
482
  <Show when={mounted()}>
538
483
  <Portal>
539
484
  <HeaderProvider>
540
485
  <ActionProvider>
541
- <div ref={setWrapperRef} data-dialog class={wrapperClass()}>
486
+ <div
487
+ ref={setWrapperRef}
488
+ data-dialog
489
+ // eslint-disable-next-line tailwindcss/enforces-shorthand -- inset-0 not supported in Chrome 84
490
+ class={clsx(
491
+ "fixed bottom-0 left-0 right-0 top-0",
492
+ "flex flex-col items-center",
493
+ local.mode !== "fill" && "pt-[calc(3rem+0.5rem)]",
494
+ local.mode === "float" && "pointer-events-none",
495
+ )}
496
+ >
542
497
  {/* Backdrop */}
543
498
  <Show when={local.mode !== "float"}>
544
- <div data-dialog-backdrop class={backdropClass()} onClick={handleBackdropClick} />
499
+ <div
500
+ data-dialog-backdrop
501
+ // eslint-disable-next-line tailwindcss/enforces-shorthand -- inset-0 not supported in Chrome 84
502
+ class={clsx(
503
+ "absolute bottom-0 left-0 right-0 top-0",
504
+ "bg-black/30",
505
+ "dark:bg-black/50",
506
+ "transition-opacity",
507
+ "duration-200",
508
+ "ease-out",
509
+ animating() ? "opacity-100" : "opacity-0",
510
+ )}
511
+ onClick={handleBackdropClick}
512
+ />
545
513
  </Show>
546
514
 
547
515
  {/* Dialog */}
@@ -554,7 +522,25 @@ const DialogInner: ParentComponent<DialogProps> = (props) => {
554
522
  aria-modal={local.mode === "float" ? undefined : true}
555
523
  aria-labelledby={hasHeader() ? headerId : undefined}
556
524
  tabIndex={0}
557
- class={twMerge(dialogBaseClass(), local.class)}
525
+ class={twMerge(
526
+ clsx(
527
+ "relative",
528
+ "mx-auto",
529
+ "w-fit min-w-[200px]",
530
+ bg.surface,
531
+ local.mode === "float"
532
+ ? clsx("shadow-md dark:shadow-black/30", "border", border.subtle)
533
+ : "shadow-2xl dark:shadow-black/40",
534
+ local.mode === "fill" ? "rounded-none border-none" : "rounded-lg",
535
+ "overflow-hidden",
536
+ "flex flex-col",
537
+ "focus:outline-none",
538
+ local.mode === "float" && "pointer-events-auto",
539
+ "transition-[opacity,transform] duration-200 ease-out",
540
+ animating() ? "translate-y-0 opacity-100" : "-translate-y-4 opacity-0",
541
+ ),
542
+ local.class,
543
+ )}
558
544
  style={dialogStyle()}
559
545
  onFocus={handleDialogFocus}
560
546
  onTransitionEnd={handleTransitionEnd}
@@ -563,7 +549,7 @@ const DialogInner: ParentComponent<DialogProps> = (props) => {
563
549
  <Show when={hasHeader()}>
564
550
  <div
565
551
  data-dialog-header
566
- class={clsx(headerClass(), "touch-none")}
552
+ class={clsx("flex items-center gap-2", "px-3 py-1", "select-none", "border-b", border.subtle, "touch-none")}
567
553
  style={
568
554
  typeof local.headerStyle === "string"
569
555
  ? mergeStyles(local.headerStyle)
@@ -402,19 +402,6 @@ const DropdownInner: ParentComponent<DropdownProps> = (props: DropdownProps) =>
402
402
 
403
403
  const maxHeight = () => local.maxHeight ?? 300;
404
404
 
405
- // Animation class (only transition opacity and transform, not position properties)
406
- const animationClass = () => {
407
- const base = "transition-[opacity,transform] duration-150 ease-out";
408
- const visible = animating();
409
- const dir = direction();
410
-
411
- if (visible) {
412
- return clsx(base, "translate-y-0 opacity-100");
413
- } else {
414
- return clsx(base, "opacity-0", dir === "down" ? "-translate-y-1" : "translate-y-1");
415
- }
416
- };
417
-
418
405
  return (
419
406
  <TriggerProvider>
420
407
  <ContentProvider>
@@ -452,7 +439,10 @@ const DropdownInner: ParentComponent<DropdownProps> = (props: DropdownProps) =>
452
439
  "shadow-lg dark:shadow-black/30",
453
440
  "rounded-md",
454
441
  "overflow-y-auto",
455
- animationClass(),
442
+ "transition-[opacity,transform] duration-150 ease-out",
443
+ animating()
444
+ ? "translate-y-0 opacity-100"
445
+ : clsx("opacity-0", direction() === "down" ? "-translate-y-1" : "translate-y-1"),
456
446
  ),
457
447
  local.class,
458
448
  )}
@@ -39,21 +39,6 @@ function TabsTabInner(props: TabsTabProps) {
39
39
  xl: "px-5 py-3 text-lg",
40
40
  };
41
41
 
42
- const sizeClasses = () => tabSizeClasses[ctx.size() ?? "md"];
43
-
44
- const stateClass = () =>
45
- isSelected()
46
- ? clsx(
47
- "border-b-2 border-primary-500 dark:border-primary-400",
48
- themeTokens.primary.text,
49
- )
50
- : clsx(
51
- "border-b-2 border-transparent",
52
- text.muted,
53
- "hover:border-base-300 hover:text-base-700",
54
- "dark:hover:border-base-600 dark:hover:text-base-200",
55
- );
56
-
57
42
  return (
58
43
  <button
59
44
  type="button"
@@ -63,8 +48,18 @@ function TabsTabInner(props: TabsTabProps) {
63
48
  tabIndex={props.disabled ? -1 : 0}
64
49
  class={twMerge(
65
50
  "relative cursor-pointer select-none font-medium transition-colors -mb-px",
66
- sizeClasses(),
67
- stateClass(),
51
+ tabSizeClasses[ctx.size() ?? "md"],
52
+ isSelected()
53
+ ? clsx(
54
+ "border-b-2 border-primary-500 dark:border-primary-400",
55
+ themeTokens.primary.text,
56
+ )
57
+ : clsx(
58
+ "border-b-2 border-transparent",
59
+ text.muted,
60
+ "hover:border-base-300 hover:text-base-700",
61
+ "dark:hover:border-base-600 dark:hover:text-base-200",
62
+ ),
68
63
  props.disabled && "opacity-50 pointer-events-none",
69
64
  props.class,
70
65
  )}
@@ -201,7 +201,6 @@ const CrudDetailBase = <TData extends object>(props: CrudDetailProps<TData>) =>
201
201
  <>
202
202
  <Show when={showSave()}>
203
203
  <Button
204
- size="lg"
205
204
  variant="ghost"
206
205
  theme="primary"
207
206
  onClick={() => formRef?.requestSubmit()}
@@ -213,7 +212,6 @@ const CrudDetailBase = <TData extends object>(props: CrudDetailProps<TData>) =>
213
212
  <Show when={showDelete()}>
214
213
  {(_) => (
215
214
  <Button
216
- size="lg"
217
215
  variant="ghost"
218
216
  theme="danger"
219
217
  onClick={() => void handleToggleDelete()}
@@ -223,7 +221,7 @@ const CrudDetailBase = <TData extends object>(props: CrudDetailProps<TData>) =>
223
221
  </Button>
224
222
  )}
225
223
  </Show>
226
- <Button size="lg" variant="ghost" theme="info" onClick={() => void handleRefresh()}>
224
+ <Button variant="ghost" theme="info" onClick={() => void handleRefresh()}>
227
225
  <Icon icon={IconRefresh} class="mr-1" />
228
226
  {i18n.t("crudDetail.refresh")}
229
227
  </Button>
@@ -259,8 +257,9 @@ const CrudDetailBase = <TData extends object>(props: CrudDetailProps<TData>) =>
259
257
  <BusyContainer
260
258
  ready={ready()}
261
259
  busy={busyCount() > 0}
262
- class={clsx("flex h-full flex-col gap-2", local.class)}
260
+ class={clsx("flex h-full flex-col", local.class)}
263
261
  >
262
+ <div class="flex min-h-0 flex-1 flex-col gap-2">
264
263
  {/* Toolbar */}
265
264
  <Show when={(!isInDialog && !topbarCtx) || tools()}>
266
265
  <div class="flex gap-2 pb-0">
@@ -318,6 +317,7 @@ const CrudDetailBase = <TData extends object>(props: CrudDetailProps<TData>) =>
318
317
 
319
318
  {/* After (outside form) */}
320
319
  <Show when={after()}>{(afterSlot) => afterSlot().children}</Show>
320
+ </div>
321
321
 
322
322
  {/* Dialog mode: bottom bar */}
323
323
  <Show when={isInDialog && canEdit()}>
@@ -348,6 +348,14 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, unknown>>(
348
348
 
349
349
  setSelectedKeys(merged);
350
350
  setSelection(newSelection);
351
+
352
+ // Auto-confirm for single selection mode in dialog
353
+ if (isInDialog && local.selectionMode === "single" && newSelection.length > 0) {
354
+ local.onSelect?.({
355
+ items: newSelection,
356
+ keys: [...merged],
357
+ });
358
+ }
351
359
  }
352
360
 
353
361
  function clearSelection() {
@@ -369,13 +377,13 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, unknown>>(
369
377
 
370
378
  // -- Keyboard Shortcuts --
371
379
  createEventListener(document, "keydown", async (e: KeyboardEvent) => {
372
- if (!isActiveCrud(crudId)) return;
373
- if (e.ctrlKey && e.key === "s" && !isSelectMode()) {
380
+ if (!e.ctrlKey || !isActiveCrud(crudId)) return;
381
+ if (e.key === "s" && !isSelectMode()) {
374
382
  e.preventDefault();
375
383
  e.stopImmediatePropagation();
376
384
  formRef?.requestSubmit();
377
385
  }
378
- if (e.ctrlKey && e.altKey && e.key === "l") {
386
+ if (e.altKey && e.key === "l") {
379
387
  e.preventDefault();
380
388
  e.stopImmediatePropagation();
381
389
  if (!checkIgnoreChanges()) return;
@@ -402,7 +410,6 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, unknown>>(
402
410
  <>
403
411
  <Show when={canEdit() && local.inlineEdit}>
404
412
  <Button
405
- size="lg"
406
413
  variant="ghost"
407
414
  theme="primary"
408
415
  onClick={() => formRef?.requestSubmit()}
@@ -411,7 +418,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, unknown>>(
411
418
  {i18n.t("crudSheet.save")}
412
419
  </Button>
413
420
  </Show>
414
- <Button size="lg" variant="ghost" theme="info" onClick={handleRefresh}>
421
+ <Button variant="ghost" theme="info" onClick={handleRefresh}>
415
422
  <Icon icon={IconRefresh} class="mr-1" />
416
423
  {i18n.t("crudSheet.refresh")}
417
424
  </Button>
@@ -149,7 +149,6 @@ export function DataSelectButton<
149
149
  "onValueChange",
150
150
  "load",
151
151
  "dialog",
152
- "dialogProps",
153
152
  "dialogOptions",
154
153
  "renderItem",
155
154
  "multiple",
@@ -174,9 +173,9 @@ export function DataSelectButton<
174
173
  // Controlled/uncontrolled pattern
175
174
  type ValueType = TKey | TKey[] | undefined;
176
175
  const [getValue, setValue] = createControllableSignal<ValueType>({
177
- value: () => local.value,
176
+ value: () => local.value as ValueType,
178
177
  onChange: () => local.onValueChange as ((v: ValueType) => void) | undefined,
179
- } as Parameters<typeof createControllableSignal<ValueType>>[0]);
178
+ });
180
179
 
181
180
  // Track keys for loading
182
181
  const [loadKeys, setLoadKeys] = createSignal<TKey[]>(normalizeKeys(local.value));
@@ -216,27 +215,38 @@ export function DataSelectButton<
216
215
  return local.validate?.(v);
217
216
  });
218
217
 
218
+ // Dialog open state for aria-expanded
219
+ const [isDialogOpen, setIsDialogOpen] = createSignal(false);
220
+
219
221
  // Open dialog
220
222
  const handleOpenDialog = async () => {
221
223
  if (local.disabled) return;
222
224
 
223
- const result = (await dialog.show(
224
- local.dialog,
225
- {
226
- ...((local as any).dialogProps ?? {}),
225
+ setIsDialogOpen(true);
226
+ try {
227
+ const dialogProps =
228
+ (props as { dialogProps?: Record<string, unknown> }).dialogProps ?? {};
229
+ const showProps = {
230
+ ...dialogProps,
227
231
  selectionMode: local.multiple ? "multiple" : "single",
228
232
  selectedKeys: normalizeKeys(getValue()) as (string | number)[],
229
- },
230
- local.dialogOptions,
231
- )) as DataSelectDialogResult<TKey> | undefined;
233
+ } as Omit<TDialogProps, "close">;
234
+ const result = (await dialog.show(
235
+ local.dialog as Component<SelectDialogBaseProps<TKey>>,
236
+ showProps as Omit<SelectDialogBaseProps<TKey>, "close">,
237
+ local.dialogOptions,
238
+ ));
232
239
 
233
- if (result) {
234
- const newKeys = result.selectedKeys;
235
- if (local.multiple) {
236
- setValue(newKeys);
237
- } else {
238
- setValue(newKeys.length > 0 ? newKeys[0] : undefined);
240
+ if (result) {
241
+ const newKeys = result.selectedKeys;
242
+ if (local.multiple) {
243
+ setValue(newKeys);
244
+ } else {
245
+ setValue(newKeys.length > 0 ? newKeys[0] : undefined);
246
+ }
239
247
  }
248
+ } finally {
249
+ setIsDialogOpen(false);
240
250
  }
241
251
  };
242
252
 
@@ -283,23 +293,17 @@ export function DataSelectButton<
283
293
  return (
284
294
  <Invalid message={errorMsg()} variant="border" lazyValidation={local.lazyValidation}>
285
295
  <div data-data-select-button class="group inline-flex items-center">
286
- <div
287
- role="combobox"
296
+ <button
297
+ type="button"
298
+ data-trigger
288
299
  aria-haspopup="dialog"
289
- aria-expanded={false}
290
- aria-disabled={local.disabled || undefined}
300
+ aria-expanded={isDialogOpen()}
291
301
  aria-required={local.required || undefined}
292
- tabIndex={local.disabled ? -1 : 0}
293
- class={triggerClassName()}
294
- onKeyDown={(e) => {
295
- if (local.disabled) return;
296
- if (e.key === "Enter" || e.key === " ") {
297
- e.preventDefault();
298
- void handleOpenDialog();
299
- }
300
- }}
302
+ disabled={local.disabled || undefined}
303
+ class={twMerge("appearance-none font-inherit text-inherit text-left", triggerClassName())}
304
+ onClick={() => void handleOpenDialog()}
301
305
  >
302
- <div class="flex-1 truncate">{renderSelectedDisplay()}</div>
306
+ <div class="flex-1 whitespace-nowrap">{renderSelectedDisplay()}</div>
303
307
  <div class={clsx("flex items-center", gap.sm)}>
304
308
  <Show when={clearable()}>
305
309
  <button
@@ -318,7 +322,10 @@ export function DataSelectButton<
318
322
  type="button"
319
323
  data-search-button
320
324
  class={twMerge(actionButtonClass, text.muted, "hover:text-primary-500")}
321
- onClick={() => void handleOpenDialog()}
325
+ onClick={(e) => {
326
+ e.stopPropagation();
327
+ void handleOpenDialog();
328
+ }}
322
329
  tabIndex={-1}
323
330
  aria-label={i18n.t("dataSelectButton.search")}
324
331
  >
@@ -326,7 +333,7 @@ export function DataSelectButton<
326
333
  </button>
327
334
  </Show>
328
335
  </div>
329
- </div>
336
+ </button>
330
337
  </div>
331
338
  </Invalid>
332
339
  );
@@ -288,7 +288,7 @@ export const PermissionTable: Component<PermissionTableProps> = (props) => {
288
288
  </DataSheet.Column>
289
289
  <For each={allPerms()}>
290
290
  {(perm) => (
291
- <DataSheet.Column key={`perm-${perm}`} header={perm} sortable={false} resizable={false}>
291
+ <DataSheet.Column key={`perm-${perm}`} header={(() => { const key = `permissionTable.${perm}`; const translated = i18n.t(key); return translated === key ? perm : translated; })()} sortable={false} resizable={false}>
292
292
  {(ctx) => {
293
293
  const item = ctx.item as AppPerm;
294
294
  return (
@@ -72,27 +72,21 @@ export const BusyContainer: ParentComponent<BusyContainerProps> = (props) => {
72
72
  );
73
73
  });
74
74
 
75
-
76
- const screenClass = () =>
77
- clsx(
78
- "absolute inset-0 z-busy bg-white/70 transition-opacity duration-150 dark:bg-base-900/70",
79
- animating() ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0",
80
- );
81
-
82
- // spinner: slide down animation
83
- const rectClass = () => {
84
- if (currVariant() !== "spinner") return "";
85
- return clsx(
86
- "transition-transform duration-100",
87
- animating() ? "translate-y-0 ease-out" : "-translate-y-full ease-in",
88
- );
89
- };
90
-
91
75
  return (
92
76
  <div ref={containerRef} class={twMerge("relative size-full min-h-[70px] min-w-[70px] overflow-auto", local.class)} {...rest}>
93
77
  <Show when={mounted()}>
94
- <div class={screenClass()} onTransitionEnd={handleTransitionEnd}>
95
- <div class={rectClass()}>
78
+ <div
79
+ class={clsx(
80
+ "absolute inset-0 z-busy bg-white/70 transition-opacity duration-150 dark:bg-base-900/70",
81
+ animating() ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0",
82
+ )}
83
+ onTransitionEnd={handleTransitionEnd}
84
+ >
85
+ <div
86
+ class={currVariant() === "spinner"
87
+ ? clsx("transition-transform duration-100", animating() ? "translate-y-0 ease-out" : "-translate-y-full ease-in")
88
+ : ""}
89
+ >
96
90
  <Show when={currVariant() === "spinner"}>
97
91
  <div class="mx-auto mt-5 size-8 animate-spin rounded-full border-[6px] border-base-200 border-b-primary-500 shadow-md dark:border-base-700 dark:border-b-primary-400" />
98
92
  </Show>
@@ -5,7 +5,7 @@ import { type ComponentSize, gap, pad } from "../../styles/control.styles";
5
5
 
6
6
  export const triggerBaseClass = clsx(
7
7
  "inline-flex items-center gap-2",
8
- "w-40",
8
+ "min-w-40",
9
9
  "border",
10
10
  border.default,
11
11
  "rounded",
@@ -76,20 +76,6 @@ export const SelectableBase: ParentComponent<SelectableBaseProps & { config: Sel
76
76
  }
77
77
  };
78
78
 
79
- const getWrapperClass = () =>
80
- twMerge(
81
- checkboxBaseClass,
82
- checkboxSizeClasses[local.size ?? "md"],
83
- local.inset && checkboxInsetClass,
84
- local.inset && checkboxInsetSizeHeightClasses[local.size ?? "md"],
85
- local.inline && checkboxInlineClass,
86
- local.disabled && checkboxDisabledClass,
87
- local.class,
88
- );
89
-
90
- const getIndicatorClass = () =>
91
- twMerge(indicatorBaseClass, local.config.indicatorShape, checked() && checkedClass);
92
-
93
79
  const errorMsg = createMemo(() => {
94
80
  const v = local.checked ?? false;
95
81
  if (local.required && !v) return i18n.t("validation.requiredSelection");
@@ -104,12 +90,20 @@ export const SelectableBase: ParentComponent<SelectableBaseProps & { config: Sel
104
90
  role={local.config.role}
105
91
  aria-checked={checked()}
106
92
  tabIndex={local.disabled ? -1 : 0}
107
- class={getWrapperClass()}
93
+ class={twMerge(
94
+ checkboxBaseClass,
95
+ checkboxSizeClasses[local.size ?? "md"],
96
+ local.inset && checkboxInsetClass,
97
+ local.inset && checkboxInsetSizeHeightClasses[local.size ?? "md"],
98
+ local.inline && checkboxInlineClass,
99
+ local.disabled && checkboxDisabledClass,
100
+ local.class,
101
+ )}
108
102
  style={local.style}
109
103
  onClick={handleClick}
110
104
  onKeyDown={handleKeyDown}
111
105
  >
112
- <div class={getIndicatorClass()}>
106
+ <div class={twMerge(indicatorBaseClass, local.config.indicatorShape, checked() && checkedClass)}>
113
107
  <Show when={checked()}>
114
108
  {local.config.indicatorContent}
115
109
  </Show>