doom-design-system 0.4.16 → 0.4.17

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.
@@ -12,13 +12,15 @@ import { Drawer } from "doom-design-system";
12
12
  | ---------- | ---------------------- | ----------- | ------------------- |
13
13
  | `isOpen` | `boolean` | required | Controls visibility |
14
14
  | `onClose` | `() => void` | required | Close callback |
15
- | `title` | `string` | — | Header title |
15
+ | `title` | `ReactNode` | — | Header title |
16
16
  | `side` | `"left" \| "right"` | `"right"` | Slide direction |
17
17
  | `children` | `ReactNode` | required | Drawer content |
18
18
  | `footer` | `ReactNode` | — | Footer content |
19
19
  | `variant` | `"default" \| "solid"` | `"default"` | Visual style |
20
20
 
21
- ## Usage
21
+ ## Usage Patterns
22
+
23
+ ### Shorthand API (simple drawers)
22
24
 
23
25
  ```tsx
24
26
  <Drawer
@@ -31,9 +33,37 @@ import { Drawer } from "doom-design-system";
31
33
  </Drawer>
32
34
  ```
33
35
 
36
+ ### Composition API (custom layouts)
37
+
38
+ ```tsx
39
+ <Drawer isOpen={isOpen} onClose={onClose} side="right">
40
+ <Drawer.Header>
41
+ <Text variant="h3">Custom Title</Text>
42
+ </Drawer.Header>
43
+ <Drawer.Body>
44
+ <Stack gap={4}>{/* Custom content */}</Stack>
45
+ </Drawer.Body>
46
+ <Drawer.Footer>
47
+ <Button variant="ghost" onClick={onClose}>
48
+ Cancel
49
+ </Button>
50
+ <Button onClick={handleSave}>Save</Button>
51
+ </Drawer.Footer>
52
+ </Drawer>
53
+ ```
54
+
55
+ ## Sub-components
56
+
57
+ | Component | Description |
58
+ | --------------- | ------------------------ |
59
+ | `Drawer.Header` | Header with close button |
60
+ | `Drawer.Body` | Scrollable content area |
61
+ | `Drawer.Footer` | Action buttons area |
62
+
34
63
  ## Guidelines
35
64
 
36
65
  - Use for side panels, settings, or detail views.
37
66
  - `side="left"` for navigation drawers, `side="right"` for actions/details.
38
67
  - Closes on Escape key and overlay click.
39
68
  - Use `variant="solid"` for high-emphasis content.
69
+ - Use composition API when you need custom header content (icons, badges, etc.).
@@ -3,7 +3,7 @@
3
3
  ## Import
4
4
 
5
5
  ```tsx
6
- import { Modal, ModalHeader, ModalBody, ModalFooter } from "doom-design-system";
6
+ import { Modal } from "doom-design-system";
7
7
  ```
8
8
 
9
9
  ## Props
@@ -18,7 +18,7 @@ import { Modal, ModalHeader, ModalBody, ModalFooter } from "doom-design-system";
18
18
 
19
19
  ## Usage Patterns
20
20
 
21
- ### Shorthand API (recommended for simple modals)
21
+ ### Shorthand API (simple modals)
22
22
 
23
23
  ```tsx
24
24
  <Modal
@@ -38,24 +38,32 @@ import { Modal, ModalHeader, ModalBody, ModalFooter } from "doom-design-system";
38
38
  </Modal>
39
39
  ```
40
40
 
41
- ### Composition API (for custom layouts)
41
+ ### Composition API (custom layouts)
42
42
 
43
43
  ```tsx
44
44
  <Modal isOpen={isOpen} onClose={onClose}>
45
- <ModalHeader>
45
+ <Modal.Header>
46
46
  <Text variant="h3">Custom Title</Text>
47
- </ModalHeader>
48
- <ModalBody>
47
+ </Modal.Header>
48
+ <Modal.Body>
49
49
  <Stack gap={4}>
50
50
  <Text>Custom body content...</Text>
51
51
  </Stack>
52
- </ModalBody>
53
- <ModalFooter>
52
+ </Modal.Body>
53
+ <Modal.Footer>
54
54
  <Button onClick={onClose}>Close</Button>
55
- </ModalFooter>
55
+ </Modal.Footer>
56
56
  </Modal>
57
57
  ```
58
58
 
