@furystack/shades-common-components 13.4.0 → 13.4.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 (33) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/esm/components/alert.d.ts.map +1 -1
  3. package/esm/components/alert.js +7 -6
  4. package/esm/components/alert.js.map +1 -1
  5. package/esm/components/cache-view.spec.js +171 -170
  6. package/esm/components/cache-view.spec.js.map +1 -1
  7. package/esm/components/command-palette/command-palette-suggestion-list.js +1 -1
  8. package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  9. package/esm/components/inputs/input.d.ts.map +1 -1
  10. package/esm/components/inputs/input.js +2 -0
  11. package/esm/components/inputs/input.js.map +1 -1
  12. package/esm/components/inputs/select.d.ts.map +1 -1
  13. package/esm/components/inputs/select.js +3 -1
  14. package/esm/components/inputs/select.js.map +1 -1
  15. package/esm/components/result.d.ts +4 -2
  16. package/esm/components/result.d.ts.map +1 -1
  17. package/esm/components/result.js +11 -10
  18. package/esm/components/result.js.map +1 -1
  19. package/esm/components/suggest/index.d.ts.map +1 -1
  20. package/esm/components/suggest/index.js +7 -3
  21. package/esm/components/suggest/index.js.map +1 -1
  22. package/esm/components/tree/tree.d.ts.map +1 -1
  23. package/esm/components/tree/tree.js +1 -0
  24. package/esm/components/tree/tree.js.map +1 -1
  25. package/package.json +6 -6
  26. package/src/components/alert.tsx +9 -6
  27. package/src/components/cache-view.spec.tsx +235 -219
  28. package/src/components/command-palette/command-palette-suggestion-list.tsx +1 -1
  29. package/src/components/inputs/input.tsx +2 -0
  30. package/src/components/inputs/select.tsx +3 -1
  31. package/src/components/result.tsx +17 -10
  32. package/src/components/suggest/index.tsx +18 -15
  33. package/src/components/tree/tree.tsx +1 -0
@@ -1,6 +1,7 @@
1
1
  import { Cache } from '@furystack/cache'
2
2
  import type { CacheResult, CacheWithValue } from '@furystack/cache'
3
3
  import { Shade, createComponent, flushUpdates } from '@furystack/shades'
4
+ import { using, usingAsync } from '@furystack/utils'
4
5
 
5
6
  import { describe, expect, it, vi } from 'vitest'
6
7
  import { CacheView } from './cache-view.js'
@@ -45,232 +46,249 @@ describe('CacheView', () => {
45
46
  })
46
47
 
47
48
  it('should create a cache-view element', () => {
48
- const cache = new Cache<string, [string]>({ load: async (key) => key })
49
- const el = (<CacheView cache={cache} args={['test']} content={TestContent} />) as unknown as HTMLElement
50
- expect(el).toBeDefined()
51
- expect(el.tagName?.toLowerCase()).toBe('shade-cache-view')
52
- cache[Symbol.dispose]()
49
+ using(new Cache<string, [string]>({ load: async (key) => key }), (cache) => {
50
+ const el = (<CacheView cache={cache} args={['test']} content={TestContent} />) as unknown as HTMLElement
51
+ expect(el).toBeDefined()
52
+ expect(el.tagName?.toLowerCase()).toBe('shade-cache-view')
53
+ })
53
54
  })
54
55
 
