doom-design-system 0.1.15 → 0.2.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 (43) hide show
  1. package/dist/components/Accordion/Accordion.d.ts +7 -6
  2. package/dist/components/Accordion/Accordion.js +36 -28
  3. package/dist/components/Accordion/Accordion.module.css +78 -22
  4. package/dist/components/Avatar/Avatar.d.ts +4 -4
  5. package/dist/components/Avatar/Avatar.js +6 -6
  6. package/dist/components/Badge/Badge.module.css +3 -3
  7. package/dist/components/Button/Button.module.css +1 -1
  8. package/dist/components/Checkbox/Checkbox.d.ts +2 -2
  9. package/dist/components/Checkbox/Checkbox.js +7 -7
  10. package/dist/components/Drawer/Drawer.js +5 -3
  11. package/dist/components/Drawer/Drawer.module.css +8 -6
  12. package/dist/components/Form/Form.module.css +1 -0
  13. package/dist/components/Input/Input.d.ts +3 -2
  14. package/dist/components/Input/Input.js +15 -3
  15. package/dist/components/Input/Input.module.css +33 -0
  16. package/dist/components/Modal/Modal.d.ts +5 -4
  17. package/dist/components/Modal/Modal.js +13 -6
  18. package/dist/components/Modal/Modal.module.css +64 -29
  19. package/dist/components/Popover/Popover.d.ts +3 -3
  20. package/dist/components/Popover/Popover.js +38 -36
  21. package/dist/components/ProgressBar/ProgressBar.d.ts +2 -1
  22. package/dist/components/ProgressBar/ProgressBar.js +10 -5
  23. package/dist/components/ProgressBar/ProgressBar.module.css +7 -0
  24. package/dist/components/Select/Select.js +6 -4
  25. package/dist/components/Select/Select.module.css +2 -2
  26. package/dist/components/Sheet/Sheet.d.ts +2 -2
  27. package/dist/components/Sheet/Sheet.js +18 -16
  28. package/dist/components/Sheet/Sheet.module.css +1 -2
  29. package/dist/components/Slider/Slider.d.ts +3 -3
  30. package/dist/components/Slider/Slider.js +11 -7
  31. package/dist/components/Slider/Slider.module.css +1 -1
  32. package/dist/components/SplitButton/SplitButton.d.ts +2 -2
  33. package/dist/components/SplitButton/SplitButton.js +8 -8
  34. package/dist/components/Tabs/Tabs.js +2 -2
  35. package/dist/components/Tabs/Tabs.module.css +9 -7
  36. package/dist/components/Textarea/Textarea.d.ts +8 -3
  37. package/dist/components/Textarea/Textarea.js +25 -6
  38. package/dist/components/Textarea/Textarea.module.css +71 -0
  39. package/dist/styles/themes/definitions.d.ts +312 -300
  40. package/dist/styles/themes/definitions.js +60 -60
  41. package/dist/tsconfig.build.tsbuildinfo +1 -0
  42. package/package.json +2 -2
  43. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,17 +1,18 @@
1
- import React from 'react';
1
+ import React from "react";
2
2
  interface AccordionItemProps {
3
3
  value: string;
4
4
  trigger: string;
5
5
  children: React.ReactNode;
6
- isOpen?: boolean;
7
- onToggle?: () => void;
6
+ className?: string;
8
7
  }