59
+ ## Sub-components
60
+
61
+ | Component | Description |
62
+ | -------------- | ------------------------ |
63
+ | `Modal.Header` | Header with close button |
64
+ | `Modal.Body` | Scrollable content area |
65
+ | `Modal.Footer` | Action buttons area |
66
+
59
67
  ## Guidelines
60
68
 
61
69
  - Use `variant="solid"` for high-impact announcements or critical alerts.
@@ -12,35 +12,60 @@ import { Sheet } from "doom-design-system";
12
12
  | ---------- | ---------------------- | ----------- | ------------------- |
13
13
  | `isOpen` | `boolean` | required | Controls visibility |
14
14
  | `onClose` | `() => void` | required | Close callback |
15
- | `title` | `string` | — | Header title |
15
+ | `title` | `ReactNode` | — | Header title |
16
16
  | `children` | `ReactNode` | required | Sheet content |
17
17
  | `footer` | `ReactNode` | — | Footer content |
18
18
  | `variant` | `"default" \| "solid"` | `"default"` | Visual style |
19
19
 
20
- ## Usage
20
+ ## Usage Patterns
21
+
22
+ ### Shorthand API (simple sheets)
21
23
 
22
24
  ```tsx
23
25
  <Sheet
24
26
  isOpen={isOpen}
25
27
  onClose={() => setIsOpen(false)}
26
- title="Filters"
27
- footer={
28
- <Flex gap={2}>
29
- <Button variant="ghost" onClick={reset}>
30
- Reset
31
- </Button>
32
- <Button onClick={apply}>Apply</Button>
33
- </Flex>
34
- }
28
+ title="Options"
29
+ footer={<Button onClick={handleConfirm}>Confirm</Button>}
35
30
  >
36
- <Stack gap={4}>{/* Filter controls */}</Stack>
31
+ <RadioGroup options={options} />
32
+ </Sheet>
33
+ ```
34
+
35
+ ### Composition API (custom layouts)
36
+
37
+ ```tsx
38
+ <Sheet isOpen={isOpen} onClose={onClose}>
39
+ <Sheet.Header>
40
+ <Flex align="center" gap={2}>
41
+ <Icon name="settings" />
42
+ <Text variant="h4">Advanced Options</Text>
43
+ </Flex>
44
+ </Sheet.Header>
45
+ <Sheet.Body>
46
+ <Stack gap={4}>{/* Custom content */}</Stack>
47
+ </Sheet.Body>
48
+ <Sheet.Footer>
49
+ <Button variant="ghost" onClick={onClose}>
50
+ Cancel
51
+ </Button>
52
+ <Button onClick={handleSave}>Apply</Button>
53
+ </Sheet.Footer>
37
54
  </Sheet>
38
55
  ```
39
56
 
57
+ ## Sub-components
58
+
59
+ | Component | Description |
60
+ | -------------- | ----------------------- |
61
+ | `Sheet.Header` | Header with drag handle |
62
+ | `Sheet.Body` | Scrollable content area |
63
+ | `Sheet.Footer` | Action buttons area |
64
+
40
65
  ## Guidelines
41
66
 
42
- - Slides up from bottom of screen (mobile-friendly).
43
- - Supports drag-to-dismiss gesture (swipe down to close).
67
+ - Use for bottom sheets on mobile or compact overlays.
68
+ - Supports drag-to-dismiss gesture (drag down 150px+ to close).
44
69
  - Closes on Escape key and overlay click.
45
- - Use for mobile-first overlays, filters, or action sheets.
46
- - For side panels, use `Drawer` instead.
70
+ - Use `variant="solid"` for high-emphasis content.
71
+ - Use composition API when you need custom header content.
@@ -9,23 +9,24 @@ import { ColumnDef } from "@tanstack/react-table";
9
9
 
10
10
  ## Props
11
11
 