55
56
  describe('loading state', () => {
56
57
  it('should render null by default when loading', async () => {
57
- const cache = new Cache<string, [string]>({
58
- load: () => new Promise(() => {}),
59
- })
60
- const { cacheView } = await renderCacheView(cache, ['test'])
61
- expect(cacheView.querySelector('test-cache-content')).toBeNull()
62
- expect(cacheView.querySelector('shade-result')).toBeNull()
63
- cache[Symbol.dispose]()
58
+ await usingAsync(
59
+ new Cache<string, [string]>({
60
+ load: () => new Promise(() => {}),
61
+ }),
62
+ async (cache) => {
63
+ const { cacheView } = await renderCacheView(cache, ['test'])
64
+ expect(cacheView.querySelector('test-cache-content')).toBeNull()
65
+ expect(cacheView.querySelector('shade-result')).toBeNull()
66
+ },
67
+ )
64
68
  })
65
69
 
66
70
  it('should render custom loader when provided', async () => {
67
- const cache = new Cache<string, [string]>({
68
- load: () => new Promise(() => {}),
69
- })
70
- const { cacheView } = await renderCacheView(cache, ['test'], {
71
- loader: (<span className="custom-loader">Loading...</span>) as unknown as JSX.Element,
72
- })
73
- const loader = cacheView.querySelector('.custom-loader')
74
- expect(loader).not.toBeNull()
75
- expect(loader?.textContent).toBe('Loading...')
76
- cache[Symbol.dispose]()
71
+ await usingAsync(
72
+ new Cache<string, [string]>({
73
+ load: () => new Promise(() => {}),
74
+ }),
75
+ async (cache) => {
76
+ const { cacheView } = await renderCacheView(cache, ['test'], {
77
+ loader: (<span className="custom-loader">Loading...</span>) as unknown as JSX.Element,
78
+ })
79
+ const loader = cacheView.querySelector('.custom-loader')
80
+ expect(loader).not.toBeNull()
81
+ expect(loader?.textContent).toBe('Loading...')
82
+ },
83
+ )
77
84
  })
78
85
  })
79
86
 
80
87
  describe('loaded state', () => {
81
88
  it('should render content when cache has loaded value', async () => {
82
- const cache = new Cache<string, [string]>({ load: async (key) => `Hello ${key}` })
83
- await cache.get('world')
84
- const { cacheView } = await renderCacheView(cache, ['world'])
85
- const contentEl = cacheView.querySelector('test-cache-content')
86
- expect(contentEl).not.toBeNull()
87
- const contentComponent = contentEl as JSX.Element
88
- contentComponent.updateComponent()
89
- await flushUpdates()
90
- const valueEl = contentComponent.querySelector('.content-value')
91
- expect(valueEl?.textContent).toBe('Hello world')
92
- cache[Symbol.dispose]()
89
+ await usingAsync(new Cache<string, [string]>({ load: async (key) => `Hello ${key}` }), async (cache) => {
90
+ await cache.get('world')
91
+ const { cacheView } = await renderCacheView(cache, ['world'])
92
+ const contentEl = cacheView.querySelector('test-cache-content')
93
+ expect(contentEl).not.toBeNull()
94
+ const contentComponent = contentEl as JSX.Element
95
+ contentComponent.updateComponent()
96
+ await flushUpdates()
97
+ const valueEl = contentComponent.querySelector('.content-value')
98
+ expect(valueEl?.textContent).toBe('Hello world')
99
+ })
93
100
  })
94
101
  })
95
102
 
