aidos-ui 4.0.0 → 4.0.2

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 (99) hide show
  1. package/.claude/settings.local.json +14 -1
  2. package/CLAUDE.md +72 -0
  3. package/dist/BaseButton.js.map +1 -1
  4. package/dist/BaseLink.js.map +1 -1
  5. package/dist/BaseListItem.d.ts +1 -1
  6. package/dist/Calendar.d.ts +2 -2
  7. package/dist/Card.js +1 -1
  8. package/dist/Card.js.map +1 -1
  9. package/dist/DarkMode.js.map +1 -1
  10. package/dist/DatePicker.js.map +1 -1
  11. package/dist/Dialog.d.ts +3 -3
  12. package/dist/Dialog.js +7 -1
  13. package/dist/Dialog.js.map +1 -1
  14. package/dist/Dropdown.js.map +1 -1
  15. package/dist/Icon.d.ts +3 -3
  16. package/dist/Icon.js +2 -21
  17. package/dist/Icon.js.map +1 -1
  18. package/dist/Interactable.js.map +1 -1
  19. package/dist/NextJSRouterState.js.map +1 -1
  20. package/dist/OnVisible.d.ts +1 -1
  21. package/dist/OnVisible.js.map +1 -1
  22. package/dist/Popover.d.ts +3 -3
  23. package/dist/Popover.js +3 -1
  24. package/dist/Popover.js.map +1 -1
  25. package/dist/Providers.d.ts +2 -2
  26. package/dist/RangeInput.d.ts +1 -1
  27. package/dist/RootView.js.map +1 -1
  28. package/dist/Select.d.ts +0 -1
  29. package/dist/Storage.d.ts +2 -1
  30. package/dist/Storage.js.map +1 -1
  31. package/dist/Stream.js.map +1 -1
  32. package/dist/Text.js.map +1 -1
  33. package/dist/TextInput.d.ts +3 -2
  34. package/dist/TextInput.js +13 -4
  35. package/dist/TextInput.js.map +1 -1
  36. package/dist/TextPairing.js.map +1 -1
  37. package/dist/Tooltip.d.ts +2 -2
  38. package/dist/Tooltip.js +3 -2
  39. package/dist/Tooltip.js.map +1 -1
  40. package/dist/aria.js.map +1 -1
  41. package/dist/hash.js.map +1 -1
  42. package/dist/jss.d.ts +11 -6
  43. package/dist/jss.js +47 -2
  44. package/dist/jss.js.map +1 -1
  45. package/dist/numberToBase.js.map +1 -1
  46. package/dist/stateMachineContextFactory.js.map +1 -1
  47. package/dist/useCookie.js.map +1 -1
  48. package/dist/useKeyboard.js.map +1 -1
  49. package/dist/useNavigation.js.map +1 -1
  50. package/dist/useRefEffect.js.map +1 -1
  51. package/next-env.d.ts +2 -1
  52. package/next.config.js +2 -1
  53. package/package.json +27 -20
  54. package/src/BaseListItem.tsx +1 -1
  55. package/src/Calendar.tsx +2 -2
  56. package/src/Card.tsx +1 -1
  57. package/src/Dialog.tsx +10 -4
  58. package/src/Icon.tsx +4 -30
  59. package/src/Popover.tsx +6 -4
  60. package/src/Providers.tsx +1 -1
  61. package/src/RangeInput.tsx +1 -1
  62. package/src/Storage.tsx +1 -1
  63. package/src/TextInput.tsx +17 -5
  64. package/src/Tooltip.tsx +5 -4
  65. package/src/docs/mdx.tsx +40 -0
  66. package/src/docs/searchIndex.ts +1 -1
  67. package/src/jss.tsx +55 -3
  68. package/src/pages/Badge.mdx +136 -14
  69. package/src/pages/Button.mdx +121 -63
  70. package/src/pages/Calendar.mdx +196 -2
  71. package/src/pages/Card.mdx +123 -47
  72. package/src/pages/Checkbox.mdx +103 -28
  73. package/src/pages/Dialog.mdx +172 -7
  74. package/src/pages/FlexLayout.mdx +185 -51
  75. package/src/pages/Hashing.mdx +199 -8
  76. package/src/pages/Icon.mdx +150 -42
  77. package/src/pages/IconButton.mdx +127 -116
  78. package/src/pages/Link.mdx +118 -47
  79. package/src/pages/List.mdx +167 -283
  80. package/src/pages/ListHeader.mdx +157 -27
  81. package/src/pages/Popover.mdx +177 -55
  82. package/src/pages/ProgressBar.mdx +186 -6
  83. package/src/pages/ProgressCircle.mdx +196 -13
  84. package/src/pages/RangeInput.mdx +189 -61
  85. package/src/pages/Tabs.mdx +202 -346
  86. package/src/pages/Text.mdx +102 -81
  87. package/src/pages/TextInput.mdx +169 -44
  88. package/src/pages/TextPairing.mdx +164 -115
  89. package/src/pages/TimeInput.mdx +142 -37
  90. package/src/pages/Tooltip.mdx +99 -12
  91. package/src/pages/_app.tsx +13 -3
  92. package/src/pages/css-in-js.mdx +196 -83
  93. package/src/pages/navigation.mdx +122 -20
  94. package/src/pages/useCookie.mdx +168 -7
  95. package/src/pages/useKeyboard.mdx +242 -5
  96. package/src/pages/useNavigation.mdx +244 -16
  97. package/src/pages/usePromise.mdx +271 -6
  98. package/src/pages/useRefEffect.mdx +230 -28
  99. package/tsconfig.json +13 -4
