@justin_evo/evo-ui 1.1.0 → 1.2.1

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 (80) hide show
  1. package/README.md +3 -3
  2. package/dist/TopNav/TopNav.d.ts +19 -0
  3. package/dist/declarations.d.ts +6 -6
  4. package/dist/evo-ui.css +1 -1
  5. package/dist/index.cjs.js +1 -1
  6. package/dist/index.es.js +3301 -3197
  7. package/package.json +52 -52
  8. package/src/Alert/Alert.tsx +49 -49
  9. package/src/AutoComplete/AutoComplete.tsx +810 -810
  10. package/src/Badge/Badge.tsx +53 -53
  11. package/src/Breadcrumb/Breadcrumb.tsx +53 -53
  12. package/src/Button/Button.tsx +125 -125
  13. package/src/Card/Card.tsx +257 -257
  14. package/src/Checkbox/Checkbox.tsx +59 -59
  15. package/src/CommandPalette/CommandPalette.tsx +185 -185
  16. package/src/Container/Container.tsx +31 -31
  17. package/src/Divider/Divider.tsx +31 -31
  18. package/src/Form/Form.tsx +185 -185
  19. package/src/Grid/Grid.tsx +66 -66
  20. package/src/ImageCropper/ImageCropper.tsx +911 -911
  21. package/src/Input/Input.tsx +74 -74
  22. package/src/Modal/Modal.tsx +77 -77
  23. package/src/Nav/Nav.tsx +708 -708
  24. package/src/Notification/Notification.tsx +1503 -1503
  25. package/src/Pagination/Pagination.tsx +76 -76
  26. package/src/Radio/Radio.tsx +69 -69
  27. package/src/RichTextArea/RichTextArea.tsx +886 -869
  28. package/src/Select/Select.tsx +515 -515
  29. package/src/Skeleton/Skeleton.tsx +70 -70
  30. package/src/Stack/Stack.tsx +52 -52
  31. package/src/Table/Table.tsx +335 -335
  32. package/src/Tabs/Tabs.tsx +90 -90
  33. package/src/Theme/ThemeProvider.tsx +253 -253
  34. package/src/Theme/ThemeToggle.tsx +79 -79
  35. package/src/Toggle/Toggle.tsx +48 -48
  36. package/src/Tooltip/Tooltip.tsx +38 -38
  37. package/src/TopNav/TopNav.tsx +1163 -994
  38. package/src/TreeSelect/TreeSelect.tsx +825 -825
  39. package/src/css/alert.module.scss +93 -93
  40. package/src/css/autocomplete.module.scss +416 -416
  41. package/src/css/badge.module.scss +82 -82
  42. package/src/css/base/_color.scss +159 -159
  43. package/src/css/base/_theme.scss +237 -237
  44. package/src/css/base/_variables.scss +161 -161
  45. package/src/css/breadcrumb.module.scss +50 -50
  46. package/src/css/button.module.scss +385 -385
  47. package/src/css/card.module.scss +217 -217
  48. package/src/css/checkbox.module.scss +123 -120
  49. package/src/css/commandpalette.module.scss +211 -211
  50. package/src/css/container.module.scss +18 -18
  51. package/src/css/divider.module.scss +41 -41
  52. package/src/css/form.module.scss +245 -245
  53. package/src/css/imagecropper.module.scss +397 -397
  54. package/src/css/input.module.scss +89 -89
  55. package/src/css/modal.module.scss +105 -105
  56. package/src/css/nav.module.scss +494 -494
  57. package/src/css/notification.module.scss +691 -691
  58. package/src/css/pagination.module.scss +63 -63
  59. package/src/css/radio.module.scss +89 -89
  60. package/src/css/richtextarea.module.scss +307 -307
  61. package/src/css/select.module.scss +525 -525
  62. package/src/css/skeleton.module.scss +30 -30
  63. package/src/css/table.module.scss +386 -386
  64. package/src/css/tabs.module.scss +63 -63
  65. package/src/css/theme-toggle.module.scss +83 -83
  66. package/src/css/toggle.module.scss +54 -54
  67. package/src/css/tooltip.module.scss +97 -97
  68. package/src/css/topnav.module.scss +568 -396
  69. package/src/css/treeselect.module.scss +558 -558
  70. package/src/css/utilities/_borders.scss +111 -111
  71. package/src/css/utilities/_colors.scss +66 -66
  72. package/src/css/utilities/_effects.scss +216 -216
  73. package/src/css/utilities/_layout.scss +181 -181
  74. package/src/css/utilities/_position.scss +75 -75
  75. package/src/css/utilities/_sizing.scss +138 -138
  76. package/src/css/utilities/_spacing.scss +99 -99
  77. package/src/css/utilities/_typography.scss +121 -121
  78. package/src/css/utilities/index.scss +24 -24
  79. package/src/declarations.d.ts +6 -6
  80. package/src/index.ts +60 -60
