cronixui 1.0.5 → 1.1.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 (130) hide show
  1. package/README.md +35 -5
  2. package/package.json +21 -3
  3. package/packages/go/cronixui/cronixui.go +784 -237
  4. package/packages/go/cronixui/go.mod +34 -9
  5. package/packages/go/cronixui/go.sum +666 -0
  6. package/packages/python/cronixui/__init__.py +131 -1
  7. package/packages/python/cronixui/alert.py +61 -0
  8. package/packages/python/cronixui/avatar.py +50 -0
  9. package/packages/python/cronixui/badge.py +46 -0
  10. package/packages/python/cronixui/button.py +64 -0
  11. package/packages/python/cronixui/card.py +62 -0
  12. package/packages/python/cronixui/form.py +255 -0
  13. package/packages/python/cronixui/layout.py +143 -0
  14. package/packages/python/cronixui/list.py +51 -0
  15. package/packages/python/cronixui/loading.py +36 -0
  16. package/packages/python/cronixui/progress.py +90 -0
  17. package/packages/python/cronixui/table.py +48 -0
  18. package/packages/python/cronixui/tokens.py +200 -0
  19. package/packages/python/cronixui/tooltip.py +28 -0
  20. package/packages/react/src/components/Accordion.tsx +82 -0
  21. package/packages/react/src/components/Alert.tsx +80 -0
  22. package/packages/react/src/components/Avatar.tsx +54 -0
  23. package/packages/react/src/components/Badge.tsx +32 -0
  24. package/packages/react/src/components/Breadcrumb.tsx +50 -0
  25. package/packages/react/src/components/Button.tsx +47 -0
  26. package/packages/react/src/components/Card.tsx +69 -0
  27. package/packages/react/src/components/Checkbox.tsx +30 -0
  28. package/packages/react/src/components/CommandPalette.tsx +131 -0
  29. package/packages/react/src/components/Container.tsx +26 -0
  30. package/packages/react/src/components/Dropdown.tsx +88 -0
  31. package/packages/react/src/components/FileInput.tsx +86 -0
  32. package/packages/react/src/components/Footer.tsx +36 -0
  33. package/packages/react/src/components/FormGroup.tsx +36 -0
  34. package/packages/react/src/components/Header.tsx +29 -0
  35. package/packages/react/src/components/Input.tsx +54 -0
  36. package/packages/react/src/components/List.tsx +55 -0
  37. package/packages/react/src/components/Modal.tsx +89 -0
  38. package/packages/react/src/components/Nav.tsx +63 -0
  39. package/packages/react/src/components/Pagination.tsx +107 -0
  40. package/packages/react/src/components/Progress.tsx +49 -0
  41. package/packages/react/src/components/Radio.tsx +64 -0
  42. package/packages/react/src/components/Search.tsx +95 -0
  43. package/packages/react/src/components/Select.tsx +41 -0
  44. package/packages/react/src/components/Sidebar.tsx +64 -0
  45. package/packages/react/src/components/Skeleton.tsx +39 -0
  46. package/packages/react/src/components/Slider.tsx +32 -0
  47. package/packages/react/src/components/Spinner.tsx +24 -0
  48. package/packages/react/src/components/Stack.tsx +69 -0
  49. package/packages/react/src/components/Stat.tsx +35 -0
  50. package/packages/react/src/components/Table.tsx +90 -0
  51. package/packages/react/src/components/Tabs.tsx +85 -0
  52. package/packages/react/src/components/Tag.tsx +30 -0
  53. package/packages/react/src/components/Textarea.tsx +21 -0
  54. package/packages/react/src/components/Toast.tsx +134 -0
  55. package/packages/react/src/components/Toggle.tsx +58 -0
  56. package/packages/react/src/components/Tooltip.tsx +31 -0
  57. package/packages/react/src/components/Typography.tsx +66 -0
  58. package/packages/react/src/index.ts +40 -0
  59. package/packages/react/src/styles.css +2039 -0
  60. package/packages/react/src/tokens/index.ts +94 -0
  61. package/packages/rust/cronixui/src/colors.rs +135 -0
  62. package/packages/rust/cronixui/src/components/accordion.rs +47 -0
  63. package/packages/rust/cronixui/src/components/alert.rs +95 -0
  64. package/packages/rust/cronixui/src/components/avatar.rs +85 -0
  65. package/packages/rust/cronixui/src/components/badge.rs +35 -0
  66. package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -0
  67. package/packages/rust/cronixui/src/components/button.rs +70 -0
  68. package/packages/rust/cronixui/src/components/card.rs +259 -0
  69. package/packages/rust/cronixui/src/components/command_palette.rs +254 -0
  70. package/packages/rust/cronixui/src/components/dropdown.rs +179 -0
  71. package/packages/rust/cronixui/src/components/file_input.rs +74 -0
  72. package/packages/rust/cronixui/src/components/input.rs +21 -0
  73. package/packages/rust/cronixui/src/components/list.rs +38 -0
  74. package/packages/rust/cronixui/src/components/mod.rs +51 -0
  75. package/packages/rust/cronixui/src/{modal.rs → components/modal.rs} +15 -1
  76. package/packages/rust/cronixui/src/components/nav.rs +19 -0
  77. package/packages/rust/cronixui/src/{pagination.rs → components/pagination.rs} +14 -13
  78. package/packages/rust/cronixui/src/components/progress.rs +50 -0
  79. package/packages/rust/cronixui/src/components/search.rs +185 -0
  80. package/packages/rust/cronixui/src/components/skeleton.rs +63 -0
  81. package/packages/rust/cronixui/src/components/spinner.rs +21 -0
  82. package/packages/rust/cronixui/src/components/table.rs +56 -0
  83. package/packages/rust/cronixui/src/components/tabs.rs +43 -0
  84. package/packages/rust/cronixui/src/components/toast.rs +69 -0
  85. package/packages/rust/cronixui/src/{toggle.rs → components/toggle.rs} +7 -5
  86. package/packages/rust/cronixui/src/components/tooltip.rs +11 -0
  87. package/packages/rust/cronixui/src/lib.rs +111 -62
  88. package/packages/rust/cronixui/src/tokens.rs +107 -0
  89. package/packages/web/src/tokens.ts +120 -0
  90. package/packages/web/src/variables.css +81 -81
  91. package/packages/python/cronixui/pyproject.toml +0 -11
  92. package/packages/react/src/components/Accordion.jsx +0 -50
  93. package/packages/react/src/components/Alert.jsx +0 -62
  94. package/packages/react/src/components/Avatar.jsx +0 -34
  95. package/packages/react/src/components/Badge.jsx +0 -15
  96. package/packages/react/src/components/Breadcrumb.jsx +0 -27
  97. package/packages/react/src/components/Button.jsx +0 -21
  98. package/packages/react/src/components/Card.jsx +0 -23
  99. package/packages/react/src/components/Checkbox.jsx +0 -27
  100. package/packages/react/src/components/CommandPalette.jsx +0 -93
  101. package/packages/react/src/components/Dropdown.jsx +0 -48
  102. package/packages/react/src/components/FileInput.jsx +0 -44
  103. package/packages/react/src/components/Input.jsx +0 -22
  104. package/packages/react/src/components/List.jsx +0 -29
  105. package/packages/react/src/components/Modal.jsx +0 -65
  106. package/packages/react/src/components/Nav.jsx +0 -50
  107. package/packages/react/src/components/Pagination.jsx +0 -81
  108. package/packages/react/src/components/Progress.jsx +0 -23
  109. package/packages/react/src/components/Radio.jsx +0 -50
  110. package/packages/react/src/components/Search.jsx +0 -70
  111. package/packages/react/src/components/Select.jsx +0 -33
  112. package/packages/react/src/components/Skeleton.jsx +0 -15
  113. package/packages/react/src/components/Slider.jsx +0 -29
  114. package/packages/react/src/components/Spinner.jsx +0 -5
  115. package/packages/react/src/components/Stat.jsx +0 -19
  116. package/packages/react/src/components/Table.jsx +0 -48
  117. package/packages/react/src/components/Tabs.jsx +0 -65
  118. package/packages/react/src/components/Tag.jsx +0 -19
  119. package/packages/react/src/components/Textarea.jsx +0 -17
  120. package/packages/react/src/components/Toast.jsx +0 -78
  121. package/packages/react/src/components/Toggle.jsx +0 -34
  122. package/packages/react/src/components/Tooltip.jsx +0 -12
  123. package/packages/react/src/index.d.ts +0 -103
  124. package/packages/react/src/index.js +0 -33
  125. package/packages/rust/cronixui/src/accordion.rs +0 -49
  126. package/packages/rust/cronixui/src/command_palette.rs +0 -62
  127. package/packages/rust/cronixui/src/dropdown.rs +0 -31
  128. package/packages/rust/cronixui/src/search.rs +0 -49
  129. package/packages/rust/cronixui/src/tabs.rs +0 -23
  130. package/packages/rust/cronixui/src/toast.rs +0 -70
