|
1 | 1 | import { useQueryErrorResetBoundary } from '@tanstack/react-query' |
2 | 2 | import { type } from 'arktype' |
3 | | -import { Suspense, useRef, useState } from 'react' |
| 3 | +import { Suspense, useMemo, useRef, useState } from 'react' |
4 | 4 | import type { FallbackProps } from 'react-error-boundary' |
5 | 5 | import { ErrorBoundary } from 'react-error-boundary' |
6 | 6 |
|
@@ -136,12 +136,20 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => { |
136 | 136 |
|
137 | 137 | const { archetypes, attributeSchema } = useData() |
138 | 138 |
|
139 | | - const schemaByProperty = new Map(attributeSchema.map((attr) => [attr.property, attr])) |
| 139 | + const schemaByProperty = useMemo( |
| 140 | + () => new Map(attributeSchema.map((attr) => [attr.property, attr])), |
| 141 | + [attributeSchema] |
| 142 | + ) |
| 143 | + |
| 144 | + const overriddenKeys = useMemo(() => { |
| 145 | + const original = originalAttributesRef.current |
140 | 146 |
|
141 | | - const isOverridden = (key: string) => |
142 | | - JSON.stringify(attributes[key]) !== JSON.stringify(originalAttributesRef.current[key]) |
| 147 | + return new Set( |
| 148 | + Object.keys(attributes).filter((key) => JSON.stringify(attributes[key]) !== JSON.stringify(original[key])) |
| 149 | + ) |
| 150 | + }, [attributes]) |
143 | 151 |
|
144 | | - const hasOverrides = Object.keys(attributes).some(isOverridden) |
| 152 | + const hasOverrides = overriddenKeys.size > 0 |
145 | 153 |
|
146 | 154 | const handleFieldChange = (key: string, value: unknown) => { |
147 | 155 | onSave({ ...attributes, [key]: value }) |
@@ -322,9 +330,11 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => { |
322 | 330 | spellCheck={false} |
323 | 331 | /> |
324 | 332 |
|
325 | | - {jsonError !== null && ( |
326 | | - <div className="mt-1 rounded bg-red-500/10 px-3 py-2 text-xs text-red-400">{jsonError}</div> |
327 | | - )} |
| 333 | + <div aria-live="polite" aria-atomic="true" className="mt-1 min-h-0"> |
| 334 | + {jsonError !== null && ( |
| 335 | + <div className="rounded bg-panel-error-bg px-3 py-2 text-xs text-panel-error">{jsonError}</div> |
| 336 | + )} |
| 337 | + </div> |
328 | 338 |
|
329 | 339 | <div className="mt-2 flex justify-end gap-2"> |
330 | 340 | <button |
@@ -365,11 +375,11 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => { |
365 | 375 | </div> |
366 | 376 |
|
367 | 377 | <div className="w-5 shrink-0"> |
368 | | - {isOverridden(key) && ( |
| 378 | + {overriddenKeys.has(key) && ( |
369 | 379 | <button |
370 | 380 | type="button" |
371 | 381 | className="text-override hover:text-override/80" |
372 | | - title="Reset to original" |
| 382 | + aria-label="Reset to original" |
373 | 383 | onClick={() => { |
374 | 384 | handleReset(key) |
375 | 385 | }}> |
@@ -404,8 +414,8 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => { |
404 | 414 | <div className="w-5 shrink-0"> |
405 | 415 | <button |
406 | 416 | type="button" |
407 | | - className="text-red-400 hover:text-red-300" |
408 | | - title="Remove attribute" |
| 417 | + className="text-panel-error hover:text-panel-error/80" |
| 418 | + aria-label="Remove attribute" |
409 | 419 | onClick={() => { |
410 | 420 | handleDeleteCustom(key) |
411 | 421 | }}> |
@@ -471,6 +481,7 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => { |
471 | 481 |
|
472 | 482 | <button |
473 | 483 | type="button" |
| 484 | + aria-label="Cancel" |
474 | 485 | className="text-sm text-panel-text-secondary hover:text-panel-text" |
475 | 486 | onClick={() => { |
476 | 487 | setShowAddForm(false) |
|
0 commit comments