12
- | Prop | Type | Default | Description |
13
- | ------------------------- | -------------------------------------- | ------------ | ------------------------------------------ |
14
- | `data` | `T[]` | required | Array of row data |
15
- | `columns` | `ColumnDef<T>[]` | required | TanStack column definitions |
16
- | `enablePagination` | `boolean` | `true` | Enable pagination controls |
17
- | `enableFiltering` | `boolean` | `true` | Enable global search |
18
- | `enableColumnFilters` | `boolean` | `true` | Enable per-column filters |
19
- | `enableSorting` | `boolean` | `true` | Enable column sorting |
20
- | `enableVirtualization` | `boolean` | `false` | Virtualize rows for large datasets |
21
- | `enableAdvancedFiltering` | `boolean` | `false` | Enable FilterBuilder UI |
22
- | `pageSize` | `number` | `10` | Default rows per page |
23
- | `height` | `string \| number` | — | Fixed height (required for virtualization) |
24
- | `variant` | `"default" \| "flat"` | `"default"` | Visual style |
25
- | `density` | `"compact" \| "standard" \| "relaxed"` | `"standard"` | Row padding |
26
- | `striped` | `boolean` | `false` | Alternating row colors |
27
- | `filters` | `FilterConfig[]` | | Filter definitions for advanced filtering |
28
- | `toolbarContent` | `ReactNode` | — | Custom toolbar content |
12
+ | Prop | Type | Default | Description |
13
+ | ------------------------- | -------------------------------------- | ------------ | ---------------------------------------------- |
14
+ | `data` | `T[]` | required | Array of row data |
15
+ | `columns` | `ColumnDef<T>[]` | required | TanStack column definitions |
16
+ | `enablePagination` | `boolean` | `true` | Enable pagination controls |
17
+ | `enableFiltering` | `boolean` | `true` | Enable global search |
18
+ | `enableColumnFilters` | `boolean` | `true` | Enable per-column filters |
19
+ | `enableSorting` | `boolean` | `true` | Enable column sorting |
20
+ | `enableVirtualization` | `boolean` | `false` | Virtualize rows for large datasets |
21
+ | `enableAdvancedFiltering` | `boolean` | `false` | Enable FilterBuilder UI |
22
+ | `pageSize` | `number` | `10` | Default rows per page |
23
+ | `height` | `string \| number` | — | Fixed height (scrolls body with sticky header) |
24
+ | `maxHeight` | `string \| number` | | Max height (scrolls body with sticky header) |
25
+ | `variant` | `"default" \| "flat"` | `"default"` | Visual style |
26
+ | `density` | `"compact" \| "standard" \| "relaxed"` | `"standard"` | Row padding |
27
+ | `striped` | `boolean` | `false` | Alternating row colors |
28
+ | `filters` | `FilterConfig[]` | — | Filter definitions for advanced filtering |
29
+ | `toolbarContent` | `ReactNode` | — | Custom toolbar content |
29
30
 
30
31
  ## Column Definition
31
32
 
