@fpkit/acss 6.4.2 → 6.5.0

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 (124) hide show
  1. package/libs/chunk-EARHB4PD.js +11 -0
  2. package/libs/chunk-EARHB4PD.js.map +1 -0
  3. package/libs/chunk-PSIXTYFY.cjs +19 -0
  4. package/libs/chunk-PSIXTYFY.cjs.map +1 -0
  5. package/libs/components/alert/alert.css +1 -0
  6. package/libs/components/alert/alert.css.map +1 -0
  7. package/libs/components/alert/alert.min.css +3 -0
  8. package/libs/components/badge/badge.css +1 -0
  9. package/libs/components/badge/badge.css.map +1 -0
  10. package/libs/components/badge/badge.min.css +3 -0
  11. package/libs/components/box/box.css +1 -0
  12. package/libs/components/box/box.css.map +1 -0
  13. package/libs/components/box/box.min.css +3 -0
  14. package/libs/components/breadcrumbs/breadcrumb.css +1 -0
  15. package/libs/components/breadcrumbs/breadcrumb.css.map +1 -0
  16. package/libs/components/breadcrumbs/breadcrumb.min.css +3 -0
  17. package/libs/components/buttons/button.css +1 -0
  18. package/libs/components/buttons/button.css.map +1 -0
  19. package/libs/components/buttons/button.min.css +3 -0
  20. package/libs/components/buttons/icon-button.css +1 -0
  21. package/libs/components/buttons/icon-button.css.map +1 -0
  22. package/libs/components/buttons/icon-button.min.css +3 -0
  23. package/libs/components/cards/card-style.css +1 -0
  24. package/libs/components/cards/card-style.css.map +1 -0
  25. package/libs/components/cards/card-style.min.css +3 -0
  26. package/libs/components/cards/card.css +1 -0
  27. package/libs/components/cards/card.css.map +1 -0
  28. package/libs/components/cards/card.min.css +3 -0
  29. package/libs/components/cluster/cluster.css +1 -0
  30. package/libs/components/cluster/cluster.css.map +1 -0
  31. package/libs/components/cluster/cluster.min.css +3 -0
  32. package/libs/components/details/details.css +1 -0
  33. package/libs/components/details/details.css.map +1 -0
  34. package/libs/components/details/details.min.css +3 -0
  35. package/libs/components/dialog/dialog.cjs +3 -3
  36. package/libs/components/dialog/dialog.css +1 -0
  37. package/libs/components/dialog/dialog.css.map +1 -0
  38. package/libs/components/dialog/dialog.d.cts +1 -1
  39. package/libs/components/dialog/dialog.d.ts +1 -1
  40. package/libs/components/dialog/dialog.js +1 -1
  41. package/libs/components/dialog/dialog.min.css +3 -0
  42. package/libs/components/flexbox/flex.css +1 -0
  43. package/libs/components/flexbox/flex.css.map +1 -0
  44. package/libs/components/flexbox/flex.min.css +3 -0
  45. package/libs/components/form/checkbox.css +1 -0
  46. package/libs/components/form/checkbox.css.map +1 -0
  47. package/libs/components/form/checkbox.min.css +3 -0
  48. package/libs/components/form/form.css +1 -0
  49. package/libs/components/form/form.css.map +1 -0
  50. package/libs/components/form/form.min.css +3 -0
  51. package/libs/components/form/select.css +1 -0
  52. package/libs/components/form/select.css.map +1 -0
  53. package/libs/components/form/select.min.css +3 -0
  54. package/libs/components/grid/grid.css +1 -0
  55. package/libs/components/grid/grid.css.map +1 -0
  56. package/libs/components/grid/grid.min.css +3 -0
  57. package/libs/components/icons/icon.css +1 -0
  58. package/libs/components/icons/icon.css.map +1 -0
  59. package/libs/components/icons/icon.min.css +3 -0
  60. package/libs/components/images/img.css +1 -0
  61. package/libs/components/images/img.css.map +1 -0
  62. package/libs/components/images/img.min.css +3 -0
  63. package/libs/components/layout/landmarks.css +1 -0
  64. package/libs/components/layout/landmarks.css.map +1 -0
  65. package/libs/components/layout/landmarks.min.css +3 -0
  66. package/libs/components/link/link.css +1 -0
  67. package/libs/components/link/link.css.map +1 -0
  68. package/libs/components/link/link.min.css +3 -0
  69. package/libs/components/list/list.css +1 -0
  70. package/libs/components/list/list.css.map +1 -0
  71. package/libs/components/list/list.min.css +3 -0
  72. package/libs/components/nav/nav.css +1 -0
  73. package/libs/components/nav/nav.css.map +1 -0
  74. package/libs/components/nav/nav.min.css +3 -0
  75. package/libs/components/popover/popover.css +1 -0
  76. package/libs/components/popover/popover.css.map +1 -0
  77. package/libs/components/popover/popover.min.css +3 -0
  78. package/libs/components/progress/progress.css +1 -0
  79. package/libs/components/progress/progress.css.map +1 -0
  80. package/libs/components/progress/progress.min.css +3 -0
  81. package/libs/components/stack/stack.css +1 -0
  82. package/libs/components/stack/stack.css.map +1 -0
  83. package/libs/components/stack/stack.min.css +3 -0
  84. package/libs/components/styles/index.css +1 -0
  85. package/libs/components/styles/index.css.map +1 -0
  86. package/libs/components/styles/index.min.css +3 -0
  87. package/libs/components/tag/tag.css +1 -0
  88. package/libs/components/tag/tag.css.map +1 -0
  89. package/libs/components/tag/tag.min.css +3 -0
  90. package/libs/components/text-to-speech/text-to-speech.css +1 -0
  91. package/libs/components/text-to-speech/text-to-speech.css.map +1 -0
  92. package/libs/components/text-to-speech/text-to-speech.min.css +3 -0
  93. package/libs/components/title/title.css +1 -0
  94. package/libs/components/title/title.css.map +1 -0
  95. package/libs/components/title/title.min.css +3 -0
  96. package/libs/{dialog-6c6b3588.d.ts → dialog-e28c085f.d.ts} +14 -1
  97. package/libs/index.cjs +38 -36
  98. package/libs/index.cjs.map +1 -1
  99. package/libs/index.css +1 -0
  100. package/libs/index.css.map +1 -0
  101. package/libs/index.d.cts +3 -32
  102. package/libs/index.d.ts +3 -32
  103. package/libs/index.js +13 -14
  104. package/libs/index.js.map +1 -1
  105. package/package.json +2 -2
  106. package/src/components/dialog/README.mdx +87 -2
  107. package/src/components/dialog/STYLES.mdx +119 -12
  108. package/src/components/dialog/dialog-modal.stories.tsx +107 -2
  109. package/src/components/dialog/dialog-modal.tsx +6 -0
  110. package/src/components/dialog/dialog.scss +89 -2
  111. package/src/components/dialog/dialog.stories.tsx +30 -3
  112. package/src/components/dialog/dialog.test.tsx +96 -0
  113. package/src/components/dialog/dialog.tsx +7 -1
  114. package/src/components/dialog/dialog.types.ts +25 -0
  115. package/src/components/dialog/views/dialog-header.tsx +10 -8
  116. package/src/index.ts +6 -2
  117. package/src/styles/dialog/dialog.css +88 -2
  118. package/src/styles/dialog/dialog.css.map +1 -1
  119. package/src/styles/index.css +87 -2
  120. package/src/styles/index.css.map +1 -1
  121. package/libs/chunk-VQTCTLFN.js +0 -11
  122. package/libs/chunk-VQTCTLFN.js.map +0 -1
  123. package/libs/chunk-ZOPHCNFD.cjs +0 -18
  124. package/libs/chunk-ZOPHCNFD.cjs.map +0 -1
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@fpkit/acss",
3
3
  "description": "A lightweight React UI library for building modern and accessible components that leverage CSS custom properties for reactive Styles.",