@@ -1,185 +1,185 @@
1
- import React, { useState, useEffect, useRef, useCallback } from 'react';
2
- import styles from '../css/commandpalette.module.scss';
3
-
4
- export interface CommandPaletteItem {
5
- label: string;
6
- description?: string;
7
- group?: string;
8
- icon?: React.ReactNode;
9
- shortcut?: string[];
10
- onSelect: () => void;
11
- }
12
-
13
- interface EvoCommandPaletteProps {
14
- items: CommandPaletteItem[];
15
- placeholder?: string;
16
- open?: boolean;
17
- onClose?: () => void;
18
- }
19
-
20
- // Detect Mac for shortcut display only — keyboard handler uses ctrlKey||metaKey for both
21
- const isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
22
-
23
- const SearchIcon = () => (
24
- <svg viewBox="0 0 16 16" fill="none" width="16" height="16">
25
- <circle cx="7" cy="7" r="4.5" stroke="currentColor" strokeWidth="1.5" />
26
- <path d="M10.5 10.5L13 13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
27
- </svg>
28
- );
29
-
30
- export const EvoCommandPalette = ({
31
- items,
32
- placeholder = 'Search commands…',
33
- open: controlledOpen,
34
- onClose,
35
- }: EvoCommandPaletteProps) => {
36
- const [internalOpen, setInternalOpen] = useState(false);
37
- const [query, setQuery] = useState('');
38
- const [activeIdx, setActiveIdx] = useState(0);
39
- const inputRef = useRef<HTMLInputElement>(null);
40
- const listRef = useRef<HTMLDivElement>(null);
41
-
42
- const isControlled = controlledOpen !== undefined;
43
- const isOpen = isControlled ? controlledOpen : internalOpen;
44
-
45
- const close = useCallback(() => {
46
- if (!isControlled) setInternalOpen(false);
47
- onClose?.();
48
- }, [isControlled, onClose]);
49
-
50
- // Ctrl+K (Windows/Linux) / ⌘+K (Mac)
51
- useEffect(() => {
52
- const handler = (e: KeyboardEvent) => {
53
- if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
54
- e.preventDefault();
55
- if (!isControlled) setInternalOpen(o => !o);
56
- }
57
- if (e.key === 'Escape' && isOpen) close();
58
- };
59
- document.addEventListener('keydown', handler);
60
- return () => document.removeEventListener('keydown', handler);
61
- }, [isControlled, isOpen, close]);
62
-
63
- useEffect(() => {
64
- if (isOpen) {
65
- setQuery('');
66
- setActiveIdx(0);
67
- // Small delay so the element is mounted before focus
68
- const t = setTimeout(() => inputRef.current?.focus(), 30);
69
- return () => clearTimeout(t);
70
- }
71
- }, [isOpen]);
72
-
73
- const filtered = query.trim()
74
- ? items.filter(item =>
75
- item.label.toLowerCase().includes(query.toLowerCase()) ||
76
- item.description?.toLowerCase().includes(query.toLowerCase()) ||
77
- item.group?.toLowerCase().includes(query.toLowerCase())
78
- )
79
- : items;
80
-
81
- const grouped = filtered.reduce<Record<string, CommandPaletteItem[]>>((acc, item) => {
82
- const g = item.group ?? 'Actions';
83
- if (!acc[g]) acc[g] = [];
84
- acc[g].push(item);
85
- return acc;
86
- }, {});
87
-
88
- const flat = Object.values(grouped).flat();
89
-
90
- const scrollActiveIntoView = (idx: number) => {
91
- const el = listRef.current?.querySelector(`[data-idx="${idx}"]`) as HTMLElement | null;
92
- el?.scrollIntoView({ block: 'nearest' });
93
- };
94
-
95
- const handleKeyDown = (e: React.KeyboardEvent) => {
96
- if (e.key === 'ArrowDown') {
97
- e.preventDefault();
98
- setActiveIdx(i => {
99
- const next = Math.min(i + 1, flat.length - 1);
100
- scrollActiveIntoView(next);
101
- return next;
102
- });
103
- } else if (e.key === 'ArrowUp') {
104
- e.preventDefault();
105
- setActiveIdx(i => {
106
- const next = Math.max(i - 1, 0);
107
- scrollActiveIntoView(next);
108
- return next;
109
- });
110
- } else if (e.key === 'Enter' && flat[activeIdx]) {
111
- flat[activeIdx].onSelect();
112
- close();
113
- }
114
- };
115
-
116
- if (!isOpen) return null;
117
-
118
- let globalIdx = 0;
119
-
120
- return (
121
- <div className={styles.overlay} onClick={close} role="dialog" aria-modal="true">
122
- <div
123
- className={styles.palette}
124
- onClick={e => e.stopPropagation()}
125
- onKeyDown={handleKeyDown}
126
- >
127
- <div className={styles.searchRow}>
128
- <span className={styles.searchIconWrap}><SearchIcon /></span>
129
- <input
130
- ref={inputRef}
131
- className={styles.searchInput}
132
- placeholder={placeholder}
133
- value={query}
134
- onChange={e => { setQuery(e.target.value); setActiveIdx(0); }}
135
- aria-label="Command search"
136
- />
137
- <kbd className={styles.escBadge}>Esc</kbd>
138
- </div>
139
-
140
- <div className={styles.results} ref={listRef}>
141
- {flat.length === 0 && (
142
- <div className={styles.empty}>No results for &ldquo;{query}&rdquo;</div>
143
- )}
144
- {Object.entries(grouped).map(([group, groupItems]) => (
145
- <div key={group} className={styles.group}>
146
- <div className={styles.groupLabel}>{group}</div>
147
- {groupItems.map(item => {
148
- const idx = globalIdx++;
149
- return (
150
- <button
151
- key={item.label}
152
- data-idx={idx}
153
- className={[styles.resultItem, idx === activeIdx ? styles.resultActive : '']
154
- .filter(Boolean)
155
- .join(' ')}
156
- onClick={() => { item.onSelect(); close(); }}
157
- onMouseEnter={() => setActiveIdx(idx)}
158
- >
159
- {item.icon && <span className={styles.resultIcon}>{item.icon}</span>}
160
- <span className={styles.resultLabel}>{item.label}</span>
161
- {item.description && <span className={styles.resultDesc}>{item.description}</span>}
162
- {item.shortcut && (
163
- <span className={styles.resultShortcut}>
164
- {item.shortcut.map((k, i) => <kbd key={i}>{k}</kbd>)}
165
- </span>
166
- )}
167
- </button>
168
- );
169
- })}
170
- </div>
171
- ))}
172
- </div>
173
-
174
- <div className={styles.footer}>
175
- <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>
176
- <span><kbd>↵</kbd> select</span>
177
- <span><kbd>Esc</kbd> close</span>
178
- <span className={styles.footerRight}>
179
- <kbd>{isMac ? '⌘' : 'Ctrl'}</kbd><kbd>K</kbd> toggle
180
- </span>
181
- </div>
182
- </div>
183
- </div>
184
- );
185
- };
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import styles from '../css/commandpalette.module.scss';
3
+
4
+ export interface CommandPaletteItem {
5
+ label: string;
6
+ description?: string;
7
+ group?: string;
8
+ icon?: React.ReactNode;
9
+ shortcut?: string[];
10
+ onSelect: () => void;
11
+ }
12
+
13
+ interface EvoCommandPaletteProps {
14
+ items: CommandPaletteItem[];
15
+ placeholder?: string;
16
+ open?: boolean;
17
+ onClose?: () => void;
18
+ }
19
+
20
+ // Detect Mac for shortcut display only — keyboard handler uses ctrlKey||metaKey for both
21
+ const isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
22
+
23
+ const SearchIcon = () => (
24
+ <svg viewBox="0 0 16 16" fill="none" width="16" height="16">
25
+ <circle cx="7" cy="7" r="4.5" stroke="currentColor" strokeWidth="1.5" />
26
+ <path d="M10.5 10.5L13 13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
27
+ </svg>
28
+ );
29
+
30
+ export const EvoCommandPalette = ({
31
+ items,
32
+ placeholder = 'Search commands…',
33
+ open: controlledOpen,
34
+ onClose,
35
+ }: EvoCommandPaletteProps) => {
36
+ const [internalOpen, setInternalOpen] = useState(false);
37
+ const [query, setQuery] = useState('');
38
+ const [activeIdx, setActiveIdx] = useState(0);
39
+ const inputRef = useRef<HTMLInputElement>(null);
40
+ const listRef = useRef<HTMLDivElement>(null);
41
+
42
+ const isControlled = controlledOpen !== undefined;
43
+ const isOpen = isControlled ? controlledOpen : internalOpen;
44
+
45
+ const close = useCallback(() => {
46
+ if (!isControlled) setInternalOpen(false);
47
+ onClose?.();
48
+ }, [isControlled, onClose]);
49
+
50
+ // Ctrl+K (Windows/Linux) / ⌘+K (Mac)
51
+ useEffect(() => {
52
+ const handler = (e: KeyboardEvent) => {
53
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
54
+ e.preventDefault();
55
+ if (!isControlled) setInternalOpen(o => !o);
56
+ }
57
+ if (e.key === 'Escape' && isOpen) close();
58
+ };
59
+ document.addEventListener('keydown', handler);
60
+ return () => document.removeEventListener('keydown', handler);
61
+ }, [isControlled, isOpen, close]);
62
+
63
+ useEffect(() => {
64
+ if (isOpen) {
65
+ setQuery('');
66
+ setActiveIdx(0);
67
+ // Small delay so the element is mounted before focus
68
+ const t = setTimeout(() => inputRef.current?.focus(), 30);
69
+ return () => clearTimeout(t);
70
+ }
71
+ }, [isOpen]);
72
+
73
+ const filtered = query.trim()
74
+ ? items.filter(item =>
75
+ item.label.toLowerCase().includes(query.toLowerCase()) ||
76
+ item.description?.toLowerCase().includes(query.toLowerCase()) ||
77
+ item.group?.toLowerCase().includes(query.toLowerCase())
78
+ )
79
+ : items;
80
+
81
+ const grouped = filtered.reduce<Record<string, CommandPaletteItem[]>>((acc, item) => {
82
+ const g = item.group ?? 'Actions';
83
+ if (!acc[g]) acc[g] = [];
84
+ acc[g].push(item);
85
+ return acc;
86
+ }, {});
87
+
88
+ const flat = Object.values(grouped).flat();
89
+
90
+ const scrollActiveIntoView = (idx: number) => {
91
+ const el = listRef.current?.querySelector(`[data-idx="${idx}"]`) as HTMLElement | null;
92
+ el?.scrollIntoView({ block: 'nearest' });
93
+ };
94
+
95
+ const handleKeyDown = (e: React.KeyboardEvent) => {
96
+ if (e.key === 'ArrowDown') {
97
+ e.preventDefault();
98
+ setActiveIdx(i => {
99
+ const next = Math.min(i + 1, flat.length - 1);
100
+ scrollActiveIntoView(next);
101
+ return next;
102
+ });
103
+ } else if (e.key === 'ArrowUp') {
104
+ e.preventDefault();
105
+ setActiveIdx(i => {
106
+ const next = Math.max(i - 1, 0);
107
+ scrollActiveIntoView(next);
108
+ return next;
109
+ });
110
+ } else if (e.key === 'Enter' && flat[activeIdx]) {
111
+ flat[activeIdx].onSelect();
112
+ close();
113
+ }
114
+ };
115
+
116
+ if (!isOpen) return null;
117
+
118
+ let globalIdx = 0;
119
+
120
+ return (
121
+ <div className={styles.overlay} onClick={close} role="dialog" aria-modal="true">
122
+ <div
123
+ className={styles.palette}
124
+ onClick={e => e.stopPropagation()}
125
+ onKeyDown={handleKeyDown}
126
+ >
127
+ <div className={styles.searchRow}>
128
+ <span className={styles.searchIconWrap}><SearchIcon /></span>
129
+ <input
130
+ ref={inputRef}
131
+ className={styles.searchInput}
132
+ placeholder={placeholder}
133
+ value={query}
134
+ onChange={e => { setQuery(e.target.value); setActiveIdx(0); }}
135
+ aria-label="Command search"
136
+ />
137
+ <kbd className={styles.escBadge}>Esc</kbd>
138
+ </div>
139
+
140
+ <div className={styles.results} ref={listRef}>
141
+ {flat.length === 0 && (
142
+ <div className={styles.empty}>No results for &ldquo;{query}&rdquo;</div>
143
+ )}
144
+ {Object.entries(grouped).map(([group, groupItems]) => (
145
+ <div key={group} className={styles.group}>
146
+ <div className={styles.groupLabel}>{group}</div>
147
+ {groupItems.map(item => {
148
+ const idx = globalIdx++;
149
+ return (
150
+ <button
151
+ key={item.label}
152
+ data-idx={idx}
153
+ className={[styles.resultItem, idx === activeIdx ? styles.resultActive : '']
154
+ .filter(Boolean)
155
+ .join(' ')}
156
+ onClick={() => { item.onSelect(); close(); }}
157
+ onMouseEnter={() => setActiveIdx(idx)}
158
+ >
159
+ {item.icon && <span className={styles.resultIcon}>{item.icon}</span>}
160
+ <span className={styles.resultLabel}>{item.label}</span>
161
+ {item.description && <span className={styles.resultDesc}>{item.description}</span>}
162
+ {item.shortcut && (
163
+ <span className={styles.resultShortcut}>
164
+ {item.shortcut.map((k, i) => <kbd key={i}>{k}</kbd>)}
165
+ </span>
166
+ )}
167
+ </button>
168
+ );
169
+ })}
170
+ </div>
171
+ ))}
172
+ </div>
173
+
174
+ <div className={styles.footer}>
175
+ <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>
176
+ <span><kbd>↵</kbd> select</span>
177
+ <span><kbd>Esc</kbd> close</span>
178
+ <span className={styles.footerRight}>
179
+ <kbd>{isMac ? '⌘' : 'Ctrl'}</kbd><kbd>K</kbd> toggle
180
+ </span>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ );
185
+ };
@@ -1,31 +1,31 @@
1
- import React from 'react';
2
- import styles from '../css/container.module.scss';
3
-
4
- type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
5
-
6
- interface EvoContainerProps {
7
- children: React.ReactNode;
8
- size?: ContainerSize;
9
- centered?: boolean;
10
- className?: string;
11
- }
12
-
13
- export const EvoContainer = ({
14
- children,
15
- size = 'lg',
16
- centered = true,
17
- className = '',
18
- }: EvoContainerProps) => (
19
- <div
20
- className={[
21
- styles.container,
22
- styles[size],
23
- centered ? styles.centered : '',
24
- className,
25
- ]
26
- .filter(Boolean)
27
- .join(' ')}
28
- >
29
- {children}
30
- </div>
31
- );
1
+ import React from 'react';
2
+ import styles from '../css/container.module.scss';
3
+
4
+ type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
5
+
6
+ interface EvoContainerProps {
7
+ children: React.ReactNode;
8
+ size?: ContainerSize;
9
+ centered?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ export const EvoContainer = ({
14
+ children,
15
+ size = 'lg',
16
+ centered = true,
17
+ className = '',
18
+ }: EvoContainerProps) => (
19
+ <div
20
+ className={[
21
+ styles.container,
22
+ styles[size],
23
+ centered ? styles.centered : '',
24
+ className,
25
+ ]
26
+ .filter(Boolean)
27
+ .join(' ')}
28
+ >
29
+ {children}
30
+ </div>
31
+ );
@@ -1,31 +1,31 @@
1
- import styles from '../css/divider.module.scss';
2
-
3
- interface EvoDividerProps {
4
- orientation?: 'horizontal' | 'vertical';
5
- label?: string;
6
- className?: string;
7
- }
8
-
9
- export const EvoDivider = ({ orientation = 'horizontal', label, className = '' }: EvoDividerProps) => {
10
- if (label) {
11
- return (
12
- <div className={`${styles.labeled} ${className}`}>
13
- <div className={styles.line} />
14
- <span className={styles.labelText}>{label}</span>
15
- <div className={styles.line} />
16
- </div>
17
- );
18
- }
19
-
20
- return (
21
- <div
22
- className={[
23
- styles.divider,
24
- orientation === 'vertical' ? styles.vertical : styles.horizontal,
25
- className,
26
- ]
27
- .filter(Boolean)
28
- .join(' ')}
29
- />
30
- );
31
- };
1
+ import styles from '../css/divider.module.scss';
2
+
3
+ interface EvoDividerProps {
4
+ orientation?: 'horizontal' | 'vertical';
5
+ label?: string;
6
+ className?: string;
7
+ }
8
+
9
+ export const EvoDivider = ({ orientation = 'horizontal', label, className = '' }: EvoDividerProps) => {
10
+ if (label) {
11
+ return (
12
+ <div className={`${styles.labeled} ${className}`}>
13
+ <div className={styles.line} />
14
+ <span className={styles.labelText}>{label}</span>
15
+ <div className={styles.line} />
16
+ </div>
17
+ );
18
+ }
19
+
20
+ return (
21
+ <div
22
+ className={[
23
+ styles.divider,
24
+ orientation === 'vertical' ? styles.vertical : styles.horizontal,
25
+ className,
26
+ ]
27
+ .filter(Boolean)
28
+ .join(' ')}
29
+ />
30
+ );
31
+ };