9
- export declare function AccordionItem({ value, trigger, children, isOpen, onToggle }: AccordionItemProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function AccordionItem({ value, trigger, children, className, }: AccordionItemProps): import("react/jsx-runtime").JSX.Element;
10
9
  interface AccordionProps {
11
- type?: 'single' | 'multiple';
10
+ type?: "single" | "multiple";
12
11
  children: React.ReactNode;
13
12
  defaultValue?: string | string[];
13
+ value?: string | string[];
14
+ onValueChange?: (value: string | string[]) => void;
14
15
  className?: string;
15
16
  }
16
- export declare function Accordion({ type, children, defaultValue, className }: AccordionProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function Accordion({ type, children, defaultValue, value: controlledValue, onValueChange, className, }: AccordionProps): import("react/jsx-runtime").JSX.Element;
17
18
  export {};
@@ -1,36 +1,44 @@
1
- 'use client';
1
+ "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import clsx from 'clsx';
4
- import { ChevronDown } from 'lucide-react';
5
- import styles from './Accordion.module.css';
6
- import React, { useState } from 'react';
7
- export function AccordionItem({ value, trigger, children, isOpen, onToggle }) {
8
- return (_jsxs("div", { className: styles.item, children: [_jsx("h3", { className: styles.header, children: _jsxs("button", { className: styles.trigger, type: "button", onClick: onToggle, "aria-expanded": isOpen, children: [trigger, _jsx(ChevronDown, { size: 20, strokeWidth: 2.5, className: styles.icon })] }) }), _jsx("div", { className: styles.contentWrapper, "aria-hidden": !isOpen, role: "region", children: _jsx("div", { className: styles.contentBody, children: children }) })] }));
3
+ import clsx from "clsx";
4
+ import { ChevronDown } from "lucide-react";
5
+ import { Stack } from "../Layout/Layout.js";
6
+ import { Button } from "../Button/Button.js";
7
+ import { Text } from "../Text/Text.js";
8
+ import styles from "./Accordion.module.css";
9
+ import React, { useState } from "react";
10
+ const AccordionContext = React.createContext(null);
11
+ export function AccordionItem({ value, trigger, children, className, }) {
12
+ const context = React.useContext(AccordionContext);
13
+ if (!context)
14
+ throw new Error("AccordionItem must be used within Accordion");
15
+ const reactId = React.useId();
16
+ const triggerId = `accordion-trigger-${reactId}`;
17
+ const contentId = `accordion-content-${reactId}`;
18
+ const isOpen = Array.isArray(context.value)
19
+ ? context.value.includes(value)
20
+ : context.value === value;
21
+ return (_jsxs("div", { className: clsx(styles.item, isOpen && styles.isOpen, className), children: [_jsx(Text, { variant: "h3", className: styles.header, children: _jsxs(Button, { id: triggerId, variant: "ghost", className: styles.trigger, onClick: () => context.onToggle(value), "aria-expanded": isOpen, "aria-controls": contentId, children: [trigger, _jsx(ChevronDown, { size: 20, strokeWidth: 2.5, className: styles.icon })] }) }), _jsx("div", { id: contentId, className: styles.contentWrapper, "aria-hidden": !isOpen, role: "region", "aria-labelledby": triggerId, children: _jsx("div", { className: styles.contentBody, children: children }) })] }));
9
22
  }
10
- export function Accordion({ type = 'single', children, defaultValue, className }) {
11
- const [value, setValue] = useState(defaultValue || (type === 'multiple' ? [] : ''));
23
+ export function Accordion({ type = "single", children, defaultValue, value: controlledValue, onValueChange, className, }) {
24
+ const [internalValue, setInternalValue] = useState(defaultValue || (type === "multiple" ? [] : ""));
25
+ const isControlled = controlledValue !== undefined;
26
+ const currentValue = isControlled ? controlledValue : internalValue;
12
27
  const handleToggle = (itemValue) => {
13
- if (type === 'single') {
14
- setValue(prev => prev === itemValue ? '' : itemValue);
28
+ let newValue;
29
+ if (type === "single") {
30
+ newValue = currentValue === itemValue ? "" : itemValue;
15
31
  }
16
32
  else {
17
- setValue(prev => {
18
- const arr = Array.isArray(prev) ? prev : [];
19
- if (arr.includes(itemValue)) {
20
- return arr.filter(v => v !== itemValue);
21
- }
22
- return [...arr, itemValue];
23
- });
33
+ const arr = Array.isArray(currentValue) ? currentValue : [];
34
+ newValue = arr.includes(itemValue)
35
+ ? arr.filter((v) => v !== itemValue)
36
+ : [...arr, itemValue];
24
37
  }
38
+ if (!isControlled) {
39
+ setInternalValue(newValue);
40
+ }
41
+ onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(newValue);
25
42
  };
26
- return (_jsx("div", { className: clsx(styles.root, className), children: React.Children.map(children, (child) => {
27
- if (!React.isValidElement(child))
28
- return null;
29
- const itemValue = child.props.value;
30
- const isOpen = Array.isArray(value) ? value.includes(itemValue) : value === itemValue;
31
- return React.cloneElement(child, {
32
- isOpen,
33
- onToggle: () => handleToggle(itemValue),
34
- });
35
- }) }));
43
+ return (_jsx(AccordionContext.Provider, { value: { value: currentValue, onToggle: handleToggle, type }, children: _jsx(Stack, { gap: 0, className: clsx(styles.root, className), children: children }) }));
36
44
  }
@@ -1,52 +1,108 @@
1
1
  .root {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- border: var(--border-width) solid var(--card-border);
5
- border-radius: var(--radius);
6
- background-color: var(--card-bg);
7
- overflow: hidden;
4
+ padding: var(--spacing-lg);
8
5
  }
9
6
 
10
7
  .item {
11
- border-bottom: var(--border-width) solid var(--card-border);
8
+ background-color: var(--card-bg);
9
+ border: var(--border-width) solid var(--card-border);
10
+ box-shadow: var(--shadow-hard);
11
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
12
+ position: relative;
13
+ overflow: hidden;
14
+ }
15
+ .item:not(:first-child) {
16
+ margin-top: calc(var(--border-width) * -1);
17
+ }
18
+ .item:first-child {
19
+ border-top-left-radius: var(--radius);
20
+ border-top-right-radius: var(--radius);
12
21
  }
13
22
  .item:last-child {
14
- border-bottom: none;
23
+ border-bottom-left-radius: var(--radius);
24
+ border-bottom-right-radius: var(--radius);
25
+ }
26
+ .item.isOpen + .item {
27
+ border-top-left-radius: var(--radius);
28
+ border-top-right-radius: var(--radius);
29
+ margin-top: var(--spacing-sm);
30
+ }
31
+ .item:has(+ .item.isOpen) {
32
+ border-bottom-left-radius: var(--radius);
33
+ border-bottom-right-radius: var(--radius);
34
+ margin-bottom: var(--spacing-sm);
35
+ }
36
+ .item.isOpen {
37
+ margin: var(--spacing-sm) 0;
38
+ transform: translate(-2px, -2px);
39
+ border-radius: var(--radius);
40
+ z-index: 10;
41
+ box-shadow: var(--shadow-hard);
42
+ }
43
+ .item.isOpen + .item.isOpen {
44
+ margin-top: var(--spacing-sm);
45
+ }
46
+ .item.isOpen .trigger.trigger {
47
+ background-color: var(--primary);
48
+ color: var(--primary-foreground);
49
+ }
50
+ .item.isOpen .trigger.trigger:hover {
51
+ background-color: var(--primary);
52
+ filter: brightness(1.1);
53
+ }
54
+ .item.isOpen .contentBody {
55
+ border-top: var(--border-width) solid var(--card-border);
56
+ padding-top: var(--spacing-md);
15
57
  }
16
58
 
17
59
  .header {
18
- display: flex;
19
60
  margin: 0;
61
+ display: flex;
20
62
  }
21
63
 
22
- .trigger {
23
- all: unset;
24
- flex: 1;
25
- display: flex;
26
- align-items: center;
64
+ /* Specificity bridge to override Button defaults without !important */
65
+ .trigger.trigger {
66
+ width: 100%;
67
+ height: auto;
68
+ border: none;
69
+ border-radius: 0;
70
+ box-shadow: none;
71
+ transform: none;
27
72
  justify-content: space-between;
28
73
  padding: var(--spacing-md);
74
+ background-color: var(--card-bg);
75
+ color: var(--foreground);
76
+ text-transform: none;
77
+ letter-spacing: normal;
29
78
  font-family: var(--font-heading);
30
79
  font-weight: 700;
31
80
  font-size: var(--text-base);
32
- background-color: var(--card-bg);
33
- color: var(--foreground);
34
- cursor: pointer;
35
- transition: background-color 0.2s ease;
36
81
  }
37
- .trigger:hover {
82
+ .trigger.trigger::after {
83
+ display: none;
84
+ }
85
+ .trigger.trigger:hover {
38
86
  background-color: color-mix(in srgb, var(--primary) 25%, transparent);
87
+ transform: none;
88
+ box-shadow: none;
39
89
  }
40
- .trigger:focus-visible {
41
- outline: 2px solid var(--primary);
90
+ .trigger.trigger:focus-visible {
91
+ outline: none;
92
+ background-color: color-mix(in srgb, var(--primary) 15%, transparent);
93
+ box-shadow: inset 0 0 0 2px var(--primary);
42
94
  position: relative;
43
95
  z-index: 1;
44
96
  }
45
- .trigger .icon {
46
- transition: transform 0.2s ease;
97
+ .trigger.trigger[aria-expanded=true] {
98
+ background-color: var(--primary);
99
+ color: var(--primary-foreground);
100
+ }
101
+ .trigger.trigger .icon {
102
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
47
103
  transform: rotate(0deg);
48
104
  }
49
- .trigger[aria-expanded=true] .icon {
105
+ .trigger.trigger[aria-expanded=true] .icon {
50
106
  transform: rotate(180deg);
51
107
  }
52
108
 
@@ -1,6 +1,6 @@
1
- import React from 'react';
2
- export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
3
- export type AvatarShape = 'circle' | 'square';
1
+ import React from "react";
2
+ export type AvatarSize = "sm" | "md" | "lg" | "xl";
3
+ export type AvatarShape = "circle" | "square";
4
4
  interface AvatarProps {
5
5
  src?: string;
6
6
  alt?: string;
@@ -9,5 +9,5 @@ interface AvatarProps {
9
9
  shape?: AvatarShape;
10
10
  className?: string;
11
11
  }
12
- export declare function Avatar({ src, alt, fallback, size, shape, className }: AvatarProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function Avatar({ src, alt, fallback, size, shape, className, }: AvatarProps): import("react/jsx-runtime").JSX.Element;
13
13
  export {};
@@ -1,9 +1,9 @@
1
- 'use client';
1
+ "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useState } from 'react';
4
- import clsx from 'clsx';
5
- import styles from './Avatar.module.css';
6
- export function Avatar({ src, alt = 'Avatar', fallback, size = 'md', shape = 'circle', className }) {
3
+ import { useState } from "react";
4
+ import clsx from "clsx";
5
+ import styles from "./Avatar.module.css";
6
+ export function Avatar({ src, alt = "Avatar", fallback, size = "md", shape = "square", className, }) {
7
7
  const [hasError, setHasError] = useState(false);
8
- return (_jsx("div", { className: clsx(styles.avatar, styles[size], styles[shape], className), children: src && !hasError ? (_jsx("img", { src: src, alt: alt, className: styles.image, onError: () => setHasError(true) })) : (_jsx("span", { className: clsx(styles.fallback, styles[size]), children: typeof fallback === 'string' ? fallback.slice(0, 2) : fallback })) }));
8
+ return (_jsx("div", { className: clsx(styles.avatar, styles[size], styles[shape], className), children: src && !hasError ? (_jsx("img", { src: src, alt: alt, className: styles.image, onError: () => setHasError(true) })) : (_jsx("span", { className: clsx(styles.fallback, styles[size]), children: typeof fallback === "string" ? fallback.slice(0, 2) : fallback })) }));
9
9
  }
@@ -15,15 +15,15 @@
15
15
  }
16
16
  .badge.success {
17
17
  background-color: var(--success);
18
- color: var(--card-bg);
18
+ color: var(--success-foreground);
19
19
  }
20
20
  .badge.warning {
21
21
  background-color: var(--warning);
22
- color: var(--card-bg);
22
+ color: var(--warning-foreground);
23
23
  }
24
24
  .badge.error {
25
25
  background-color: var(--error);
26
- color: var(--card-bg);
26
+ color: var(--error-foreground);
27
27
  }
28
28
  .badge.secondary {
29
29
  background-color: var(--secondary);
@@ -64,7 +64,7 @@
64
64
  --btn-focus-border: var(--card-border);
65
65
  --btn-focus-shadow: #000000;
66
66
  background-color: var(--success);
67
- color: var(--card-bg);
67
+ color: var(--success-foreground);
68
68
  }
69
69
  .success:hover {
70
70
  filter: brightness(1.1);
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
- export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
1
+ import React from "react";
2
+ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
3
3
  label?: string;
4
4
  error?: boolean;
5
5
  }
@@ -1,4 +1,4 @@
1
- 'use client';
1
+ "use client";
2
2
  var __rest = (this && this.__rest) || function (s, e) {
3
3
  var t = {};
4
4
  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -11,14 +11,14 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
- import { forwardRef, useId } from 'react';
15
- import clsx from 'clsx';
16
- import { Check } from 'lucide-react';
17
- import { Label } from '../Label/index.js';
18
- import styles from './Checkbox.module.css';
14
+ import { forwardRef, useId } from "react";
15
+ import clsx from "clsx";
16
+ import { Check } from "lucide-react";
17
+ import { Label } from "../Label/index.js";
18
+ import styles from "./Checkbox.module.css";
19
19
  export const Checkbox = forwardRef((_a, ref) => {
20
20
  var { className, label, error, disabled, checked, defaultChecked, onChange, id: propsId } = _a, props = __rest(_a, ["className", "label", "error", "disabled", "checked", "defaultChecked", "onChange", "id"]);
21
21
  const generatedId = useId();
22
22
  const id = propsId || generatedId;
23
- return (_jsxs("div", { className: clsx(styles.checkboxWrapper, disabled && styles.disabled, className), children: [_jsx("input", Object.assign({ id: id, type: "checkbox", className: styles.checkboxInput, ref: ref, disabled: disabled, checked: checked, defaultChecked: defaultChecked, onChange: onChange }, props)), _jsx("label", { htmlFor: id, className: clsx(styles.checkboxDisplay), "aria-hidden": "true", children: _jsx(Check, { className: styles.icon }) }), label && (_jsx(Label, { htmlFor: id, className: styles.labelOverride, children: label }))] }));
23
+ return (_jsxs(Label, { htmlFor: id, className: clsx(styles.checkboxWrapper, disabled && styles.disabled, className), children: [_jsx("input", Object.assign({ id: id, type: "checkbox", className: styles.checkboxInput, ref: ref, disabled: disabled, checked: checked, defaultChecked: defaultChecked, onChange: onChange }, props)), _jsx("span", { className: clsx(styles.checkboxDisplay), "aria-hidden": "true", children: _jsx(Check, { className: styles.icon }) }), label && _jsx("span", { className: styles.labelOverride, children: label })] }));
24
24
  });
@@ -5,8 +5,10 @@ import { createPortal } from "react-dom";
5
5
  import { X } from "lucide-react";
6
6
  import { Button } from "../Button/Button.js";
7
7
  import styles from "./Drawer.module.css";
8
- import { useEffect, useState } from "react";
8
+ import React, { useEffect, useState } from "react";
9
9
  export function Drawer({ isOpen, onClose, title, side = "right", children, footer, className, }) {
10
+ const reactId = React.useId();
11
+ const titleId = `drawer-title-${reactId}`;
10
12
  const [mounted, setMounted] = useState(false);
11
13
  useEffect(() => {
12
14
  setMounted(true);
@@ -29,7 +31,7 @@ export function Drawer({ isOpen, onClose, title, side = "right", children, foote
29
31
  window.addEventListener("keydown", handleEsc);
30
32
  return () => window.removeEventListener("keydown", handleEsc);
31
33
  }, [isOpen, onClose]);
32
- if (!mounted || !isOpen)
34
+ if (!mounted)
33
35
  return null;
34
- return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { className: clsx(styles.overlay, isOpen && styles.open), onClick: onClose, "aria-hidden": "true" }), _jsxs("div", { className: clsx(styles.panel, styles[side], isOpen && styles.open, className), role: "dialog", "aria-modal": "true", children: [_jsxs("div", { className: styles.header, children: [_jsx("h2", { className: styles.title, children: title }), _jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, "aria-label": "Close drawer", children: _jsx(X, { size: 24 }) })] }), _jsx("div", { className: styles.content, children: children }), footer && _jsx("div", { className: styles.footer, children: footer })] })] }), document.body);
36
+ return createPortal(_jsxs(_Fragment, { children: [_jsx("div", { className: clsx(styles.overlay, isOpen && styles.isOpen), onClick: onClose, "aria-hidden": "true" }), _jsxs("div", { className: clsx(styles.panel, styles[side], isOpen && styles.isOpen, className), role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : undefined, "aria-label": !title ? "Drawer" : undefined, children: [_jsxs("div", { className: styles.header, children: [title && (_jsx("h2", { id: titleId, className: styles.title, children: title })), _jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, "aria-label": "Close drawer", children: _jsx(X, { size: 24 }) })] }), _jsx("div", { className: styles.content, children: children }), footer && _jsx("div", { className: styles.footer, children: footer })] })] }), document.body);
35
37
  }
@@ -4,12 +4,14 @@
4
4
  background-color: rgba(0, 0, 0, var(--overlay-opacity));
5
5
  backdrop-filter: blur(4px);
6
6
  opacity: 0;
7
- transition: opacity 0.2s ease-in-out;
7
+ visibility: hidden;
8
+ transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
8
9
  pointer-events: none;
9
10
  z-index: var(--z-dropdown);
10
11
  }
11
- .overlay.open {
12
+ .overlay.isOpen {
12
13
  opacity: 1;
14
+ visibility: visible;
13
15
  pointer-events: auto;
14
16
  }
15
17
 
@@ -19,7 +21,7 @@
19
21
  z-index: var(--z-modal);
20
22
  display: flex;
21
23
  flex-direction: column;
22
- transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
24
+ transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.3s cubic-bezier(0.16, 1, 0.3, 1);
23
25
  top: 0;
24
26
  bottom: 0;
25
27
  width: 100%;
@@ -31,16 +33,16 @@
31
33
  box-shadow: 8px 0px 0px 0px var(--card-border);
32
34
  border-top-right-radius: var(--radius);
33
35
  border-bottom-right-radius: var(--radius);
34
- transform: translateX(-100%);
36
+ transform: translateX(calc(-100% - 20px));
35
37
  }
36
38
  .panel.right {
37
39
  right: 0;
38
40
  box-shadow: -8px 0px 0px 0px var(--card-border);
39
41
  border-top-left-radius: var(--radius);
40
42
  border-bottom-left-radius: var(--radius);
41
- transform: translateX(100%);
43
+ transform: translateX(calc(100% + 20px));
42
44
  }
43
- .panel.open {
45
+ .panel.isOpen {
44
46
  visibility: visible;
45
47
  transform: translateX(0);
46
48
  }
@@ -16,6 +16,7 @@
16
16
  .message {
17
17
  font-size: 0.75rem;
18
18
  font-weight: 500;
19
+ margin-top: -0.25rem;
19
20
  }
20
21
 
21
22
  .error {
@@ -1,12 +1,13 @@
1
1
  import React from "react";
2
- interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
2
+ interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "title"> {
3
3
  label?: string;
4
4
  error?: string;
5
5
  helperText?: string;
6
6
  startAdornment?: React.ReactNode;
7
7
  endAdornment?: React.ReactNode;
8
+ showCount?: boolean;
8
9
  format?: (value: string | number | readonly string[] | undefined) => string;
9
10
  validate?: (value: string | number | readonly string[] | undefined) => string | undefined;
10
11
  }
11
- export declare function Input({ label, error: errorProp, helperText, startAdornment, endAdornment, style, className, format, validate, onBlur, onFocus, value, id, required, ...props }: InputProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function Input({ label, error: errorProp, helperText, startAdornment, endAdornment, showCount, style, className, format, validate, onBlur, onFocus, onChange, value, defaultValue, id, required, maxLength, ...props }: InputProps): import("react/jsx-runtime").JSX.Element;
12
13
  export {};
@@ -11,14 +11,15 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
- import { useState, useId } from "react";
14
+ import React, { useState, useId } from "react";
15
15
  import clsx from "clsx";
16
16
  import { Label } from "../Label/Label.js";
17
17
  import styles from "./Input.module.css";
18
18
  export function Input(_a) {
19
- var { label, error: errorProp, helperText, startAdornment, endAdornment, style, className, format, validate, onBlur, onFocus, value, id, required } = _a, props = __rest(_a, ["label", "error", "helperText", "startAdornment", "endAdornment", "style", "className", "format", "validate", "onBlur", "onFocus", "value", "id", "required"]);
19
+ var { label, error: errorProp, helperText, startAdornment, endAdornment, showCount, style, className, format, validate, onBlur, onFocus, onChange, value, defaultValue, id, required, maxLength } = _a, props = __rest(_a, ["label", "error", "helperText", "startAdornment", "endAdornment", "showCount", "style", "className", "format", "validate", "onBlur", "onFocus", "onChange", "value", "defaultValue", "id", "required", "maxLength"]);
20
20
  const [isFocused, setIsFocused] = useState(false);
21
21
  const [internalError, setInternalError] = useState(undefined);
22
+ const [charCount, setCharCount] = useState(((value === null || value === void 0 ? void 0 : value.toString()) || (defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.toString()) || "").length);
22
23
  const reactId = useId();
23
24
  const inputId = id || `input-${reactId}`;
24
25
  const helperId = `${inputId}-helper`;
@@ -37,7 +38,18 @@ export function Input(_a) {
37
38
  if (onFocus)
38
39
  onFocus(e);
39
40
  };
41
+ const handleChange = (e) => {
42
+ setCharCount(e.target.value.length);
43
+ if (onChange)
44
+ onChange(e);
45
+ };
46
+ // Sync charCount if value is updated externally (controlled)
47
+ React.useEffect(() => {
48
+ if (value !== undefined) {
49
+ setCharCount(value.toString().length);
50
+ }
51
+ }, [value]);
40
52
  const displayValue = !isFocused && format && value !== undefined ? format(value) : value;
41
53
  const describedBy = clsx(helperText && helperId, error && errorId) || undefined;
42
- return (_jsxs("div", { className: clsx(styles.container, className), style: style, children: [label && (_jsx(Label, { htmlFor: inputId, required: required, children: label })), _jsxs("div", { className: styles.wrapper, children: [startAdornment && (_jsx("span", { className: clsx(styles.adornment, styles.start), children: startAdornment })), _jsx("input", Object.assign({ className: clsx(styles.input, startAdornment && styles.hasStartAdornment, endAdornment && styles.hasEndAdornment, error && styles.error), id: inputId, required: required, value: displayValue, onBlur: handleBlur, onFocus: handleFocus, "aria-invalid": !!error, "aria-describedby": describedBy }, props)), endAdornment && (_jsx("span", { className: clsx(styles.adornment, styles.end), children: endAdornment }))] }), helperText && !error && (_jsx("span", { id: helperId, className: styles.helperText, children: helperText })), error && (_jsx("span", { id: errorId, className: clsx(styles.helperText, styles.error), role: "alert", children: error }))] }));
54
+ return (_jsxs("div", { className: clsx(styles.container, className), style: style, children: [label && (_jsx(Label, { htmlFor: inputId, required: required, children: label })), _jsxs("div", { className: styles.wrapper, children: [startAdornment && (_jsx("span", { className: clsx(styles.adornment, styles.start), children: startAdornment })), _jsx("input", Object.assign({ className: clsx(styles.input, startAdornment && styles.hasStartAdornment, endAdornment && styles.hasEndAdornment, error && styles.error), id: inputId, required: required, value: displayValue, defaultValue: defaultValue, onBlur: handleBlur, onFocus: handleFocus, onChange: handleChange, maxLength: maxLength, "aria-invalid": !!error, "aria-describedby": describedBy }, props)), endAdornment && (_jsx("span", { className: clsx(styles.adornment, styles.end), children: endAdornment }))] }), (error || helperText || (showCount !== null && showCount !== void 0 ? showCount : maxLength !== undefined)) && (_jsxs("div", { className: styles.bottomRow, children: [_jsx("div", { className: styles.messageArea, children: error ? (_jsx("span", { id: errorId, className: clsx(styles.helperText, styles.error), role: "alert", children: error })) : (helperText && (_jsx("span", { id: helperId, className: styles.helperText, children: helperText }))) }), (showCount !== null && showCount !== void 0 ? showCount : maxLength !== undefined) && (_jsxs("span", { className: styles.counter, children: [charCount, maxLength !== undefined ? ` / ${maxLength}` : ""] }))] }))] }));
43
55
  }
@@ -41,6 +41,19 @@
41
41
  border-color: var(--error);
42
42
  box-shadow: 7px 7px 0px 0px var(--shadow-error);
43
43
  }
44
+ .input:disabled {
45
+ cursor: not-allowed;
46
+ opacity: 0.7;
47
+ background-color: var(--muted);
48
+ background-image: linear-gradient(45deg, transparent 25%, rgba(0, 0, 0, 0.05) 25%, rgba(0, 0, 0, 0.05) 50%, transparent 50%, transparent 75%, rgba(0, 0, 0, 0.05) 75%, rgba(0, 0, 0, 0.05));
49
+ background-size: 20px 20px;
50
+ box-shadow: none;
51
+ transform: none;
52
+ color: var(--muted-foreground);
53
+ }
54
+ .input:disabled::placeholder {
55
+ color: var(--muted-foreground);
56
+ }
44
57
  .input.hasStartAdornment {
45
58
  padding-left: 2rem;
46
59
  }
@@ -62,10 +75,23 @@
62
75
  right: 0.75rem;
63
76
  }
64
77
 
78
+ .bottomRow {
79
+ display: flex;
80
+ justify-content: space-between;
81
+ align-items: flex-start;
82
+ gap: 1rem;
83
+ margin-top: -0.25rem;
84
+ }
85
+
86
+ .messageArea {
87
+ flex: 1;
88
+ }
89
+
65
90
  .helperText {
66
91
  font-size: var(--text-xs);
67
92
  font-weight: var(--font-medium);
68
93
  color: var(--muted-foreground);
94
+ display: block;
69
95
  }
70
96
  .helperText.error {
71
97
  color: var(--error);
@@ -84,3 +110,10 @@
84
110
  transform: translateX(4px);
85
111
  }
86
112
  }
113
+
114
+ .counter {
115
+ font-size: var(--text-xs);
116
+ font-weight: var(--font-medium);
117
+ color: var(--muted-foreground);
118
+ white-space: nowrap;
119
+ }
@@ -1,16 +1,17 @@
1
1
  import React from "react";
2
- interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
2
+ interface ModalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
3
3
  isOpen: boolean;
4
4
  onClose: () => void;
5
- title?: string;
5
+ title?: React.ReactNode;
6
6
  children: React.ReactNode;
7
7
  footer?: React.ReactNode;
8
8
  }
9
- export declare function ModalHeader({ children, className, id, }: {
9
+ interface ModalHeaderProps {
10
10
  children: React.ReactNode;
11
11
  className?: string;
12
12
  id?: string;
13
- }): import("react/jsx-runtime").JSX.Element;
13
+ }
14
+ export declare function ModalHeader({ children, className, id }: ModalHeaderProps): import("react/jsx-runtime").JSX.Element;
14
15
  export declare function ModalBody({ children, className, }: {
15
16
  children: React.ReactNode;
16
17
  className?: string;
@@ -17,26 +17,30 @@ import { X } from "lucide-react";
17
17
  import clsx from "clsx";
18
18
  import { Card } from "../Card/Card.js";
19
19
  import { Button } from "../Button/Button.js";
20
- import { Flex } from "../Layout/Layout.js";
20
+ import { Stack, Flex } from "../Layout/Layout.js";
21
21
  import styles from "./Modal.module.css";
22
22
  const ModalContext = React.createContext({
23
23
  onClose: () => { },
24
24
  });
25
- export function ModalHeader({ children, className, id, }) {
25
+ export function ModalHeader({ children, className, id }) {
26
26
  const { onClose, titleId } = React.useContext(ModalContext);
27
- return (_jsxs(Flex, { align: "center", justify: "space-between", className: clsx(styles.header, className), children: [_jsx("h2", { id: id || titleId, children: children }), _jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, "aria-label": "Close modal", children: _jsx(X, { size: 20, strokeWidth: 2.5 }) })] }));
27
+ return (_jsxs(Flex, { align: "center", justify: "space-between", className: clsx(styles.header, className), children: [_jsx("div", { id: id || titleId, className: styles.headerContent, children: children }), _jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, "aria-label": "Close modal", className: styles.closeButton, children: _jsx(X, { size: 20, strokeWidth: 2.5 }) })] }));
28
28
  }
29
29
  export function ModalBody({ children, className, }) {
30
30
  return _jsx("div", { className: clsx(styles.body, className), children: children });
31
31
  }
32
32
  export function ModalFooter({ children, className, }) {
33
- return _jsx("div", { className: clsx(styles.footer, className), children: children });
33
+ return (_jsx(Flex, { justify: "flex-end", align: "center", gap: "var(--spacing-md)", className: clsx(styles.footer, className), children: children }));
34
34
  }
35
35
  export function Modal(_a) {
36
36
  var { isOpen, onClose, title, children, footer, className, style } = _a, props = __rest(_a, ["isOpen", "onClose", "title", "children", "footer", "className", "style"]);
37
37
  const overlayRef = useRef(null);
38
38
  const reactId = useId();
39
39
  const titleId = `modal-title-${reactId}`;
40
+ const [mounted, setMounted] = React.useState(false);
41
+ useEffect(() => {
42
+ setMounted(true);
43
+ }, []);
40
44
  useEffect(() => {
41
45
  const handleEscape = (e) => {
42
46
  if (e.key === "Escape")
@@ -46,17 +50,20 @@ export function Modal(_a) {
46
50
  document.addEventListener("keydown", handleEscape);
47
51
  document.body.style.overflow = "hidden";
48
52
  }
53
+ else {
54
+ document.body.style.overflow = "unset";
55
+ }
49
56
  return () => {
50
57
  document.removeEventListener("keydown", handleEscape);
51
58
  document.body.style.overflow = "unset";
52
59
  };
53
60
  }, [isOpen, onClose]);
54
- if (!isOpen)
61
+ if (!mounted)
55
62
  return null;
56
63
  const handleOverlayClick = (e) => {
57
64
  if (e.target === overlayRef.current) {
58
65
  onClose();
59
66
  }
60
67
  };
61
- return createPortal(_jsx(ModalContext.Provider, { value: { onClose, titleId }, children: _jsx("div", { className: clsx(styles.overlay, className), ref: overlayRef, onClick: handleOverlayClick, style: style, children: _jsx("div", { className: styles.contentContainer, children: _jsx("div", Object.assign({ role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : undefined }, props, { children: _jsx(Card, { style: { padding: 0, overflow: "hidden" }, children: title ? (_jsxs(_Fragment, { children: [_jsx(ModalHeader, { children: title }), _jsx(ModalBody, { children: children }), footer && _jsx(ModalFooter, { children: footer })] })) : (children) }) })) }) }) }), document.body);
68
+ return createPortal(_jsx(ModalContext.Provider, { value: { onClose, titleId }, children: _jsx("div", { className: clsx(styles.overlay, isOpen && styles.isOpen, className), ref: overlayRef, onClick: handleOverlayClick, style: style, children: _jsx("div", { className: styles.contentContainer, children: _jsx("div", Object.assign({ role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : undefined }, props, { children: _jsx(Card, { className: styles.modalCard, style: { padding: 0, overflow: "hidden" }, children: title ? (_jsxs(_Fragment, { children: [_jsx(ModalHeader, { children: title }), _jsxs(Stack, { gap: 0, className: styles.modalContent, children: [_jsx(ModalBody, { children: children }), footer && _jsx(ModalFooter, { children: footer })] })] })) : (children) }) })) }) }) }), document.body);
62
69
  }