4
4
  "private": false,
5
- "version": "6.4.2",
5
+ "version": "6.5.0",
6
6
  "engines": {
7
7
  "node": ">=22.12.0",
8
8
  "npm": ">=8.0.0"
@@ -123,5 +123,5 @@
123
123
  "publishConfig": {
124
124
  "access": "public"
125
125
  },
126
- "gitHead": "e0e6e8a0bd3931cd33bfcabcab7f25f276bfe0bf"
126
+ "gitHead": "9063512fa822963d8151c972bed9f5b0e531df0f"
127
127
  }
@@ -15,6 +15,8 @@ A modern, accessible dialog component system built with React and TypeScript, le
15
15
  ✅ **Two Modes** - Modal dialogs (overlay) and inline alert dialogs
16
16
  ✅ **Focus Management** - Automatic focus restoration to trigger element
17
17
  ✅ **Flexible API** - Controlled `Dialog` or uncontrolled `DialogModal` wrapper
18
+ ✅ **Size Variants** - Small, medium, large, and full-screen dialog sizes
19
+ ✅ **Position Control** - Center, top, bottom, left/right drawers, and corner positioning
18
20
  ✅ **Customizable** - CSS custom properties for theming
19
21
  ✅ **Keyboard Accessible** - `:focus-visible` styles, Escape key support