@@ -201,7 +201,7 @@ export function Combobox({ options, value, onChange, placeholder = "Select...",
201
201
  if (inline) {
202
202
  return dropdownContent;
203
203
  }
204
- return (_jsx(Popover, { content: dropdownContent, isOpen: isOpen, placement: "bottom-start", trigger: _jsxs("button", { className: clsx(styles.trigger, styles[size], hasValue && styles.hasValue, disabled && styles.disabled, className), disabled: disabled, type: "button", onClick: handleOpen, children: [_jsx(Text, { as: "span", className: styles.triggerText, children: displayText }), _jsxs("span", { className: styles.triggerIcons, children: [hasValue && !disabled && (_jsx("span", { className: styles.clearButton, role: "button", tabIndex: 0, onClick: handleClear, onKeyDown: (e) => {
204
+ return (_jsx(Popover, { content: dropdownContent, isOpen: isOpen, placement: "bottom-start", trigger: _jsxs("button", { "aria-expanded": isOpen, className: clsx(styles.trigger, styles[size], hasValue && styles.hasValue, disabled && styles.disabled, className), disabled: disabled, type: "button", onClick: handleOpen, children: [_jsx(Text, { as: "span", className: styles.triggerText, children: displayText }), _jsxs("span", { className: styles.triggerIcons, children: [hasValue && !disabled && (_jsx("span", { className: styles.clearButton, role: "button", tabIndex: 0, onClick: handleClear, onKeyDown: (e) => {
205
205
  if (e.key === "Enter") {
206
206
  handleClear(e);
207
207
  }
@@ -8,15 +8,24 @@
8
8
  align-items: center;
9
9
  justify-content: space-between;
10
10
  width: 100%;
11
- padding: var(--spacing-sm);
12
- width: 100%;
13
- padding: var(--spacing-sm);
11
+ padding: var(--spacing-md) var(--spacing-lg);
12
+ background: var(--card-bg);
13
+ font-size: var(--text-base);
14
+ font-weight: var(--font-bold);
15
+ text-transform: uppercase;
16
+ letter-spacing: 0.05em;
14
17
  text-align: left;
15
18
  cursor: pointer;
19
+ min-height: var(--size-lg);
16
20
  transition: all var(--duration-fast) var(--ease-in-out);
17
21
  }
22
+ .trigger:hover {
23
+ transform: translate(-2px, -2px);
24
+ box-shadow: 6px 6px 0px 0px var(--shadow-base);
25
+ }
18
26
  .trigger.sm {
19
- padding: var(--spacing-xs) var(--spacing-sm);
27
+ padding: var(--spacing-sm) var(--spacing-md);
28
+ min-height: var(--size-md);
20
29
  font-size: var(--text-xs);
21
30
  }
22
31
  .trigger.hasValue {
@@ -78,6 +87,9 @@
78
87
  max-width: 320px;
79
88
  padding: 0;
80
89
  overflow: hidden;
90
+ border: var(--border-width) solid var(--primary);
91
+ border-radius: var(--radius);
92
+ background: var(--card-bg);
81
93
  }
82
94
 
83
95
  .searchWrapper {
@@ -119,6 +131,10 @@
119
131
  max-height: 240px;
120
132
  overflow-y: auto;
121
133
  padding: var(--spacing-xs);
134
+ padding-bottom: var(--spacing-sm);
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: var(--spacing-xs);
122
138
  }
123
139
  .optionsList::-webkit-scrollbar {
124
140
  width: 6px;
@@ -128,7 +144,7 @@
128
144
  }
129
145
  .optionsList::-webkit-scrollbar-thumb {
130
146
  background: var(--border);
131
- border-radius: 4px;
147
+ border-radius: var(--radius-sm);
132
148
  }
133
149
  .optionsList::-webkit-scrollbar-thumb:hover {
134
150
  background: var(--muted-foreground);
@@ -139,31 +155,31 @@
139
155
  align-items: center;
140
156
  gap: var(--spacing-sm);
141
157
  width: 100%;
142
- padding: var(--spacing-xs) var(--spacing-sm);
143
- margin-bottom: 2px;
158
+ padding: var(--spacing-sm) var(--spacing-md);
144
159
  border: none;
145
- border-radius: var(--radius-sm);
146
- background: transparent;
147
- border-radius: var(--radius-sm);
160
+ border-radius: calc(var(--radius) - 2px);
148
161
  background: transparent;
149
162
  text-align: left;
150
163
  color: var(--foreground);
151
164
  cursor: pointer;
165
+ font-size: var(--text-base);
166
+ font-weight: var(--font-regular);
152
167
  transition: all var(--duration-fast) var(--ease-in-out);
168
+ user-select: none;
153
169
  }
154
170
  .option:hover {
155
- background: var(--surface-accent);
171
+ background-color: color-mix(in srgb, var(--primary), transparent 85%);
172
+ color: color-mix(in srgb, var(--primary), var(--foreground) 25%);
156
173
  }
157
174
  .option.selected {
158
- background: color-mix(in srgb, var(--primary) 8%, transparent);
159
- color: var(--primary);
160
- font-weight: 500;
175
+ background: var(--primary);
176
+ color: var(--primary-foreground);
177
+ font-weight: var(--font-bold);
161
178
  }
162
179
  .option.selected:hover {
163
- background: color-mix(in srgb, var(--primary) 12%, transparent);
164
- }
165
- .option:last-child {
166
- margin-bottom: 0;
180
+ background-color: var(--primary);
181
+ color: var(--primary-foreground);
182
+ filter: brightness(1.1);
167
183
  }
168
184
 
169
185
  .optionLabel {
@@ -1,13 +1,32 @@
1
1
  import React from "react";
2
+ interface DrawerHeaderProps {
3
+ children: React.ReactNode;
4
+ className?: string;
5
+ id?: string;
6
+ }
7
+ export declare function DrawerHeader({ children, className, id }: DrawerHeaderProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function DrawerBody({ children, className, }: {
9
+ children: React.ReactNode;
10
+ className?: string;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ export declare function DrawerFooter({ children, className, }: {
13
+ children: React.ReactNode;
14
+ className?: string;
15
+ }): import("react/jsx-runtime").JSX.Element;
2
16
  interface DrawerProps {
3
17
  isOpen: boolean;
4
18
  onClose: () => void;
5
- title?: string;
19
+ title?: React.ReactNode;
6
20
  side?: "left" | "right";
7
21
  children: React.ReactNode;
8
22
  footer?: React.ReactNode;
9
23
  className?: string;
10
24
  variant?: "default" | "solid";
11
25
  }
12
- export declare function Drawer({ isOpen, onClose, title, side, children, footer, className, variant, }: DrawerProps): React.ReactPortal | null;
26
+ declare function DrawerInternal({ isOpen, onClose, title, side, children, footer, className, variant, }: DrawerProps): React.ReactPortal | null;
27
+ export declare const Drawer: typeof DrawerInternal & {
28
+ Header: typeof DrawerHeader;
29
+ Body: typeof DrawerBody;
30
+ Footer: typeof DrawerFooter;
31
+ };
13
32
  export {};
@@ -5,8 +5,24 @@ import { X } from "lucide-react";
5
5
  import React, { useEffect, useState } from "react";
6
6
  import { createPortal } from "react-dom";
7
7
  import { Button } from "../Button/Button.js";
8
+ import { Flex } from "../Layout/Layout.js";
8
9
  import styles from "./Drawer.module.css";
9
- export function Drawer({ isOpen, onClose, title, side = "right", children, footer, className, variant = "default", }) {
10
+ // Context for composition API
11
+ const DrawerContext = React.createContext({
12
+ onClose: () => { },
13
+ variant: "default",
14
+ });
15
+ export function DrawerHeader({ children, className, id }) {
16
+ const { onClose, titleId } = React.useContext(DrawerContext);
17
+ return (_jsxs(Flex, { align: "center", className: clsx(styles.header, className), justify: "space-between", children: [_jsx("div", { className: styles.headerContent, id: id || titleId, children: children }), _jsx(Button, { "aria-label": "Close drawer", size: "sm", variant: "danger", onClick: onClose, children: _jsx(X, { size: 24 }) })] }));
18
+ }
19
+ export function DrawerBody({ children, className, }) {
20
+ return _jsx("div", { className: clsx(styles.content, className), children: children });
21
+ }
22
+ export function DrawerFooter({ children, className, }) {
23
+ return (_jsx(Flex, { align: "center", className: clsx(styles.footer, className), gap: 4, justify: "flex-end", children: children }));
24
+ }
25
+ function DrawerInternal({ isOpen, onClose, title, side = "right", children, footer, className, variant = "default", }) {
10
26
  const reactId = React.useId();
11
27
  const titleId = `drawer-title-${reactId}`;
12
28
  const [mounted, setMounted] = useState(false);
@@ -36,5 +52,11 @@ export function Drawer({ isOpen, onClose, title, side = "right", children, foote
36
52
  if (!mounted) {
37
53
  return null;
38
54
  }
39
- return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": "true", className: clsx(styles.overlay, isOpen && styles.isOpen), onClick: onClose }), _jsxs("div", { "aria-label": !title ? "Drawer" : undefined, "aria-labelledby": title ? titleId : undefined, "aria-modal": "true", className: clsx(styles.panel, styles[side], styles[variant], isOpen && styles.isOpen, className), role: "dialog", children: [_jsxs("div", { className: styles.header, children: [title && (_jsx("h2", { className: styles.title, id: titleId, children: title })), _jsx(Button, { "aria-label": "Close drawer", size: "sm", variant: "danger", onClick: onClose, children: _jsx(X, { size: 24 }) })] }), _jsx("div", { className: styles.content, children: children }), footer && _jsx("div", { className: styles.footer, children: footer })] })] }), document.body);
55
+ return createPortal(_jsxs(DrawerContext.Provider, { value: { onClose, titleId, variant }, children: [_jsx("div", { "aria-hidden": "true", className: clsx(styles.overlay, isOpen && styles.isOpen), onClick: onClose }), _jsx("div", { "aria-label": !title ? "Drawer" : undefined, "aria-labelledby": title ? titleId : undefined, "aria-modal": "true", className: clsx(styles.panel, styles[side], styles[variant], isOpen && styles.isOpen, className), role: "dialog", children: title ? (_jsxs(_Fragment, { children: [_jsx(DrawerHeader, { children: title }), _jsx(DrawerBody, { children: children }), footer && _jsx(DrawerFooter, { children: footer })] })) : (children) })] }), document.body);
40
56
  }
57
+ // Namespace export pattern (like Chart)
58
+ export const Drawer = Object.assign(DrawerInternal, {
59
+ Header: DrawerHeader,
60
+ Body: DrawerBody,
61
+ Footer: DrawerFooter,
62
+ });
@@ -12,14 +12,19 @@ interface ModalHeaderProps {
12
12
  className?: string;
13
13
  id?: string;
14
14
  }
15
- export declare function ModalHeader({ children, className, id }: ModalHeaderProps): import("react/jsx-runtime").JSX.Element;
16
- export declare function ModalBody({ children, className, }: {
15
+ declare function ModalHeader({ children, className, id }: ModalHeaderProps): import("react/jsx-runtime").JSX.Element;
16
+ declare function ModalBody({ children, className, }: {
17
17
  children: React.ReactNode;
18
18
  className?: string;
19
19
  }): import("react/jsx-runtime").JSX.Element;
20
- export declare function ModalFooter({ children, className, }: {
20
+ declare function ModalFooter({ children, className, }: {
21
21
  children: React.ReactNode;
22
22
  className?: string;
23
23
  }): import("react/jsx-runtime").JSX.Element;
24
- export declare function Modal({ isOpen, onClose, title, children, footer, className, style, variant, ...props }: ModalProps): React.ReactPortal | null;
24
+ declare function ModalInternal({ isOpen, onClose, title, children, footer, className, style, variant, ...props }: ModalProps): React.ReactPortal | null;
25
+ export declare const Modal: typeof ModalInternal & {
26
+ Header: typeof ModalHeader;
27
+ Body: typeof ModalBody;
28
+ Footer: typeof ModalFooter;
29
+ };
25
30
  export {};
@@ -23,17 +23,17 @@ const ModalContext = React.createContext({
23
23
  onClose: () => { },
24
24
  variant: "default",
25
25
  });
26
- export function ModalHeader({ children, className, id }) {
26
+ function ModalHeader({ children, className, id }) {
27
27
  const { onClose, titleId } = React.useContext(ModalContext);
28
28
  return (_jsxs(Flex, { align: "center", className: clsx(styles.header, className), justify: "space-between", children: [_jsx("div", { className: styles.headerContent, id: id || titleId, children: children }), _jsx(Button, { "aria-label": "Close modal", className: styles.closeButton, size: "sm", variant: "danger", onClick: onClose, children: _jsx(X, { size: 20, strokeWidth: 2.5 }) })] }));
29
29
  }
30
- export function ModalBody({ children, className, }) {
30
+ function ModalBody({ children, className, }) {
31
31
  return _jsx("div", { className: clsx(styles.body, className), children: children });
32
32
  }
33
- export function ModalFooter({ children, className, }) {
33
+ function ModalFooter({ children, className, }) {
34
34
  return (_jsx(Flex, { align: "center", className: clsx(styles.footer, className), gap: 4, justify: "flex-end", children: children }));
35
35
  }
36
- export function Modal(_a) {
36
+ function ModalInternal(_a) {
37
37
  var { isOpen, onClose, title, children, footer, className, style, variant = "default" } = _a, props = __rest(_a, ["isOpen", "onClose", "title", "children", "footer", "className", "style", "variant"]);
38
38
  const overlayRef = useRef(null);
39
39
  const reactId = useId();
@@ -71,3 +71,9 @@ export function Modal(_a) {
71
71
  return createPortal(_jsx(ModalContext.Provider, { value: { onClose, titleId, variant }, children: _jsx("div", { ref: overlayRef, className: clsx(styles.overlay, isOpen && styles.isOpen, className), style: style, onClick: handleOverlayClick, children: _jsx("div", { className: styles.contentContainer, children: _jsx("div", Object.assign({ "aria-label": props["aria-label"] ||
72
72
  (!title && !props["aria-labelledby"] ? "Modal Window" : undefined), "aria-labelledby": title ? titleId : props["aria-labelledby"], "aria-modal": "true", role: "dialog" }, props, { children: _jsx(Card, { className: clsx(styles.modalCard, styles[variant]), style: { padding: 0, overflow: "hidden" }, children: title ? (_jsxs(_Fragment, { children: [_jsx(ModalHeader, { children: title }), _jsxs(Stack, { className: styles.modalContent, gap: 0, children: [_jsx(ModalBody, { children: children }), footer && _jsx(ModalFooter, { children: footer })] })] })) : (children) }) })) }) }) }), document.body);
73
73
  }
74
+ // Namespace export pattern (like Chart)
75
+ export const Modal = Object.assign(ModalInternal, {
76
+ Header: ModalHeader,
77
+ Body: ModalBody,
78
+ Footer: ModalFooter,
79
+ });
@@ -1 +1 @@
1
- export * from "./Modal";
1
+ export { Modal } from "./Modal";
@@ -1 +1 @@
1
- export * from "./Modal.js";
1
+ export { Modal } from "./Modal.js";
@@ -9,16 +9,16 @@
9
9
  width: 100%;
10
10
  background: var(--card-bg);
11
11
  color: var(--foreground);
12
- padding: 0.75rem 1rem;
12
+ padding: var(--spacing-md) var(--spacing-lg);
13
13
  font-size: var(--text-base);
14
14
  cursor: pointer;
15
15
  display: flex;
16
16
  justify-content: space-between;
17
17
  align-items: center;
18
- font-weight: 700;
18
+ font-weight: var(--font-bold);
19
19
  text-transform: uppercase;
20
20
  letter-spacing: 0.05em;
21
- min-height: 42px;
21
+ min-height: var(--size-lg);
22
22
  border: var(--border-width) solid var(--card-border);
23
23
  border-radius: var(--radius);
24
24
  box-shadow: var(--shadow-hard);
@@ -39,9 +39,9 @@
39
39
  outline: none;
40
40
  }
41
41
  .trigger.sm {
42
- padding: 0.4rem 0.75rem;
43
- min-height: 32px;
44
- font-size: 0.75rem;
42
+ padding: var(--spacing-sm) var(--spacing-md);
43
+ min-height: var(--size-md);
44
+ font-size: var(--text-xs);
45
45
  text-transform: uppercase;
46
46
  letter-spacing: 0.05em;
47
47
  }
@@ -56,23 +56,23 @@
56
56
  overflow-y: auto;
57
57
  display: flex;
58
58
  flex-direction: column;
59
- gap: 0.25rem;
60
- padding: 0.25rem;
59
+ gap: var(--spacing-xs);
60
+ padding: var(--spacing-xs);
61
61
  margin: 0;
62
62
  list-style: none;
63
63
  }
64
64
 
65
65
  .optionItem {
66
66
  text-align: left;
67
- padding: 0.75rem 1rem;
67
+ padding: var(--spacing-md) var(--spacing-lg);
68
68
  background: transparent;
69
69
  border: none;
70
70
  border-radius: calc(var(--radius) - 2px);
71
71
  color: var(--foreground);
72
72
  cursor: pointer;
73
73
  font-size: var(--text-base);
74
- font-weight: 400;
75
- transition: all 0.1s ease;
74
+ font-weight: var(--font-regular);
75
+ transition: all var(--duration-fast) var(--ease-in-out);
76
76
  width: 100%;
77
77
  display: flex;
78
78
  align-items: center;
@@ -90,7 +90,7 @@
90
90
  .optionItem.selected {
91
91
  background: var(--primary);
92
92
  color: var(--primary-foreground);
93
- font-weight: 700;
93
+ font-weight: var(--font-bold);
94
94
  }
95
95
  .optionItem.selected:hover {
96
96
  background-color: var(--primary);
@@ -1,12 +1,31 @@
1
1
  import React from "react";
2
+ interface SheetHeaderProps {
3
+ children: React.ReactNode;
4
+ className?: string;
5
+ id?: string;
6
+ }
7
+ export declare function SheetHeader({ children, className, id }: SheetHeaderProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function SheetBody({ children, className, }: {
9
+ children: React.ReactNode;
10
+ className?: string;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ export declare function SheetFooter({ children, className, }: {
13
+ children: React.ReactNode;
14
+ className?: string;
15
+ }): import("react/jsx-runtime").JSX.Element;
2
16
  interface SheetProps {
3
17
  isOpen: boolean;
4
18
  onClose: () => void;
5
- title?: string;
19
+ title?: React.ReactNode;
6
20
  children: React.ReactNode;
7
21
  footer?: React.ReactNode;
8
22
  variant?: "default" | "solid";
9
23
  className?: string;
10
24
  }
11
- export declare function Sheet({ isOpen, onClose, title, children, footer, variant, className, }: SheetProps): React.ReactPortal | null;
25
+ declare function SheetInternal({ isOpen, onClose, title, children, footer, variant, className, }: SheetProps): React.ReactPortal | null;
26
+ export declare const Sheet: typeof SheetInternal & {
27
+ Header: typeof SheetHeader;
28
+ Body: typeof SheetBody;
29
+ Footer: typeof SheetFooter;
30
+ };
12
31
  export {};
@@ -7,7 +7,23 @@ import { createPortal } from "react-dom";
7
7
  import { Button } from "../Button/Button.js";
8
8
  import { Flex } from "../Layout/Layout.js";
9
9
  import styles from "./Sheet.module.css";
10
- export function Sheet({ isOpen, onClose, title, children, footer, variant = "default", className, }) {
10
+ // Context for composition API
11
+ const SheetContext = React.createContext({
12
+ onClose: () => { },
13
+ variant: "default",
14
+ handlePointerDown: () => { },
15
+ });
16
+ export function SheetHeader({ children, className, id }) {
17
+ const { onClose, titleId, handlePointerDown } = React.useContext(SheetContext);
18
+ return (_jsxs("div", { className: clsx(styles.header, className), onPointerDown: handlePointerDown, children: [_jsx("div", { className: styles.handleBar }), _jsxs(Flex, { align: "center", className: styles.headerBody, justify: "space-between", children: [_jsx("div", { className: styles.headerContent, id: id || titleId, children: children }), _jsx(Button, { "aria-label": "Close sheet", size: "sm", variant: "danger", onClick: onClose, children: _jsx(X, { size: 24 }) })] })] }));
19
+ }
20
+ export function SheetBody({ children, className, }) {
21
+ return _jsx("div", { className: clsx(styles.content, className), children: children });
22
+ }
23
+ export function SheetFooter({ children, className, }) {
24
+ return (_jsx(Flex, { align: "center", className: clsx(styles.footer, className), gap: 4, justify: "flex-end", children: children }));
25
+ }
26
+ function SheetInternal({ isOpen, onClose, title, children, footer, variant = "default", className, }) {
11
27
  const reactId = React.useId();
12
28
  const titleId = `sheet-title-${reactId}`;
13
29
  const [mounted, setMounted] = useState(false);
@@ -100,5 +116,11 @@ export function Sheet({ isOpen, onClose, title, children, footer, variant = "def
100
116
  if (!mounted) {
101
117
  return null;
102
118
  }
103
- return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": "true", className: clsx(styles.overlay, isOpen && styles.isOpen), onClick: onClose }), _jsxs("div", { ref: panelRef, "aria-label": !title ? "Sheet" : undefined, "aria-labelledby": title ? titleId : undefined, "aria-modal": "true", className: clsx(styles.panel, styles[variant], isOpen && styles.isOpen, className), role: "dialog", children: [_jsxs("div", { className: styles.header, onPointerDown: handlePointerDown, children: [_jsx("div", { className: styles.handleBar }), _jsxs(Flex, { align: "center", className: styles.headerBody, justify: "space-between", children: [title && (_jsx("h2", { className: styles.title, id: titleId, children: title })), _jsx(Button, { "aria-label": "Close sheet", size: "sm", variant: "danger", onClick: onClose, children: _jsx(X, { size: 24 }) })] })] }), _jsx("div", { className: styles.content, children: children }), footer && _jsx("div", { className: styles.footer, children: footer })] })] }), document.body);
119
+ return createPortal(_jsxs(SheetContext.Provider, { value: { onClose, titleId, variant, handlePointerDown }, children: [_jsx("div", { "aria-hidden": "true", className: clsx(styles.overlay, isOpen && styles.isOpen), onClick: onClose }), _jsx("div", { ref: panelRef, "aria-label": !title ? "Sheet" : undefined, "aria-labelledby": title ? titleId : undefined, "aria-modal": "true", className: clsx(styles.panel, styles[variant], isOpen && styles.isOpen, className), role: "dialog", children: title ? (_jsxs(_Fragment, { children: [_jsx(SheetHeader, { children: title }), _jsx(SheetBody, { children: children }), footer && _jsx(SheetFooter, { children: footer })] })) : (children) })] }), document.body);
104
120
  }
121
+ // Namespace export pattern (like Chart)
122
+ export const Sheet = Object.assign(SheetInternal, {
123
+ Header: SheetHeader,
124
+ Body: SheetBody,
125
+ Footer: SheetFooter,
126
+ });