@@ -0,0 +1,80 @@
1
+ import * as React from 'react';
2
+
3
+ export type AlertVariant = 'info' | 'success' | 'warning' | 'error';
4
+
5
+ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ variant?: AlertVariant;
7
+ title?: string;
8
+ dismissible?: boolean;
9
+ onClose?: () => void;
10
+ }
11
+
12
+ export const Alert: React.FC<AlertProps> = ({
13
+ variant = 'info',
14
+ title,
15
+ children,
16
+ dismissible = true,
17
+ onClose,
18
+ className = '',
19
+ ...props
20
+ }) => {
21
+ const [visible, setVisible] = React.useState(true);
22
+
23
+ const handleClose = () => {
24
+ setVisible(false);
25
+ onClose?.();
26
+ };
27
+
28
+ if (!visible) return null;
29
+
30
+ const icons: Record<AlertVariant, React.ReactNode> = {
31
+ success: <polyline points="20 6 9 17 4 12" />,
32
+ error: (
33
+ <>
34
+ <circle cx="12" cy="12" r="10" />
35
+ <line x1="15" y1="9" x2="9" y2="15" />
36
+ <line x1="9" y1="9" x2="15" y2="15" />
37
+ </>
38
+ ),
39
+ warning: (
40
+ <>
41
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
42
+ <line x1="12" y1="9" x2="12" y2="13" />
43
+ <line x1="12" y1="17" x2="12.01" y2="17" />
44
+ </>
45
+ ),
46
+ info: (
47
+ <>
48
+ <circle cx="12" cy="12" r="10" />
49
+ <line x1="12" y1="16" x2="12" y2="12" />
50
+ <line x1="12" y1="8" x2="12.01" y2="8" />
51
+ </>
52
+ ),
53
+ };
54
+
55
+ return (
56
+ <div className={`cn-alert cn-alert-${variant} ${className}`.trim()} role="alert" {...props}>
57
+ <div className="cn-alert-icon">
58
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
59
+ {icons[variant]}
60
+ </svg>
61
+ </div>
62
+ <div className="cn-alert-content">
63
+ {title && <div className="cn-alert-title">{title}</div>}
64
+ <div className="cn-alert-message">{children}</div>
65
+ </div>
66
+ {dismissible && (
67
+ <button className="cn-alert-close" onClick={handleClose} aria-label="Close">
68
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
69
+ <line x1="18" y1="6" x2="6" y2="18" />
70
+ <line x1="6" y1="6" x2="18" y2="18" />
71
+ </svg>
72
+ </button>
73
+ )}
74
+ </div>
75
+ );
76
+ };
77
+
78
+ Alert.displayName = 'Alert';
79
+
80
+ export default Alert;
@@ -0,0 +1,54 @@
1
+ import * as React from 'react';
2
+
3
+ export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
4
+
5
+ export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ src?: string;
7
+ alt?: string;
8
+ initials?: string;
9
+ size?: AvatarSize;
10
+ }
11
+
12
+ export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
13
+ max?: number;
14
+ }
15
+
16
+ export const Avatar: React.FC<AvatarProps> = ({
17
+ src,
18
+ alt = '',
19
+ initials,
20
+ size = 'md',
21
+ className = '',
22
+ ...props
23
+ }) => {
24
+ const sizeClass = size !== 'md' ? `cn-avatar-${size}` : '';
25
+
26
+ return (
27
+ <div className={`cn-avatar ${sizeClass} ${className}`.trim()} {...props}>
28
+ {src ? (
29
+ <img src={src} alt={alt} />
30
+ ) : initials ? (
31
+ initials
32
+ ) : null}
33
+ </div>
34
+ );
35
+ };
36
+
37
+ Avatar.displayName = 'Avatar';
38
+
39
+ export const AvatarGroup: React.FC<AvatarGroupProps> = ({ children, max, className = '', ...props }) => {
40
+ const items = React.Children.toArray(children);
41
+ const visible = max ? items.slice(0, max) : items;
42
+ const remaining = max ? items.length - max : 0;
43
+
44
+ return (
45
+ <div className={`cn-avatar-group ${className}`.trim()} {...props}>
46
+ {visible}
47
+ {remaining > 0 && <div className="cn-avatar">+{remaining}</div>}
48
+ </div>
49
+ );
50
+ };
51
+
52
+ AvatarGroup.displayName = 'AvatarGroup';
53
+
54
+ export default Avatar;
@@ -0,0 +1,32 @@
1
+ import * as React from 'react';
2
+
3
+ export type BadgeVariant = 'default' | 'accent' | 'success' | 'warning' | 'error' | 'info';
4
+
5
+ export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
6
+ variant?: BadgeVariant;
7
+ solid?: boolean;
8
+ }
9
+
10
+ export const Badge: React.FC<BadgeProps> = ({
11
+ children,
12
+ variant = 'default',
13
+ solid = false,
14
+ className = '',
15
+ ...props
16
+ }) => {
17
+ const variantClass = variant !== 'default' ? `cn-badge-${variant}` : '';
18
+ const solidClass = solid ? 'cn-badge-solid' : '';
19
+
20
+ return (
21
+ <span
22
+ className={`cn-badge ${variantClass} ${solidClass} ${className}`.trim()}
23
+ {...props}
24
+ >
25
+ {children}
26
+ </span>
27
+ );
28
+ };
29
+
30
+ Badge.displayName = 'Badge';
31
+
32
+ export default Badge;
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+
3
+ export interface BreadcrumbProps extends React.HTMLAttributes<HTMLElement> {}
4
+
5
+ export interface BreadcrumbItemProps extends React.HTMLAttributes<HTMLAnchorElement | HTMLSpanElement> {
6
+ href?: string;
7
+ active?: boolean;
8
+ }
9
+
10
+ export const Breadcrumb: React.FC<BreadcrumbProps> & {
11
+ Item: React.FC<BreadcrumbItemProps>;
12
+ } = Object.assign(
13
+ ({ children, className = '', ...props }: BreadcrumbProps) => {
14
+ const items = React.Children.toArray(children);
15
+
16
+ return (
17
+ <nav className={`cn-breadcrumb ${className}`.trim()} aria-label="Breadcrumb" {...props}>
18
+ {items.map((child, idx) => (
19
+ <span key={idx}>
20
+ {child}
21
+ {idx < items.length - 1 && (
22
+ <span className="cn-breadcrumb-separator" aria-hidden="true">/</span>
23
+ )}
24
+ </span>
25
+ ))}
26
+ </nav>
27
+ );
28
+ },
29
+ {
30
+ Item: ({ children, href, active = false, className = '', ...props }: BreadcrumbItemProps) => {
31
+ if (active) {
32
+ return (
33
+ <span className={`cn-breadcrumb-current ${className}`.trim()} aria-current="page" {...props}>
34
+ {children}
35
+ </span>
36
+ );
37
+ }
38
+ return (
39
+ <a href={href} className={`cn-breadcrumb-item ${className}`.trim()} {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}>
40
+ {children}
41
+ </a>
42
+ );
43
+ },
44
+ }
45
+ );
46
+
47
+ Breadcrumb.displayName = 'Breadcrumb';
48
+ (Breadcrumb.Item as React.FC<BreadcrumbItemProps> & { displayName?: string }).displayName = 'BreadcrumbItem';
49
+
50
+ export default Breadcrumb;
@@ -0,0 +1,47 @@
1
+ import * as React from 'react';
2
+
3
+ export type ButtonVariant = 'primary' | 'ghost' | 'outline' | 'danger' | 'success' | 'default';
4
+ export type ButtonSize = 'sm' | 'md' | 'lg';
5
+
6
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
7
+ variant?: ButtonVariant;
8
+ size?: ButtonSize;
9
+ icon?: boolean;
10
+ }
11
+
12
+ export interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {}
13
+
14
+ export const Button: React.FC<ButtonProps> & {
15
+ Group: React.FC<ButtonGroupProps>;
16
+ } = Object.assign(
17
+ React.forwardRef<HTMLButtonElement, ButtonProps>(
18
+ ({ children, variant = 'default', size = 'md', icon = false, disabled = false, className = '', ...props }, ref) => {
19
+ const variantClass = variant !== 'default' ? `cn-btn-${variant}` : '';
20
+ const sizeClass = size !== 'md' ? `cn-btn-${size}` : '';
21
+ const iconClass = icon ? 'cn-btn-icon' : '';
22
+
23
+ return (
24
+ <button
25
+ ref={ref}
26
+ className={`cn-btn ${variantClass} ${sizeClass} ${iconClass} ${className}`.trim()}
27
+ disabled={disabled}
28
+ {...props}
29
+ >
30
+ {children}
31
+ </button>
32
+ );
33
+ }
34
+ ),
35
+ {
36
+ Group: ({ children, className = '', ...props }: ButtonGroupProps) => (
37
+ <div className={`cn-btn-group ${className}`.trim()} {...props}>
38
+ {children}
39
+ </div>
40
+ ),
41
+ }
42
+ );
43
+
44
+ Button.displayName = 'Button';
45
+ Button.Group.displayName = 'Button.Group';
46
+
47
+ export default Button;
@@ -0,0 +1,69 @@
1
+ import * as React from 'react';
2
+
3
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ clickable?: boolean;
5
+ }
6
+
7
+ export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}
8
+
9
+ export interface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> {}
10
+
11
+ export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {}
12
+
13
+ export interface CardIconProps extends React.HTMLAttributes<HTMLDivElement> {}
14
+
15
+ export interface CardTitleProps extends React.HTMLAttributes<HTMLDivElement> {}
16
+
17
+ export interface CardSubtitleProps extends React.HTMLAttributes<HTMLDivElement> {}
18
+
19
+ export const Card: React.FC<CardProps> & {
20
+ Header: React.FC<CardHeaderProps>;
21
+ Body: React.FC<CardBodyProps>;
22
+ Footer: React.FC<CardFooterProps>;
23
+ Icon: React.FC<CardIconProps>;
24
+ Title: React.FC<CardTitleProps>;
25
+ Subtitle: React.FC<CardSubtitleProps>;
26
+ } = Object.assign(
27
+ ({ children, clickable = false, className = '', ...props }: CardProps) => {
28
+ return (
29
+ <div
30
+ className={`cn-card ${clickable ? 'cn-card-clickable' : ''} ${className}`.trim()}
31
+ {...props}
32
+ >
33
+ {children}
34
+ </div>
35
+ );
36
+ },
37
+ {
38
+ Header: ({ children, className = '', ...props }: CardHeaderProps) => (
39
+ <div className={`cn-card-header ${className}`.trim()} {...props}>{children}</div>
40
+ ),
41
+ Body: ({ children, className = '', ...props }: CardBodyProps) => (
42
+ <div className={`cn-card-body ${className}`.trim()} {...props}>{children}</div>
43
+ ),
44
+ Footer: ({ children, className = '', ...props }: CardFooterProps) => (
45
+ <div className={`cn-card-footer ${className}`.trim()} {...props}>{children}</div>
46
+ ),
47
+ Icon: ({ children, className = '', ...props }: CardIconProps) => (
48
+ <div className={`cn-card-icon ${className}`.trim()} {...props}>
49
+ {children}
50
+ </div>
51
+ ),
52
+ Title: ({ children, className = '', ...props }: CardTitleProps) => (
53
+ <div className={`cn-card-title ${className}`.trim()} {...props}>{children}</div>
54
+ ),
55
+ Subtitle: ({ children, className = '', ...props }: CardSubtitleProps) => (
56
+ <div className={`cn-card-subtitle ${className}`.trim()} {...props}>{children}</div>
57
+ ),
58
+ }
59
+ );
60
+
61
+ Card.displayName = 'Card';
62
+ Card.Header.displayName = 'Card.Header';
63
+ Card.Body.displayName = 'Card.Body';
64
+ Card.Footer.displayName = 'Card.Footer';
65
+ Card.Icon.displayName = 'Card.Icon';
66
+ Card.Title.displayName = 'Card.Title';
67
+ Card.Subtitle.displayName = 'Card.Subtitle';
68
+
69
+ export default Card;
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+
3
+ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'checked' | 'onChange'> {
4
+ checked?: boolean;
5
+ onChange?: (checked: boolean) => void;
6
+ label?: React.ReactNode;
7
+ }
8
+
9
+ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
10
+ ({ checked = false, onChange, disabled = false, children, label, className = '', ...props }, ref) => {
11
+ return (
12
+ <label className={`cn-checkbox ${disabled ? 'disabled' : ''} ${className}`.trim()}>
13
+ <input
14
+ ref={ref}
15
+ type="checkbox"
16
+ checked={checked}
17
+ onChange={(e) => onChange?.(e.target.checked)}
18
+ disabled={disabled}
19
+ {...props}
20
+ />
21
+ <span className="cn-checkbox-box" />
22
+ {(label || children) && <span className="cn-checkbox-label">{label || children}</span>}
23
+ </label>
24
+ );
25
+ }
26
+ );
27
+
28
+ Checkbox.displayName = 'Checkbox';
29
+
30
+ export default Checkbox;
@@ -0,0 +1,131 @@
1
+ import * as React from 'react';
2
+
3
+ export interface CommandPaletteItem {
4
+ title: string;
5
+ subtitle?: string;
6
+ icon?: React.ReactNode;
7
+ kbd?: string;
8
+ action?: () => void;
9
+ }
10
+
11
+ export interface CommandPaletteProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
12
+ items?: CommandPaletteItem[];
13
+ isOpen?: boolean;
14
+ onClose?: () => void;
15
+ onSelect?: (item: CommandPaletteItem) => void;
16
+ placeholder?: string;
17
+ }
18
+
19
+ export const CommandPalette: React.FC<CommandPaletteProps> = ({
20
+ items = [],
21
+ isOpen = false,
22
+ onClose,
23
+ onSelect,
24
+ placeholder = 'Search commands...',
25
+ className = '',
26
+ ...props
27
+ }) => {
28
+ const [query, setQuery] = React.useState('');
29
+ const [activeIndex, setActiveIndex] = React.useState(0);
30
+ const inputRef = React.useRef<HTMLInputElement>(null);
31
+
32
+ const filtered = items.filter(
33
+ (item) =>
34
+ item.title.toLowerCase().includes(query.toLowerCase()) ||
35
+ (item.subtitle && item.subtitle.toLowerCase().includes(query.toLowerCase()))
36
+ );
37
+
38
+ React.useEffect(() => {
39
+ if (isOpen) {
40
+ setQuery('');
41
+ setActiveIndex(0);
42
+ setTimeout(() => inputRef.current?.focus(), 100);
43
+ }
44
+ }, [isOpen]);
45
+
46
+ React.useEffect(() => {
47
+ const handleKeyDown = (e: KeyboardEvent) => {
48
+ if (!isOpen) return;
49
+
50
+ if (e.key === 'Escape') {
51
+ onClose?.();
52
+ } else if (e.key === 'ArrowDown') {
53
+ e.preventDefault();
54
+ setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
55
+ } else if (e.key === 'ArrowUp') {
56
+ e.preventDefault();
57
+ setActiveIndex((i) => Math.max(i - 1, 0));
58
+ } else if (e.key === 'Enter' && filtered[activeIndex]) {
59
+ handleSelect(filtered[activeIndex]);
60
+ }
61
+ };
62
+
63
+ document.addEventListener('keydown', handleKeyDown);
64
+ return () => document.removeEventListener('keydown', handleKeyDown);
65
+ }, [isOpen, filtered, activeIndex, onClose]);
66
+
67
+ const handleSelect = (item: CommandPaletteItem) => {
68
+ item.action?.();
69
+ onSelect?.(item);
70
+ onClose?.();
71
+ };
72
+
73
+ if (!isOpen) return null;
74
+
75
+ return (
76
+ <div
77
+ className={`cn-command-palette cn-command-palette-open ${className}`.trim()}
78
+ onClick={(e) => e.target === e.currentTarget && onClose?.()}
79
+ role="dialog"
80
+ aria-modal="true"
81
+ aria-label="Command palette"
82
+ {...props}
83
+ >
84
+ <div className="cn-command-palette-inner">
85
+ <input
86
+ ref={inputRef}
87
+ type="text"
88
+ className="cn-command-palette-input"
89
+ placeholder={placeholder}
90
+ value={query}
91
+ onChange={(e) => {
92
+ setQuery(e.target.value);
93
+ setActiveIndex(0);
94
+ }}
95
+ role="combobox"
96
+ aria-autocomplete="list"
97
+ aria-expanded={filtered.length > 0}
98
+ />
99
+ <div className="cn-command-palette-results" role="listbox">
100
+ {filtered.length > 0 ? (
101
+ filtered.map((item, idx) => (
102
+ <div
103
+ key={idx}
104
+ className={`cn-command-item ${idx === activeIndex ? 'cn-command-item-active' : ''}`.trim()}
105
+ onClick={() => handleSelect(item)}
106
+ onMouseEnter={() => setActiveIndex(idx)}
107
+ role="option"
108
+ aria-selected={idx === activeIndex}
109
+ >
110
+ {item.icon && <div className="cn-command-item-icon">{item.icon}</div>}
111
+ <div className="cn-command-item-content">
112
+ <div className="cn-command-item-title">{item.title}</div>
113
+ {item.subtitle && (
114
+ <div className="cn-command-item-subtitle">{item.subtitle}</div>
115
+ )}
116
+ </div>
117
+ {item.kbd && <div className="cn-command-item-kbd">{item.kbd}</div>}
118
+ </div>
119
+ ))
120
+ ) : (
121
+ <div className="cn-command-empty">No commands found</div>
122
+ )}
123
+ </div>
124
+ </div>
125
+ </div>
126
+ );
127
+ };
128
+
129
+ CommandPalette.displayName = 'CommandPalette';
130
+
131
+ export default CommandPalette;
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+
3
+ export type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'fluid';
4
+
5
+ export interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ size?: ContainerSize;
7
+ }
8
+
9
+ export const Container: React.FC<ContainerProps> = ({
10
+ size = 'md',
11
+ children,
12
+ className = '',
13
+ ...props
14
+ }) => {
15
+ const sizeClass = size !== 'md' ? `cn-container-${size}` : '';
16
+
17
+ return (
18
+ <div className={`cn-container ${sizeClass} ${className}`.trim()} {...props}>
19
+ {children}
20
+ </div>
21
+ );
22
+ };
23
+
24
+ Container.displayName = 'Container';
25
+
26
+ export default Container;
@@ -0,0 +1,88 @@
1
+ import * as React from 'react';
2
+
3
+ export interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ trigger: React.ReactNode;
5
+ }
6
+
7
+ export interface DropdownItemProps extends React.HTMLAttributes<HTMLDivElement | HTMLButtonElement> {
8
+ icon?: React.ReactNode;
9
+ divider?: boolean;
10
+ }
11
+
12
+ export const Dropdown: React.FC<DropdownProps> & {
13
+ Item: React.FC<DropdownItemProps>;
14
+ Divider: React.FC;
15
+ } = Object.assign(
16
+ ({ trigger, children, className = '', ...props }: DropdownProps) => {
17
+ const [isOpen, setIsOpen] = React.useState(false);
18
+ const dropdownRef = React.useRef<HTMLDivElement>(null);
19
+
20
+ const toggle = () => setIsOpen((prev) => !prev);
21
+ const close = () => setIsOpen(false);
22
+
23
+ React.useEffect(() => {
24
+ const handleClickOutside = (e: MouseEvent) => {
25
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
26
+ close();
27
+ }
28
+ };
29
+
30
+ if (isOpen) {
31
+ document.addEventListener('click', handleClickOutside);
32
+ }
33
+
34
+ return () => {
35
+ document.removeEventListener('click', handleClickOutside);
36
+ };
37
+ }, [isOpen]);
38
+
39
+ return (
40
+ <div
41
+ ref={dropdownRef}
42
+ className={`cn-dropdown ${isOpen ? 'cn-dropdown-open' : ''} ${className}`.trim()}
43
+ {...props}
44
+ >
45
+ <div
46
+ className="cn-dropdown-trigger"
47
+ onClick={toggle}
48
+ role="button"
49
+ aria-haspopup="true"
50
+ aria-expanded={isOpen}
51
+ tabIndex={0}
52
+ onKeyDown={(e) => {
53
+ if (e.key === 'Enter' || e.key === ' ') {
54
+ e.preventDefault();
55
+ toggle();
56
+ }
57
+ }}
58
+ >
59
+ {trigger}
60
+ </div>
61
+ <div className="cn-dropdown-menu" role="menu" onClick={close}>
62
+ {children}
63
+ </div>
64
+ </div>
65
+ );
66
+ },
67
+ {
68
+ Item: ({ children, icon, onClick, className = '', ...props }: DropdownItemProps) => (
69
+ <div
70
+ className={`cn-dropdown-item ${className}`.trim()}
71
+ onClick={onClick}
72
+ role="menuitem"
73
+ tabIndex={0}
74
+ {...props}
75
+ >
76
+ {icon && <span className="cn-dropdown-item-icon">{icon}</span>}
77
+ {children}
78
+ </div>
79
+ ),
80
+ Divider: () => <div className="cn-dropdown-divider" role="separator" />,
81
+ }
82
+ );
83
+
84
+ Dropdown.displayName = 'Dropdown';
85
+ Dropdown.Item.displayName = 'Dropdown.Item';
86
+ Dropdown.Divider.displayName = 'Dropdown.Divider';
87
+
88
+ export default Dropdown;