20
22
  ✅ **High Contrast Mode** - Supports `prefers-contrast: high` media query
@@ -116,6 +118,8 @@ function MyComponent() {
116
118
  | `className` | `string` | `""` | Additional CSS classes to apply to the dialog |
117
119
  | `dialogLabel` | `string` | - | Optional `aria-label` for the dialog |
118
120
  | `styles` | `CSSProperties` | - | Inline styles to apply to the dialog element |
121
+ | `size` | `"sm" \| "md" \| "lg" \| "full"` | - | Size variant controlling dialog dimensions |
122
+ | `position` | `"center" \| "top" \| "bottom" \| "left" \| "right" \| "top-left" \| "top-right" \| "bottom-left" \| "bottom-right"` | `"center"` | Position of the dialog on screen |
119
123
 
120
124
  ---
121
125
 
@@ -251,6 +255,70 @@ function SimpleExample() {
251
255
  }
252
256
  ```
253
257
 
258
+ ### Size Variants
259
+
260
+ ```tsx
261
+ import { DialogModal } from "@fpkit/acss";
262
+
263
+ function SizeExample() {
264
+ return (
265
+ <>
266
+ {/* Small dialog (25rem) */}
267
+ <DialogModal dialogTitle="Small" btnLabel="Open Small" size="sm">
268
+ <p>A compact dialog for simple actions.</p>
269
+ </DialogModal>
270
+
271
+ {/* Medium dialog (32rem) */}
272
+ <DialogModal dialogTitle="Medium" btnLabel="Open Medium" size="md">
273
+ <p>A standard dialog for forms and content.</p>
274
+ </DialogModal>
275
+
276
+ {/* Large dialog (48rem) */}
277
+ <DialogModal dialogTitle="Large" btnLabel="Open Large" size="lg">
278
+ <p>A wide dialog for complex content.</p>
279
+ </DialogModal>
280
+
281
+ {/* Full-screen dialog */}
282
+ <DialogModal dialogTitle="Full Screen" btnLabel="Open Full" size="full">
283
+ <p>This dialog fills the entire viewport.</p>
284
+ </DialogModal>
285
+ </>
286
+ );
287
+ }
288
+ ```
289
+
290
+ ### Position Variants
291
+
292
+ ```tsx
293
+ import { DialogModal } from "@fpkit/acss";
294
+
295
+ function PositionExample() {
296
+ return (
297
+ <>
298
+ {/* Top-positioned dialog */}
299
+ <DialogModal dialogTitle="Top" btnLabel="Open Top" position="top">
300
+ <p>Positioned at the top center of the viewport.</p>
301
+ </DialogModal>
302
+
303
+ {/* Bottom sheet */}
304
+ <DialogModal dialogTitle="Bottom Sheet" btnLabel="Open Bottom" position="bottom">
305
+ <p>Positioned at the bottom, like a mobile bottom sheet.</p>
306
+ </DialogModal>
307
+
308
+ {/* Right drawer (full-height side panel) */}
309
+ <DialogModal dialogTitle="Right Drawer" btnLabel="Open Drawer" size="sm" position="right">
310
+ <p>A full-height drawer sliding in from the right.</p>
311
+ </DialogModal>
312
+
313
+ {/* Corner positioning */}
314
+ <DialogModal dialogTitle="Corner" btnLabel="Open Corner" size="sm" position="bottom-right">
315
+ <p>Positioned in the bottom-right corner.</p>
316
+ </DialogModal>
317
+ </>
318
+ );
319
+ }
320
+ ```
321
+
254
322
  ### Custom Styling
255
323
 
256
324
  ```tsx
@@ -370,7 +438,13 @@ Override these variables to customize the dialog appearance:
370
438
  ```css
371
439
  :root {
372
440
  /* Dimensions */
373
- --dialog-min-w: max(20rem, 80%);
441
+ --dialog-min-width: max(20rem, 80%);
442
+ --dialog-width: var(--dialog-min-width);
443
+ --dialog-max-width: 90vw;
444
+ --dialog-height: auto;
445
+ --dialog-max-height: 85vh;
446
+ --dialog-margin: auto;
447
+ --dialog-inset: 0;
374
448
  --dialog-gap: 0.625rem;
375
449
  --dialog-padding: 1.5rem;
376
450
  --dialog-padding-inline: 1rem;
@@ -421,6 +495,8 @@ import type {
421
495
  DialogModalProps,
422
496
  DialogHeaderProps,
423
497
  DialogFooterProps,
498
+ DialogSize,
499
+ DialogPosition,
424
500
  } from "@fpkit/acss";
425
501
  ```
426
502
 
@@ -429,6 +505,13 @@ import type {
429
505
  All components are fully typed with comprehensive JSDoc comments:
430
506
 
431
507
  ```tsx
508
+ type DialogSize = "sm" | "md" | "lg" | "full";
509
+ type DialogPosition =
510
+ | "center" | "top" | "bottom"
511
+ | "left" | "right"
512
+ | "top-left" | "top-right"
513
+ | "bottom-left" | "bottom-right";
514
+
432
515
  interface DialogProps extends BaseDialogProps {
433
516
  isOpen: boolean;
434
517
  onOpenChange: (open: boolean) => void;
@@ -438,6 +521,8 @@ interface DialogProps extends BaseDialogProps {
438
521
  confirmLabel?: string;
439
522
  cancelLabel?: string;
440
523
  hideFooter?: boolean;
524
+ size?: DialogSize;
525
+ position?: DialogPosition;
441
526
  }
442
527
  ```
443
528
 
@@ -554,7 +639,7 @@ For older browsers, consider using a polyfill like [`dialog-polyfill`](https://g
554
639
  ## Testing
555
640
 
556
641
  The dialog component has comprehensive test coverage:
557
- - ✓ 24 tests covering all scenarios
642
+ - ✓ 40 tests covering all scenarios
558
643
  - ✓ Controlled vs uncontrolled behavior
559
644
  - ✓ Modal vs non-modal rendering
560
645
  - ✓ ARIA attributes validation
@@ -18,6 +18,8 @@ accessibility and high contrast mode support.
18
18
 
19
19
  - **Native `<dialog>` element** - Built on HTML5 dialog with proper semantics
20
20
  - **Flexible layout** - Flexbox-based column layout with customizable gap
21
+ - **Size variants** - Small, medium, large, and full-screen via `data-size` attribute
22
+ - **Position variants** - Center, top, bottom, left/right drawers, and corners via `data-position` attribute
21
23
  - **Header with close button** - Pre-styled header section with close control
22
24
  - **Action footer** - Flexible footer for action buttons
23
25
  - **Keyboard accessibility** - Full keyboard navigation with visible focus
@@ -38,6 +40,12 @@ Root-level variables define the dialog's overall appearance:
38
40
  :root {
39
41
  /* Dimensions */
40
42
  --dialog-min-width: max(20rem, 80%); /* Responsive width: min 320px or 80% */
43
+ --dialog-width: var(--dialog-min-width); /* Actual applied width */
44
+ --dialog-max-width: 90vw; /* Maximum width cap */
45
+ --dialog-height: auto; /* Dialog height */
46
+ --dialog-max-height: 85vh; /* Maximum height cap */
47
+ --dialog-margin: auto; /* Positioning margin */
48
+ --dialog-inset: 0; /* Positioning inset */
41
49
 
42
50
  /* Layout */
43
51
  --dialog-display: flex;
@@ -739,7 +747,8 @@ All dialog CSS variables follow specific patterns:
739
747
 
740
748
  | Category | Variable Pattern | Example |
741
749
  | -------------- | ---------------------------- | ------------------------------------------------ |
742
- | **Dimensions** | `--dialog-{dimension}` | `--dialog-min-width` |
750
+ | **Dimensions** | `--dialog-{dimension}` | `--dialog-width`, `--dialog-max-width`, `--dialog-height` |
751
+ | **Position** | `--dialog-{position-prop}` | `--dialog-margin`, `--dialog-inset` |
743
752
  | **Layout** | `--dialog-{layout-property}` | `--dialog-display`, `--dialog-flex-direction` |
744
753
  | **Spacing** | `--dialog-{spacing-type}` | `--dialog-padding`, `--dialog-gap` |
745
754
  | **Borders** | `--dialog-border-{property}` | `--dialog-border-color`, `--dialog-border-width` |
@@ -752,6 +761,12 @@ All dialog CSS variables follow specific patterns:
752
761
  ```css
753
762
  /* Dimensions */
754
763
  --dialog-min-width /* Minimum width (responsive) */
764
+ --dialog-width /* Applied width (defaults to min-width) */
765
+ --dialog-max-width /* Maximum width cap (90vw) */
766
+ --dialog-height /* Dialog height (auto) */
767
+ --dialog-max-height /* Maximum height cap (85vh) */
768
+ --dialog-margin /* Positioning margin (auto) */
769
+ --dialog-inset /* Positioning inset (0) */
755
770
 
756
771
  /* Layout */
757
772
  --dialog-display /* Display mode (flex) */
@@ -815,29 +830,121 @@ For browsers without native `<dialog>` support, use:
815
830
  </script>
816
831
  ```
817
832
 
818
- ## Performance Tips
833
+ ## Size Variants
834
+
835
+ Control dialog dimensions using the `size` prop (React) or `data-size` attribute (HTML).
836
+ Sizes are applied via `data-size` attribute selectors in CSS.
819
837
 
820
- ### Avoid Inline Styles
838
+ | Size | Width | Description |
839
+ |------|-------|-------------|
840
+ | `sm` | 25rem (400px) | Compact dialog for simple confirmations |
841
+ | `md` | 32rem (512px) | Standard dialog for forms |
842
+ | `lg` | 48rem (768px) | Wide dialog for complex content |
843
+ | `full` | 100vw/100vh | Full-screen dialog filling the viewport |
821
844
 
822
- Create reusable dialog variants:
845
+ ### CSS for Size Variants
823
846
 
824
847
  ```css
825
- .dialog-sm {
826
- --dialog-min-width: max(15rem, 50%);
827
- --dialog-padding: 1rem;
848
+ dialog[data-size="sm"] { --dialog-width: 25rem; }
849
+ dialog[data-size="md"] { --dialog-width: 32rem; }
850
+ dialog[data-size="lg"] { --dialog-width: 48rem; }
851
+
852
+ /* Full-screen uses direct values to override browser UA stylesheet */
853
+ dialog[data-size="full"] {
854
+ box-sizing: border-box;
855
+ width: 100vw;
856
+ max-width: 100vw;
857
+ height: 100vh;
858
+ max-height: 100vh;
859
+ margin: 0;
860
+ inset: 0;
861
+ border: 0;
862
+ border-radius: 0;
828
863
  }
864
+ ```
829
865
 
830
- .dialog-lg {
831
- --dialog-min-width: max(40rem, 90%);
832
- --dialog-padding: 2rem;
866
+ ### React Usage
867
+
868
+ ```tsx
869
+ <Dialog size="sm" ... />
870
+ <DialogModal size="lg" btnLabel="Open Large" ... />
871
+ ```
872
+
873
+ ### HTML Usage
874
+
875
+ ```html
876
+ <dialog data-size="sm">Small dialog</dialog>
877
+ <dialog data-size="full">Full-screen dialog</dialog>
878
+ ```
879
+
880
+ ## Position Variants
881
+
882
+ Control dialog placement using the `position` prop (React) or `data-position` attribute (HTML).
883
+ Left/right positions create full-height drawer panels.
884
+
885
+ | Position | Behavior |
886
+ |----------|----------|
887
+ | `center` | Centered on screen (default) |
888
+ | `top` | Anchored to top center |
889
+ | `bottom` | Anchored to bottom center (bottom sheet) |
890
+ | `left` | Full-height left drawer |
891
+ | `right` | Full-height right drawer |
892
+ | `top-left` | Anchored to top-left corner |
893
+ | `top-right` | Anchored to top-right corner |
894
+ | `bottom-left` | Anchored to bottom-left corner |
895
+ | `bottom-right` | Anchored to bottom-right corner |
896
+
897
+ ### CSS for Position Variants
898
+
899
+ Position variants override `--dialog-margin` and `--dialog-inset` to control placement:
900
+
901
+ ```css
902
+ dialog[data-position="top"] {
903
+ --dialog-margin: 0 auto auto auto;
904
+ --dialog-inset: 0;
905
+ }
906
+
907
+ dialog[data-position="right"] {
908
+ --dialog-margin: 0 0 0 auto;
909
+ --dialog-inset: 0;
910
+ --dialog-height: 100vh;
911
+ --dialog-max-height: 100vh;
912
+ --dialog-border-radius: 0;
833
913
  }
834
914
  ```
835
915
 
916
+ ### React Usage
917
+
918
+ ```tsx
919
+ <Dialog position="top" ... />
920
+ <DialogModal size="sm" position="right" btnLabel="Open Drawer" ... />
921
+ ```
922
+
923
+ ### HTML Usage
924
+
836
925
  ```html
837
- <dialog class="dialog-sm">Small dialog</dialog>
838
- <dialog class="dialog-lg">Large dialog</dialog>
926
+ <dialog data-position="bottom">Bottom sheet</dialog>
927
+ <dialog data-size="sm" data-position="right">Right drawer</dialog>
839
928
  ```
840
929
 
930
+ ### Combining Size and Position
931
+
932
+ Size and position can be combined for precise control:
933
+
934
+ ```tsx
935
+ {/* Right drawer panel */}
936
+ <DialogModal size="sm" position="right" dialogTitle="Settings" btnLabel="Open">
937
+ <p>Drawer content</p>
938
+ </DialogModal>
939
+
940
+ {/* Small bottom-right notification */}
941
+ <DialogModal size="sm" position="bottom-right" dialogTitle="Notice" btnLabel="Show">
942
+ <p>Corner notification</p>
943
+ </DialogModal>
944
+ ```
945
+
946
+ ## Performance Tips
947
+
841
948
  ### Use Native Dialog Methods
842
949
 
843
950
  Prefer `.showModal()` over custom implementations:
@@ -38,6 +38,18 @@ const meta: Meta<typeof DialogModal> = {
38
38
  isOpen: false,
39
39
  onClose: () => {},
40
40
  },
41
+ argTypes: {
42
+ size: {
43
+ control: "select",
44
+ options: ["sm", "md", "lg", "full"],
45
+ description: "Size variant controlling dialog dimensions",
46
+ },
47
+ position: {
48
+ control: "select",
49
+ options: ["center", "top", "bottom", "left", "right", "top-left", "top-right", "bottom-left", "bottom-right"],
50
+ description: "Position of the dialog on screen",
51
+ },
52
+ },
41
53
  } as Meta;
42
54
 
43
55
  export default meta;
@@ -49,9 +61,14 @@ export const Default: Story = {
49
61
  "DialogModal is a modal dialog component that provides an accessible overlay for displaying content.",
50
62
  },
51
63
  decorators: [WithInstructions()],
52
- play: async ({ canvasElement }) => {
64
+ play: async ({ canvasElement, step }) => {
53
65
  const canvas = within(canvasElement);
54
- expect(canvas.getByRole("dialog")).toBeInTheDocument();
66
+
67
+ await step("Dialog renders with default center position", async () => {
68
+ const dialog = canvas.getByRole("dialog");
69
+ expect(dialog).toBeInTheDocument();
70
+ expect(dialog).toHaveAttribute("data-position", "center");
71
+ });
55
72
  },
56
73
  } as Story;
57
74
 
@@ -181,3 +198,91 @@ export const IconTriggerWithOutlineVariant: Story = {
181
198
  });
182
199
  },
183
200
  } as Story;
201
+
202
+ /* ── Size variant stories ──────────────────── */
203
+
204
+ export const SmallDialog: Story = {
205
+ args: {
206
+ dialogTitle: "Small Dialog",
207
+ btnLabel: "Open Small",
208
+ size: "sm",
209
+ children: "This is a small (25rem) dialog.",
210
+ },
211
+ } as Story;
212
+
213
+ export const MediumDialog: Story = {
214
+ args: {
215
+ dialogTitle: "Medium Dialog",
216
+ btnLabel: "Open Medium",
217
+ size: "md",
218
+ children: "This is a medium (32rem) dialog for forms and standard content.",
219
+ },
220
+ } as Story;
221
+
222
+ export const LargeDialog: Story = {
223
+ args: {
224
+ dialogTitle: "Large Dialog",
225
+ btnLabel: "Open Large",
226
+ size: "lg",
227
+ children: "This is a large (48rem) dialog with more room for complex content.",
228
+ },
229
+ } as Story;
230
+
231
+ export const FullScreenDialog: Story = {
232
+ args: {
233
+ dialogTitle: "Full Screen Dialog",
234
+ btnLabel: "Open Full Screen",
235
+ size: "full",
236
+ children: "This dialog takes up the entire viewport.",
237
+ },
238
+ } as Story;
239
+
240
+ /* ── Position variant stories ──────────────── */
241
+
242
+ export const TopPositioned: Story = {
243
+ args: {
244
+ dialogTitle: "Top Dialog",
245
+ btnLabel: "Open Top",
246
+ position: "top",
247
+ children: "This dialog is positioned at the top center of the viewport.",
248
+ },
249
+ } as Story;
250
+
251
+ export const BottomSheet: Story = {
252
+ args: {
253
+ dialogTitle: "Bottom Sheet",
254
+ btnLabel: "Open Bottom Sheet",
255
+ position: "bottom",
256
+ children: "This dialog is positioned at the bottom, like a mobile bottom sheet.",
257
+ },
258
+ } as Story;
259
+
260
+ export const RightDrawer: Story = {
261
+ args: {
262
+ dialogTitle: "Right Drawer",
263
+ btnLabel: "Open Right Drawer",
264
+ size: "sm",
265
+ position: "right",
266
+ children: "This dialog slides in from the right as a full-height drawer panel.",
267
+ },
268
+ } as Story;
269
+
270
+ export const LeftDrawer: Story = {
271
+ args: {
272
+ dialogTitle: "Left Drawer",
273
+ btnLabel: "Open Left Drawer",
274
+ size: "sm",
275
+ position: "left",
276
+ children: "This dialog slides in from the left as a full-height drawer panel.",
277
+ },
278
+ } as Story;
279
+
280
+ export const BottomRightPositioned: Story = {
281
+ args: {
282
+ dialogTitle: "Bottom Right",
283
+ btnLabel: "Open Bottom Right",
284
+ size: "sm",
285
+ position: "bottom-right",
286
+ children: "This dialog appears in the bottom-right corner.",
287
+ },
288
+ } as Story;
@@ -81,6 +81,9 @@ export const DialogModal: React.FC<DialogModalProps> = ({
81
81
  hideFooter = false,
82
82
  btnProps,
83
83
  icon,
84
+ size,
85
+ position,
86
+ closeIconSize,
84
87
  }) => {
85
88
  const [isOpen, setIsOpen] = useState(false);
86
89
  const lastFocusedElement = useRef<HTMLElement | null>(null);
@@ -150,6 +153,9 @@ export const DialogModal: React.FC<DialogModalProps> = ({
150
153
  confirmLabel={confirmLabel}
151
154
  cancelLabel={cancelLabel}
152
155
  hideFooter={hideFooter}
156
+ size={size}
157
+ position={position}
158
+ closeIconSize={closeIconSize}
153
159
  >
154
160
  {children}
155
161
  </Dialog>
@@ -1,5 +1,10 @@
1
1
  :root {
2
2
  --dialog-min-width: max(20rem, 80%);
3
+ --dialog-width: var(--dialog-min-width);
4
+ --dialog-max-width: 90vw;
5
+ --dialog-max-height: 85vh;
6
+ --dialog-margin: auto;
7
+ --dialog-inset: 0;
3
8
  --dialog-gap: 0.625rem;
4
9
  --dialog-border-color: var(--color-border);
5
10
  --dialog-border-width: thin;
@@ -33,8 +38,11 @@
33
38
  }
34
39
 
35
40
  dialog {
36
- width: var(--dialog-min-width);
37
- min-width: var(--dialog-min-width);
41
+ margin: var(--dialog-margin);
42
+ inset: var(--dialog-inset);
43
+ width: var(--dialog-width);
44
+ max-width: var(--dialog-max-width);
45
+ max-height: var(--dialog-max-height);
38
46
  gap: var(--dialog-gap);
39
47
  border: var(--dialog-border-color) var(--dialog-border-width) solid;
40
48
  border-radius: var(--dialog-border-radius);
@@ -125,3 +133,82 @@ dialog {
125
133
  outline: none;
126
134
  }
127
135
  }
136
+
137
+ /* ── Size variants ─────────────────────────── */
138
+
139
+ dialog[data-size="sm"] {
140
+ --dialog-width: 25rem;
141
+ }
142
+
143
+ dialog[data-size="md"] {
144
+ --dialog-width: 32rem;
145
+ }
146
+
147
+ dialog[data-size="lg"] {
148
+ --dialog-width: 48rem;
149
+ }
150
+
151
+ dialog[data-size="full"] {
152
+ box-sizing: border-box;
153
+ width: 100vw;
154
+ max-width: 100vw;
155
+ height: 100vh;
156
+ max-height: 100vh;
157
+ margin: 0;
158
+ inset: 0;
159
+ border: 0;
160
+ border-radius: 0;
161
+ }
162
+
163
+ /* ── Position variants ─────────────────────── */
164
+
165
+ dialog[data-position="center"] {
166
+ --dialog-margin: auto;
167
+ --dialog-inset: 0;
168
+ }
169
+
170
+ dialog[data-position="top"] {
171
+ --dialog-margin: 0 auto auto auto;
172
+ --dialog-inset: 0;
173
+ }
174
+
175
+ dialog[data-position="bottom"] {
176
+ --dialog-margin: auto auto 0 auto;
177
+ --dialog-inset: 0;
178
+ }
179
+
180
+ dialog[data-position="left"] {
181
+ --dialog-margin: 0 auto 0 0;
182
+ --dialog-inset: 0;
183
+ height: 100vh;
184
+ max-height: 100vh;
185
+ border-radius: 0;
186
+ }
187
+
188
+ dialog[data-position="right"] {
189
+ --dialog-margin: 0 0 0 auto;
190
+ --dialog-inset: 0;
191
+ height: 100vh;
192
+ max-height: 100vh;
193
+ border-radius: 0;
194
+ }
195
+
196
+ dialog[data-position="top-left"] {
197
+ --dialog-margin: 0 auto auto 0;
198
+ --dialog-inset: 0;
199
+ }
200
+
201
+ dialog[data-position="top-right"] {
202
+ --dialog-margin: 0 0 auto auto;
203
+ --dialog-inset: 0;
204
+ }
205
+
206
+ dialog[data-position="bottom-left"] {
207
+ --dialog-margin: auto auto 0 0;
208
+ --dialog-inset: 0;
209
+ }
210
+
211
+ dialog[data-position="bottom-right"] {
212
+ --dialog-margin: auto 0 0 auto;
213
+ --dialog-inset: 0;
214
+ }
@@ -41,6 +41,18 @@ const meta: Meta<typeof Dialog> = {
41
41
  args: {
42
42
  children: content,
43
43
  },
44
+ argTypes: {
45
+ size: {
46
+ control: "select",
47
+ options: ["sm", "md", "lg", "full"],
48
+ description: "Size variant controlling dialog dimensions",
49
+ },
50
+ position: {
51
+ control: "select",
52
+ options: ["center", "top", "bottom", "left", "right", "top-left", "top-right", "bottom-left", "bottom-right"],
53
+ description: "Position of the dialog on screen (defaults to center)",
54
+ },
55
+ },
44
56
  decorators: [
45
57
  (Story) => {
46
58
  return (
@@ -71,6 +83,15 @@ export const BasicDialog: Story = {
71
83
  onOpenChange: () => {},
72
84
  dialogTitle: "Basic Dialog",
73
85
  },
86
+ play: async ({ canvasElement, step }) => {
87
+ const canvas = within(canvasElement);
88
+
89
+ await step("Dialog defaults to center position", async () => {
90
+ const dialog = canvas.getByRole("dialog");
91
+ await expect(dialog).toBeInTheDocument();
92
+ await expect(dialog).toHaveAttribute("data-position", "center");
93
+ });
94
+ },
74
95
  } as Story;
75
96
 
76
97
  /**
@@ -84,9 +105,14 @@ export const NonModalDialog: Story = {
84
105
  isAlertDialog: true,
85
106
  dialogTitle: "Non Modal Dialog",
86
107
  },
87
- play: async ({ canvasElement }) => {
108
+ play: async ({ canvasElement, step }) => {
88
109
  const canvas = within(canvasElement);
89
- await expect(canvas.getByRole("alertdialog")).toBeInTheDocument();
110
+
111
+ await step("Alert dialog renders with default center position", async () => {
112
+ const dialog = canvas.getByRole("alertdialog");
113
+ await expect(dialog).toBeInTheDocument();
114
+ await expect(dialog).toHaveAttribute("data-position", "center");
115
+ });
90
116
  },
91
117
  } as Story;
92
118
 
@@ -106,8 +132,9 @@ export const DialogInteractions: Story = {
106
132
  const dialog = canvas.getByRole("dialog");
107
133
  const closeButton = canvas.getByRole("button", { name: /close dialog/i });
108
134
 
109
- await step("Modal is rendered", async () => {
135
+ await step("Modal is rendered with default center position", async () => {
110
136
  await expect(dialog).toBeInTheDocument();
137
+ await expect(dialog).toHaveAttribute("data-position", "center");
111
138
  await expect(closeButton).toBeInTheDocument();
112
139
  });
113
140