@@ -1,15 +1,280 @@
1
1
  # usePromise
2
2
 
3
- This hook can be used to unwrap a promsie value, with an initial value that can be assigned while the promise has not resolved.
3
+ A React hook for unwrapping Promise values into React state. Handles loading, success, and error states automatically.
4
+
5
+ ---
6
+
7
+ ## Signature
8
+
9
+ ```ts
10
+ function usePromise<T>(
11
+ promiseFactory: () => Promise<T>,
12
+ inputs: Array<any>,
13
+ initialValue: T
14
+ ): [T | null, any]
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Parameters
20
+
21
+ | Parameter | Type | Description |
22
+ |-----------|------|-------------|
23
+ | `promiseFactory` | `() => Promise<T>` | Factory function that returns a Promise |
24
+ | `inputs` | `Array<any>` | Dependency array (like useEffect deps) |
25
+ | `initialValue` | `T` | Value to use before the Promise resolves |
26
+
27
+ ---
28
+
29
+ ## Returns
30
+
31
+ A tuple with two elements:
32
+
33
+ | Index | Type | Description |
34
+ |-------|------|-------------|
35
+ | `0` | `T \| null` | The resolved value, or `initialValue` while loading |
36
+ | `1` | `any` | Error if the Promise rejected, otherwise `null` |
37
+
38
+ ---
39
+
40
+ ## Basic usage
41
+
42
+ ```js
43
+ import { usePromise } from "aidos-ui";
44
+
45
+ function UserProfile({ userId }) {
46
+ const [user, error] = usePromise(
47
+ () => fetchUser(userId),
48
+ [userId],
49
+ null
50
+ );
51
+
52
+ if (error) {
53
+ return <ErrorMessage error={error} />;
54
+ }
55
+
56
+ if (!user) {
57
+ return <LoadingSpinner />;
58
+ }
59
+
60
+ return <div>{user.name}</div>;
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## With initial value
67
+
68
+ Provide an initial value to show immediately while loading:
69
+
70
+ ```js
71
+ function Counter() {
72
+ const [count, error] = usePromise(
73
+ () => fetchCurrentCount(),
74
+ [],
75
+ 0 // Show 0 while loading
76
+ );
77
+
78
+ return <span>{count}</span>;
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Dependency-based refetching
85
+
86
+ The Promise re-executes when dependencies change:
87
+
88
+ ```js
89
+ function SearchResults({ query }) {
90
+ const [results, error] = usePromise(
91
+ () => searchAPI(query),
92
+ [query], // Refetch when query changes
93
+ []
94
+ );
95
+
96
+ return (
97
+ <ul>
98
+ {results.map((result) => (
99
+ <li key={result.id}>{result.title}</li>
100
+ ))}
101
+ </ul>
102
+ );
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Error handling
109
+
110
+ ```js
111
+ function DataDisplay() {
112
+ const [data, error] = usePromise(
113
+ () => fetchData(),
114
+ [],
115
+ null
116
+ );
117
+
118
+ if (error) {
119
+ return (
120
+ <Card>
121
+ <Span color="negative">Error: {error.message}</Span>
122
+ <Button onClick={() => window.location.reload()}>
123
+ Retry
124
+ </Button>
125
+ </Card>
126
+ );
127
+ }
128
+
129
+ if (!data) {
130
+ return <Spinner />;
131
+ }
132
+
133
+ return <DataTable data={data} />;
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Multiple async calls
140
+
141
+ Use multiple `usePromise` hooks for parallel data fetching:
4
142
 
5
143
  ```js
6
- function Foo() {
7
- const [result, error] = usePromise(Promise.resolve(10));
144
+ function Dashboard() {
145
+ const [stats, statsError] = usePromise(
146
+ () => fetchStats(),
147
+ [],
148
+ null
149
+ );
150
+
151
+ const [notifications, notifError] = usePromise(
152
+ () => fetchNotifications(),
153
+ [],
154
+ []
155
+ );
156
+
157
+ const [user, userError] = usePromise(
158
+ () => fetchCurrentUser(),
159
+ [],
160
+ null
161
+ );
162
+
163
+ const isLoading = !stats || !user;
164
+ const hasError = statsError || notifError || userError;
165
+
166
+ if (hasError) {
167
+ return <ErrorPage />;
168
+ }
169
+
170
+ if (isLoading) {
171
+ return <LoadingSkeleton />;
172
+ }
8
173
 
9
- if (error != null) {
10
- return <p>{error}</p>;
174
+ return (
175
+ <Column gap="large">
176
+ <StatsWidget stats={stats} />
177
+ <NotificationList notifications={notifications} />
178
+ <UserGreeting user={user} />
179
+ </Column>
180
+ );
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## With typed responses
187
+
188
+ ```ts
189
+ interface User {
190
+ id: string;
191
+ name: string;
192
+ email: string;
193
+ }
194
+
195
+ function UserCard({ userId }: { userId: string }) {
196
+ const [user, error] = usePromise<User | null>(
197
+ () => fetchUser(userId),
198
+ [userId],
199
+ null
200
+ );
201
+
202
+ // user is typed as User | null
203
+ if (user) {
204
+ return <span>{user.name}</span>;
11
205
  }
12
206
 
13
- return <span>{result}</span>;
207
+ return <span>Loading...</span>;
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Conditional fetching
214
+
215
+ Use dependencies to control when fetching occurs:
216
+
217
+ ```js
218
+ function OptionalData({ shouldFetch, id }) {
219
+ const [data, error] = usePromise(
220
+ () => shouldFetch ? fetchData(id) : Promise.resolve(null),
221
+ [shouldFetch, id],
222
+ null
223
+ );
224
+
225
+ // ...
14
226
  }
15
227
  ```
228
+
229
+ ---
230
+
231
+ ## Comparison with other approaches
232
+
233
+ ### vs. plain useEffect
234
+
235
+ ```js
236
+ // ❌ Manual state management
237
+ const [data, setData] = useState(null);
238
+ const [error, setError] = useState(null);
239
+
240
+ useEffect(() => {
241
+ fetchData()
242
+ .then(setData)
243
+ .catch(setError);
244
+ }, []);
245
+
246
+ // ✅ usePromise - cleaner
247
+ const [data, error] = usePromise(() => fetchData(), [], null);
248
+ ```
249
+
250
+ ### vs. React Query / SWR
251
+
252
+ `usePromise` is simpler but lacks:
253
+ - Caching
254
+ - Background refetching
255
+ - Stale-while-revalidate
256
+ - Automatic retries
257
+
258
+ Use `usePromise` for simple cases; consider React Query or SWR for complex data fetching needs.
259
+
260
+ ---
261
+
262
+ ## Best practices
263
+
264
+ 1. **Use meaningful initial values** - Choose values that make sense for your UI (empty array for lists, null for objects)
265
+
266
+ 2. **Handle all states** - Always check for error and loading states
267
+
268
+ 3. **Keep factories stable** - Avoid creating new functions on each render:
269
+
270
+ ```js
271
+ // ❌ Creates new function each render
272
+ const [data] = usePromise(() => fetch(`/api/${id}`), [id], null);
273
+
274
+ // ✅ Factory only changes when id changes
275
+ const [data] = usePromise(
276
+ () => fetchById(id),
277
+ [id],
278
+ null
279
+ );
280
+ ```
@@ -1,42 +1,192 @@
1
1
  # useRefEffect
2
2
 
3
- This effect is useful when you want to run an effect for a component once only (`useEffect` does not reliably do this). This implies that this component cannot have dependencies in the way other hooks do. Other hooks in this library are implemented using this.
3
+ A React hook that reliably runs an effect once when an element is mounted. Unlike `useEffect`, this hook guarantees single execution and proper cleanup, making it ideal for setting up event listeners and DOM manipulations.
4
+
5
+ ---
6
+
7
+ ## Signature
8
+
9
+ ```ts
10
+ function useRefEffect<T>(
11
+ callback: (root: T) => (() => void) | void
12
+ ): (root: T) => void
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Parameters
18
+
19
+ | Parameter | Type | Description |
20
+ |-----------|------|-------------|
21
+ | `callback` | `(root: T) => (() => void) \| void` | Function called when the element mounts. Can return a cleanup function. |
22
+
23
+ ---
24
+
25
+ ## Returns
26
+
27
+ A ref callback function that should be passed to the element's `ref` prop.
28
+
29
+ ---
30
+
31
+ ## Why use this?
32
+
33
+ React's `useEffect` with an empty dependency array doesn't reliably run only once due to:
34
+ - Strict Mode double-invocation in development
35
+ - Concurrent rendering behaviors
36
+ - Fast refresh during development
37
+
38
+ `useRefEffect` solves this by using a ref callback pattern that guarantees:
39
+ - The effect runs exactly once when the element mounts
40
+ - Cleanup runs exactly once when the element unmounts
41
+ - No dependency array needed
42
+
43
+ ---
44
+
45
+ ## Basic usage
46
+
47
+ ```js
48
+ import { useRefEffect } from "aidos-ui";
49
+
50
+ function ResizeObserverComponent() {
51
+ const ref = useRefEffect((element) => {
52
+ const observer = new ResizeObserver((entries) => {
53
+ console.log("Size changed:", entries[0].contentRect);
54
+ });
55
+
56
+ observer.observe(element);
57
+
58
+ // Return cleanup function
59
+ return () => observer.disconnect();
60
+ });
61
+
62
+ return <div ref={ref}>Resize me!</div>;
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Event listeners
69
+
70
+ Perfect for attaching event listeners to elements:
71
+
72
+ ```js
73
+ function DraggableElement() {
74
+ const ref = useRefEffect((element) => {
75
+ let isDragging = false;
76
+ let startX = 0;
77
+ let startY = 0;
78
+
79
+ const onMouseDown = (e) => {
80
+ isDragging = true;
81
+ startX = e.clientX;
82
+ startY = e.clientY;
83
+ };
84
+
85
+ const onMouseMove = (e) => {
86
+ if (!isDragging) return;
87
+ const dx = e.clientX - startX;
88
+ const dy = e.clientY - startY;
89
+ element.style.transform = `translate(${dx}px, ${dy}px)`;
90
+ };
91
+
92
+ const onMouseUp = () => {
93
+ isDragging = false;
94
+ };
95
+
96
+ element.addEventListener("mousedown", onMouseDown);
97
+ window.addEventListener("mousemove", onMouseMove);
98
+ window.addEventListener("mouseup", onMouseUp);
99
+
100
+ return () => {
101
+ element.removeEventListener("mousedown", onMouseDown);
102
+ window.removeEventListener("mousemove", onMouseMove);
103
+ window.removeEventListener("mouseup", onMouseUp);
104
+ };
105
+ });
106
+
107
+ return <div ref={ref}>Drag me</div>;
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## DOM measurements
114
+
115
+ Use for operations that need the actual DOM element:
116
+
117
+ ```js
118
+ function MeasuredElement() {
119
+ const [dimensions, setDimensions] = useState(null);
120
+
121
+ const ref = useRefEffect((element) => {
122
+ const rect = element.getBoundingClientRect();
123
+ setDimensions({
124
+ width: rect.width,
125
+ height: rect.height,
126
+ });
127
+ });
128
+
129
+ return (
130
+ <div ref={ref}>
131
+ {dimensions && (
132
+ <span>
133
+ {dimensions.width}x{dimensions.height}
134
+ </span>
135
+ )}
136
+ </div>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Third-party library integration
144
+
145
+ Initialize libraries that need a DOM element:
146
+
147
+ ```js
148
+ function ChartComponent({ data }) {
149
+ const ref = useRefEffect((element) => {
150
+ const chart = new SomeChartLibrary(element, {
151
+ data,
152
+ width: 400,
153
+ height: 300,
154
+ });
155
+
156
+ return () => chart.destroy();
157
+ });
158
+
159
+ return <div ref={ref} />;
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Without cleanup
166
+
167
+ If no cleanup is needed, simply don't return anything:
4
168
 
5
169
  ```js
6
- import { useRefEffect } from "./useRefEffect";
170
+ const ref = useRefEffect((element) => {
171
+ element.scrollIntoView({ behavior: "smooth" });
172
+ // No cleanup needed
173
+ });
174
+ ```
175
+
176
+ ---
7
177
 
8
- export type KeyboardShortcut<T> = {
9
- key: string;
10
- metaKey?: boolean;
11
- ctrlKey?: boolean;
12
- action: (root: T) => void;
13
- onlyWhenFocused?: boolean;
14
- };
178
+ ## Building other hooks
15
179
 
180
+ `useRefEffect` is the foundation for other hooks in this library:
181
+
182
+ ```js
183
+ // useKeyboard is built on useRefEffect
16
184
  export function useKeyboard<T extends HTMLElement>(
17
185
  shortcuts: Array<KeyboardShortcut<T>>
18
186
  ) {
19
187
  return useRefEffect<T>((root: T) => {
20
188
  const onKeyDown = (e: KeyboardEvent) => {
21
- for (const {
22
- metaKey,
23
- key,
24
- action,
25
- ctrlKey,
26
- onlyWhenFocused,
27
- } of shortcuts) {
28
- if (metaKey && e.metaKey !== true) {
29
- continue;
30
- } else if (ctrlKey && e.ctrlKey !== true) {
31
- continue;
32
- } else if (onlyWhenFocused && document.activeElement !== root) {
33
- continue;
34
- }
35
-
36
- if (e.key.toLowerCase() === key.toLowerCase()) {
37
- action(root);
38
- }
39
- }
189
+ // Handle shortcuts...
40
190
  };
41
191
 
42
192
  window.addEventListener("keydown", onKeyDown);
@@ -47,3 +197,55 @@ export function useKeyboard<T extends HTMLElement>(
47
197
  });
48
198
  }
49
199
  ```
200
+
201
+ ---
202
+
203
+ ## TypeScript usage
204
+
205
+ The hook is generic and preserves element types:
206
+
207
+ ```ts
208
+ // Typed as HTMLInputElement
209
+ const inputRef = useRefEffect<HTMLInputElement>((input) => {
210
+ input.focus();
211
+ input.select();
212
+ });
213
+
214
+ // Typed as HTMLCanvasElement
215
+ const canvasRef = useRefEffect<HTMLCanvasElement>((canvas) => {
216
+ const ctx = canvas.getContext("2d");
217
+ // ctx is properly typed
218
+ });
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Comparison with useEffect
224
+
225
+ ```js
226
+ // ❌ useEffect - may run multiple times in dev/strict mode
227
+ useEffect(() => {
228
+ const element = ref.current;
229
+ if (!element) return;
230
+
231
+ element.addEventListener("click", handler);
232
+ return () => element.removeEventListener("click", handler);
233
+ }, []);
234
+
235
+ // ✅ useRefEffect - runs exactly once
236
+ const ref = useRefEffect((element) => {
237
+ element.addEventListener("click", handler);
238
+ return () => element.removeEventListener("click", handler);
239
+ });
240
+ ```
241
+
242
+ ---
243
+
244
+ ## How it works
245
+
246
+ The hook uses a ref callback combined with a cleanup ref:
247
+
248
+ 1. When the element mounts, the callback receives the element
249
+ 2. The callback's return value (cleanup function) is stored
250
+ 3. When the element unmounts (ref receives `null`), cleanup runs
251
+ 4. The callback is memoized with `useCallback` to prevent re-runs
package/tsconfig.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "lib": ["dom", "dom.iterable", "es2020", "es2020.string"],
3
+ "lib": [
4
+ "dom",
5
+ "dom.iterable",
6
+ "es2020",
7
+ "es2020.string"
8
+ ],
4
9
  "target": "ES2020",
5
10
  "module": "esnext",
6
11
  "sourceMap": true,
7
12
  "outDir": "dist/",
8
- "jsx": "preserve",
13
+ "jsx": "react-jsx",
9
14
  "moduleResolution": "node",
10
15
  "esModuleInterop": true,
11
16
  "declaration": true,
@@ -18,6 +23,10 @@
18
23
  "resolveJsonModule": true,
19
24
  "isolatedModules": true
20
25
  },
21
- "include": ["src/*"],
22
- "exclude": ["node_modules"]
26
+ "include": [
27
+ "src/*"
28
+ ],
29
+ "exclude": [
30
+ "node_modules"
31
+ ]
23
32
  }