96
103
  describe('failed state', () => {
97
104
  it('should render default error UI when cache has failed', async () => {
98
- const cache = new Cache<string, [string]>({
99
- load: async () => {
100
- throw new Error('Test failure')
105
+ await usingAsync(
106
+ new Cache<string, [string]>({
107
+ load: async () => {
108
+ throw new Error('Test failure')
109
+ },
110
+ }),
111
+ async (cache) => {
112
+ try {
113
+ await cache.get('test')
114
+ } catch {
115
+ // expected
116
+ }
117
+ const { cacheView } = await renderCacheView(cache, ['test'])
118
+ const resultEl = cacheView.querySelector('shade-result')
119
+ expect(resultEl).not.toBeNull()
120
+ const resultComponent = resultEl as JSX.Element
121
+ resultComponent.updateComponent()
122
+ await flushUpdates()
123
+ const titleEl = resultComponent.querySelector('.result-title') as JSX.Element
124
+ titleEl.updateComponent()
125
+ await flushUpdates()
126
+ expect(titleEl?.textContent).toBe('Something went wrong')
101
127
  },
102
- })
103
- try {
104
- await cache.get('test')
105
- } catch {
106
- // expected
107
- }
108
- const { cacheView } = await renderCacheView(cache, ['test'])
109
- const resultEl = cacheView.querySelector('shade-result')
110
- expect(resultEl).not.toBeNull()
111
- const resultComponent = resultEl as JSX.Element
112
- resultComponent.updateComponent()
113
- await flushUpdates()
114
- const titleEl = resultComponent.querySelector('.result-title') as JSX.Element
115
- titleEl.updateComponent()
116
- await flushUpdates()
117
- expect(titleEl?.textContent).toBe('Something went wrong')
118
- cache[Symbol.dispose]()
128
+ )
119
129
  })
120
130
 
121
131
  it('should render custom error UI when error prop is provided', async () => {
122
- const cache = new Cache<string, [string]>({
123
- load: async () => {
124
- throw new Error('Custom failure')
132
+ await usingAsync(
133
+ new Cache<string, [string]>({
134
+ load: async () => {
135
+ throw new Error('Custom failure')
136
+ },
137
+ }),
138
+ async (cache) => {
139
+ try {
140
+ await cache.get('test')
141
+ } catch {
142
+ // expected
143
+ }
144
+ const customError = vi.fn(
145
+ (err: unknown, _retry: () => void) =>
146
+ (<span className="custom-error">{String(err)}</span>) as unknown as JSX.Element,
147
+ )
148
+ const { cacheView } = await renderCacheView(cache, ['test'], { error: customError })
149
+ expect(customError).toHaveBeenCalledOnce()
150
+ expect(customError.mock.calls[0][0]).toBeInstanceOf(Error)
151
+ const customErrorEl = cacheView.querySelector('.custom-error')
152
+ expect(customErrorEl).not.toBeNull()
125
153
  },
126
- })
127
- try {
128
- await cache.get('test')
129
- } catch {
130
- // expected
131
- }
132
- const customError = vi.fn(
133
- (err: unknown, _retry: () => void) =>
134
- (<span className="custom-error">{String(err)}</span>) as unknown as JSX.Element,
135
154
  )
136
- const { cacheView } = await renderCacheView(cache, ['test'], { error: customError })
137
- expect(customError).toHaveBeenCalledOnce()
138
- expect(customError.mock.calls[0][0]).toBeInstanceOf(Error)
139
- const customErrorEl = cacheView.querySelector('.custom-error')
140
- expect(customErrorEl).not.toBeNull()
141
- cache[Symbol.dispose]()
142
155
  })
143
156
 
144
157
  it('should not render content when failed even if stale value exists', async () => {
145
- const cache = new Cache<string, [string]>({ load: async (key) => key })
146
- await cache.get('test')
147
- cache.setExplicitValue({
148
- loadArgs: ['test'],
149
- value: { status: 'failed', error: new Error('fail'), value: 'stale', updatedAt: new Date() },
158
+ await usingAsync(new Cache<string, [string]>({ load: async (key) => key }), async (cache) => {
159
+ await cache.get('test')
160
+ cache.setExplicitValue({
161
+ loadArgs: ['test'],
162
+ value: { status: 'failed', error: new Error('fail'), value: 'stale', updatedAt: new Date() },
163
+ })
164
+ const { cacheView } = await renderCacheView(cache, ['test'])
165
+ expect(cacheView.querySelector('test-cache-content')).toBeNull()
166
+ expect(cacheView.querySelector('shade-result')).not.toBeNull()
150
167
  })
151
- const { cacheView } = await renderCacheView(cache, ['test'])
152
- expect(cacheView.querySelector('test-cache-content')).toBeNull()
153
- expect(cacheView.querySelector('shade-result')).not.toBeNull()
154
- cache[Symbol.dispose]()
155
168
  })
156
169
 
157
170
  it('should call cache.reload when retry is invoked', async () => {
158
171
  const loadFn = vi.fn<(key: string) => Promise<string>>(async () => {
159
172
  throw new Error('fail')
160
173
  })
161
- const cache = new Cache<string, [string]>({ load: loadFn })
162
- try {
163
- await cache.get('test')
164
- } catch {
165
- // expected
166
- }
167
- let capturedRetry: (() => void) | undefined
168
- const customError = (_err: unknown, retry: () => void) => {
169
- capturedRetry = retry
170
- return (<span className="custom-error">Error</span>) as unknown as JSX.Element
171
- }
172
- await renderCacheView(cache, ['test'], { error: customError })
173
- expect(capturedRetry).toBeDefined()
174
-
175
- loadFn.mockResolvedValueOnce('recovered')
176
- capturedRetry!()
177
- await flushUpdates()
178
-
179
- const observable = cache.getObservable('test')
180
- const result = observable.getValue()
181
- expect(result.status).toBe('loaded')
182
- expect(result.value).toBe('recovered')
183
- cache[Symbol.dispose]()
174
+ await usingAsync(new Cache<string, [string]>({ load: loadFn }), async (cache) => {
175
+ try {
176
+ await cache.get('test')
177
+ } catch {
178
+ // expected
179
+ }
180
+ let capturedRetry: (() => void) | undefined
181
+ const customError = (_err: unknown, retry: () => void) => {
182
+ capturedRetry = retry
183
+ return (<span className="custom-error">Error</span>) as unknown as JSX.Element
184
+ }
185
+ await renderCacheView(cache, ['test'], { error: customError })
186
+ expect(capturedRetry).toBeDefined()
187
+
188
+ loadFn.mockResolvedValueOnce('recovered')
189
+ capturedRetry!()
190
+ await flushUpdates()
191
+
192
+ const observable = cache.getObservable('test')
193
+ const result = observable.getValue()
194
+ expect(result.status).toBe('loaded')
195
+ expect(result.value).toBe('recovered')
196
+ })
184
197
  })
185
198
  })
186
199
 
187
200
  describe('obsolete state', () => {
188
201
  it('should render content when obsolete and trigger reload', async () => {
189
202
  const loadFn = vi.fn(async (key: string) => `Hello ${key}`)
190
- const cache = new Cache<string, [string]>({ load: loadFn })
191
- await cache.get('test')
192
- cache.setObsolete('test')
193
-
194
- const { cacheView } = await renderCacheView(cache, ['test'])
195
- const contentEl = cacheView.querySelector('test-cache-content')
196
- expect(contentEl).not.toBeNull()
197
-
198
- await flushUpdates()
199
- // reload should have been called (initial load + obsolete reload)
200
- expect(loadFn).toHaveBeenCalledTimes(2)
201
- cache[Symbol.dispose]()
203
+ await usingAsync(new Cache<string, [string]>({ load: loadFn }), async (cache) => {
204
+ await cache.get('test')
205
+ cache.setObsolete('test')
206
+
207
+ const { cacheView } = await renderCacheView(cache, ['test'])
208
+ const contentEl = cacheView.querySelector('test-cache-content')
209
+ expect(contentEl).not.toBeNull()
210
+
211
+ await flushUpdates()
212
+ // reload should have been called (initial load + obsolete reload)
213
+ expect(loadFn).toHaveBeenCalledTimes(2)
214
+ })
202
215
  })
203
216
  })
204
217
 
205
218
  describe('error takes priority over value', () => {
206
219
  it('should show error when failed with value, not content', async () => {
207
- const cache = new Cache<string, [string]>({ load: async (key) => key })
208
- await cache.get('test')
209
- const failedWithValue: CacheResult<string> = {
210
- status: 'failed',
211
- error: new Error('whoops'),
212
- value: 'stale-data',
213
- updatedAt: new Date(),
214
- }
215
- cache.setExplicitValue({ loadArgs: ['test'], value: failedWithValue })
216
- const { cacheView } = await renderCacheView(cache, ['test'])
217
- expect(cacheView.querySelector('test-cache-content')).toBeNull()
218
- expect(cacheView.querySelector('shade-result')).not.toBeNull()
219
- cache[Symbol.dispose]()
220
+ await usingAsync(new Cache<string, [string]>({ load: async (key) => key }), async (cache) => {
221
+ await cache.get('test')
222
+ const failedWithValue: CacheResult<string> = {
223
+ status: 'failed',
224
+ error: new Error('whoops'),
225
+ value: 'stale-data',
226
+ updatedAt: new Date(),
227
+ }
228
+ cache.setExplicitValue({ loadArgs: ['test'], value: failedWithValue })
229
+ const { cacheView } = await renderCacheView(cache, ['test'])
230
+ expect(cacheView.querySelector('test-cache-content')).toBeNull()
231
+ expect(cacheView.querySelector('shade-result')).not.toBeNull()
232
+ })
220
233
  })
221
234
  })
222
235
 
223
236
  describe('contentProps', () => {
224
237
  it('should forward contentProps to the content component', async () => {
225
- const cache = new Cache<string, [string]>({ load: async (key) => `Hello ${key}` })
226
- await cache.get('world')
227
-
228
- const el = (
229
- <div>
230
- <CacheView
231
- cache={cache}
232
- args={['world']}
233
- content={TestContentWithLabel}
234
- contentProps={{ label: 'Greeting' }}
235
- />
236
- </div>
237
- )
238
- const cacheView = el.firstElementChild as JSX.Element
239
- cacheView.updateComponent()
240
- await flushUpdates()
241
-
242
- const contentEl = cacheView.querySelector('test-cache-content-with-label') as JSX.Element
243
- expect(contentEl).not.toBeNull()
244
- contentEl.updateComponent()
245
- await flushUpdates()
246
- const valueEl = contentEl.querySelector('.content-value')
247
- expect(valueEl?.textContent).toBe('Greeting: Hello world')
248
- cache[Symbol.dispose]()
238
+ await usingAsync(new Cache<string, [string]>({ load: async (key) => `Hello ${key}` }), async (cache) => {
239
+ await cache.get('world')
240
+
241
+ const el = (
242
+ <div>
243
+ <CacheView
244
+ cache={cache}
245
+ args={['world']}
246
+ content={TestContentWithLabel}
247
+ contentProps={{ label: 'Greeting' }}
248
+ />
249
+ </div>
250
+ )
251
+ const cacheView = el.firstElementChild as JSX.Element
252
+ cacheView.updateComponent()
253
+ await flushUpdates()
254
+
255
+ const contentEl = cacheView.querySelector('test-cache-content-with-label') as JSX.Element
256
+ expect(contentEl).not.toBeNull()
257
+ contentEl.updateComponent()
258
+ await flushUpdates()
259
+ const valueEl = contentEl.querySelector('.content-value')
260
+ expect(valueEl?.textContent).toBe('Greeting: Hello world')
261
+ })
249
262
  })
250
263
 
251
264
  it('should forward contentProps when cache entry is obsolete', async () => {
252
265
  const loadFn = vi.fn(async (key: string) => `Hello ${key}`)
253
- const cache = new Cache<string, [string]>({ load: loadFn })
254
- await cache.get('world')
255
- cache.setObsolete('world')
256
-
257
- const el = (
258
- <div>
259
- <CacheView cache={cache} args={['world']} content={TestContentWithLabel} contentProps={{ label: 'Stale' }} />
260
- </div>
261
- )
262
- const cacheView = el.firstElementChild as JSX.Element
263
- cacheView.updateComponent()
264
- await flushUpdates()
265
-
266
- const contentEl = cacheView.querySelector('test-cache-content-with-label') as JSX.Element
267
- expect(contentEl).not.toBeNull()
268
- contentEl.updateComponent()
269
- await flushUpdates()
270
- const valueEl = contentEl.querySelector('.content-value')
271
- expect(valueEl?.textContent).toBe('Stale: Hello world')
272
- expect(loadFn).toHaveBeenCalledTimes(2)
273
- cache[Symbol.dispose]()
266
+ await usingAsync(new Cache<string, [string]>({ load: loadFn }), async (cache) => {
267
+ await cache.get('world')
268
+ cache.setObsolete('world')
269
+
270
+ const el = (
271
+ <div>
272
+ <CacheView
273
+ cache={cache}
274
+ args={['world']}
275
+ content={TestContentWithLabel}
276
+ contentProps={{ label: 'Stale' }}
277
+ />
278
+ </div>
279
+ )
280
+ const cacheView = el.firstElementChild as JSX.Element
281
+ cacheView.updateComponent()
282
+ await flushUpdates()
283
+
284
+ const contentEl = cacheView.querySelector('test-cache-content-with-label') as JSX.Element
285
+ expect(contentEl).not.toBeNull()
286
+ contentEl.updateComponent()
287
+ await flushUpdates()
288
+ const valueEl = contentEl.querySelector('.content-value')
289
+ expect(valueEl?.textContent).toBe('Stale: Hello world')
290
+ expect(loadFn).toHaveBeenCalledTimes(2)
291
+ })
274
292
  })
275
293
  })
276
294
 
@@ -292,61 +310,59 @@ describe('CacheView', () => {
292
310
 
293
311
  it('should call startViewTransition when viewTransition is enabled and cache state category changes', async () => {
294
312
  const spy = mockStartViewTransition()
295
- const cache = new Cache<string, [string]>({ load: async (key) => `loaded-${key}` })
296
-
297
- const el = (
298
- <div>
299
- <CacheView
300
- cache={cache}
301
- args={['test']}
302
- content={TestContent}
303
- loader={<span className="loader">Loading</span>}
304
- viewTransition
305
- />
306
- </div>
307
- )
308
- const cacheView = el.firstElementChild as JSX.Element
309
- cacheView.updateComponent()
310
- await flushUpdates()
311
-
312
- expect(cacheView.querySelector('.loader')).toBeTruthy()
313
- spy.mockClear()
313
+ await usingAsync(new Cache<string, [string]>({ load: async (key) => `loaded-${key}` }), async (cache) => {
314
+ const el = (
315
+ <div>
316
+ <CacheView
317
+ cache={cache}
318
+ args={['test']}
319
+ content={TestContent}
320
+ loader={<span className="loader">Loading</span>}
321
+ viewTransition
322
+ />
323
+ </div>
324
+ )
325
+ const cacheView = el.firstElementChild as JSX.Element
326
+ cacheView.updateComponent()
327
+ await flushUpdates()
328
+
329
+ expect(cacheView.querySelector('.loader')).toBeTruthy()
330
+ spy.mockClear()
314
331
 
315
- await cache.get('test')
316
- cacheView.updateComponent()
317
- await flushUpdates()
332
+ await cache.get('test')
333
+ cacheView.updateComponent()
334
+ await flushUpdates()
318
335
 
319
- expect(spy).toHaveBeenCalled()
320
- cache[Symbol.dispose]()
336
+ expect(spy).toHaveBeenCalled()
337
+ })
321
338
  delete (document as unknown as Record<string, unknown>).startViewTransition
322
339
  })
323
340
 
324
341
  it('should not call startViewTransition when viewTransition is not set', async () => {
325
342
  const spy = mockStartViewTransition()
326
- const cache = new Cache<string, [string]>({ load: async (key) => `loaded-${key}` })
327
-
328
- const el = (
329
- <div>
330
- <CacheView
331
- cache={cache}
332
- args={['test']}
333
- content={TestContent}
334
- loader={<span className="loader">Loading</span>}
335
- />
336
- </div>
337
- )
338
- const cacheView = el.firstElementChild as JSX.Element
339
- cacheView.updateComponent()
340
- await flushUpdates()
341
-
342
- spy.mockClear()
343
+ await usingAsync(new Cache<string, [string]>({ load: async (key) => `loaded-${key}` }), async (cache) => {
344
+ const el = (
345
+ <div>
346
+ <CacheView
347
+ cache={cache}
348
+ args={['test']}
349
+ content={TestContent}
350
+ loader={<span className="loader">Loading</span>}
351
+ />
352
+ </div>
353
+ )
354
+ const cacheView = el.firstElementChild as JSX.Element
355
+ cacheView.updateComponent()
356
+ await flushUpdates()
357
+
358
+ spy.mockClear()
343
359
 
344
- await cache.get('test')
345
- cacheView.updateComponent()
346
- await flushUpdates()
360
+ await cache.get('test')
361
+ cacheView.updateComponent()
362
+ await flushUpdates()
347
363
 
348
- expect(spy).not.toHaveBeenCalled()
349
- cache[Symbol.dispose]()
364
+ expect(spy).not.toHaveBeenCalled()
365
+ })
350
366
  delete (document as unknown as Record<string, unknown>).startViewTransition
351
367
  })
352
368
  })
@@ -95,7 +95,7 @@ export const CommandPaletteSuggestionList = Shade<{ manager: CommandPaletteManag
95
95
  ref={containerRef}
96
96
  className="suggestion-items-container"
97
97
  style={{
98
- opacity: manager.isOpened.getValue() ? '1' : '0',
98
+ opacity: isOpenedAtRender ? '1' : '0',
99
99
  maxHeight: `${window.innerHeight * 0.8}px`,
100
100
  width: `calc(${Math.round(hostParentWidth)}px - 3em)`,
101
101
  ...(props.fullScreenSuggestions ? { left: '0', width: 'calc(100% - 42px)' } : {}),
@@ -243,6 +243,8 @@ export const Input = Shade<TextInputProps>({
243
243
 
244
244
  const themeProvider = injector.getInstance(ThemeProviderService)
245
245
 
246
+ // We want to use the CSS state hooks for the focused and validity states, so we need to disable the rule
247
+ // eslint-disable-next-line furystack/no-css-state-hooks
246
248
  const [focused, setFocused] = useState('focused', props.autofocus || false)
247
249
  const [validity, setValidity] = useState('validity', inputRef.current?.validity || emptyValidity)
248
250
 
@@ -4,8 +4,8 @@ import { cssVariableTheme } from '../../services/css-variable-theme.js'
4
4
  import type { Palette } from '../../services/theme-provider-service.js'
5
5
  import { ThemeProviderService } from '../../services/theme-provider-service.js'
6
6
  import { FormService } from '../form.js'
7
- import { Icon } from '../icons/icon.js'
8
7
  import { check, close } from '../icons/icon-definitions.js'
8
+ import { Icon } from '../icons/icon.js'
9
9
  import type { InputValidationResult } from './input.js'
10
10
 
11
11
  export type SelectOption = {
@@ -384,6 +384,8 @@ export const Select = Shade<SelectProps>({
384
384
  }
385
385
 
386
386
  const [state, setState] = useState<SelectState>('selectState', initialState)
387
+ // We want to use the CSS state hooks for the focused and dropdown direction states, so we need to disable the rule
388
+ // eslint-disable-next-line furystack/no-css-state-hooks
387
389
  const [isFocused, setIsFocused] = useState('isFocused', false)
388
390
  const [dropdownDirection, setDropdownDirection] = useState<'up' | 'down'>('dropdownDirection', 'down')
389
391
 
@@ -40,16 +40,19 @@ const statusColorMap: Record<ResultStatus, string> = {
40
40
  '500': paletteMainColors.error.main,
41
41
  }
42
42
 
43
- const defaultIcons: Record<ResultStatus, JSX.Element> = {
44
- success: (<Icon icon={checkCircle} size={64} />) as unknown as JSX.Element,
45
- error: (<Icon icon={errorCircle} size={64} />) as unknown as JSX.Element,
46
- warning: (<Icon icon={warningIcon} size={64} />) as unknown as JSX.Element,
47
- info: (<Icon icon={infoIcon} size={64} />) as unknown as JSX.Element,
48
- '403': (<Icon icon={forbidden} size={64} />) as unknown as JSX.Element,
49
- '404': (<Icon icon={searchOff} size={64} />) as unknown as JSX.Element,
50
- '500': (<Icon icon={serverError} size={64} />) as unknown as JSX.Element,
43
+ const defaultIconDefs: Record<ResultStatus, typeof checkCircle> = {
44
+ success: checkCircle,
45
+ error: errorCircle,
46
+ warning: warningIcon,
47
+ info: infoIcon,
48
+ '403': forbidden,
49
+ '404': searchOff,
50
+ '500': serverError,
51
51
  }
52
52
 
53
+ const getDefaultIcon = (status: ResultStatus): JSX.Element =>
54
+ (<Icon icon={defaultIconDefs[status]} size={64} />) as unknown as JSX.Element
55
+
53
56
  const defaultTitles: Record<ResultStatus, string> = {
54
57
  success: 'Success',
55
58
  error: 'Error',
@@ -114,7 +117,7 @@ export const Result = Shade<ResultProps>({
114
117
  render: ({ props, children, useHostProps }) => {
115
118
  const { status, title, subtitle, icon, style } = props
116
119
 
117
- const displayIcon = icon ?? defaultIcons[status]
120
+ const displayIcon = icon ?? getDefaultIcon(status)
118
121
  const statusColor = statusColorMap[status]
119
122
 
120
123
  useHostProps({
@@ -146,4 +149,8 @@ export const Result = Shade<ResultProps>({
146
149
  },
147
150
  })
148
151
 
149
- export { defaultIcons as resultDefaultIcons, defaultTitles as resultDefaultTitles }
152
+ export {
153
+ getDefaultIcon as resultGetDefaultIcon,
154
+ defaultIconDefs as resultDefaultIconDefs,
155
+ defaultTitles as resultDefaultTitles,
156
+ }