@nixxie-cms/core 2.1.0 → 2.2.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 (126) hide show
  1. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +7 -7
  2. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +7 -7
  3. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
  4. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
  5. package/admin-ui/utils/dist/nixxie-cms-core-admin-ui-utils.cjs.js +5 -5
  6. package/admin-ui/utils/dist/nixxie-cms-core-admin-ui-utils.esm.js +3 -3
  7. package/dist/{CreateItemDialog-7008b050.esm.js → CreateItemDialog-66621fe8.esm.js} +3 -3
  8. package/dist/{CreateItemDialog-a0cab315.cjs.js → CreateItemDialog-96b044ce.cjs.js} +4 -4
  9. package/dist/{Field-47f85161.esm.js → Field-1820c4e6.esm.js} +1 -0
  10. package/dist/{Field-ed8d7627.cjs.js → Field-38d3cdf9.cjs.js} +1 -0
  11. package/dist/GraphQLErrorNotice-7594a9f8.esm.js +64 -0
  12. package/dist/GraphQLErrorNotice-c8890f80.cjs.js +66 -0
  13. package/dist/{PageContainer-5ae731cc.esm.js → PageContainer-355cfbfa.esm.js} +362 -156
  14. package/dist/{PageContainer-abd7159f.cjs.js → PageContainer-4095555a.cjs.js} +361 -155
  15. package/dist/{context-af9957ed.esm.js → context-2924eaaa.esm.js} +53 -58
  16. package/dist/{context-b5204629.cjs.js → context-2ce61d0b.cjs.js} +53 -58
  17. package/dist/declarations/src/admin-ui/components/GraphQLErrorNotice.d.ts.map +1 -1
  18. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  19. package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
  20. package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
  21. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
  22. package/dist/declarations/src/fields/types/bigInt/views/index.d.ts +2 -1
  23. package/dist/declarations/src/fields/types/bigInt/views/index.d.ts.map +1 -1
  24. package/dist/declarations/src/fields/types/bytes/views/index.d.ts +2 -1
  25. package/dist/declarations/src/fields/types/bytes/views/index.d.ts.map +1 -1
  26. package/dist/declarations/src/fields/types/calendarDay/views/index.d.ts.map +1 -1
  27. package/dist/declarations/src/fields/types/decimal/views/index.d.ts +2 -1
  28. package/dist/declarations/src/fields/types/decimal/views/index.d.ts.map +1 -1
  29. package/dist/declarations/src/fields/types/float/views/index.d.ts +2 -1
  30. package/dist/declarations/src/fields/types/float/views/index.d.ts.map +1 -1
  31. package/dist/declarations/src/fields/types/integer/views/index.d.ts +2 -1
  32. package/dist/declarations/src/fields/types/integer/views/index.d.ts.map +1 -1
  33. package/dist/declarations/src/fields/types/json/views/index.d.ts.map +1 -1
  34. package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
  35. package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
  36. package/dist/declarations/src/fields/types/select/views/index.d.ts.map +1 -1
  37. package/dist/declarations/src/fields/types/text/views/index.d.ts +2 -1
  38. package/dist/declarations/src/fields/types/text/views/index.d.ts.map +1 -1
  39. package/dist/declarations/src/fields/types/timestamp/views/index.d.ts.map +1 -1
  40. package/dist/declarations/src/fields/types/virtual/views/index.d.ts.map +1 -1
  41. package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -1
  42. package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -1
  43. package/dist/pick-4c785a54.esm.js +34 -0
  44. package/dist/pick-906341bb.cjs.js +37 -0
  45. package/dist/{useCreateItem-1f94d252.esm.js → useCreateItem-36a75f1c.esm.js} +26 -26
  46. package/dist/{useCreateItem-1be4987e.cjs.js → useCreateItem-acf06f77.cjs.js} +37 -37
  47. package/dist/{useFilter-acc9d413.cjs.js → useFilter-c29f17a8.cjs.js} +1 -1
  48. package/dist/{useFilter-9b6db1f9.esm.js → useFilter-f79b2abb.esm.js} +1 -1
  49. package/dist/{Fields-956d9a14.esm.js → usePreventNavigation-093389dd.esm.js} +28 -2
  50. package/dist/{Fields-e2c28056.cjs.js → usePreventNavigation-d4f9f4fa.cjs.js} +27 -0
  51. package/fields/types/bigInt/views/dist/nixxie-cms-core-fields-types-bigInt-views.cjs.js +8 -0
  52. package/fields/types/bigInt/views/dist/nixxie-cms-core-fields-types-bigInt-views.esm.js +8 -1
  53. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +14 -3
  54. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +15 -5
  55. package/fields/types/calendarDay/views/dist/nixxie-cms-core-fields-types-calendarDay-views.cjs.js +2 -1
  56. package/fields/types/calendarDay/views/dist/nixxie-cms-core-fields-types-calendarDay-views.esm.js +2 -1
  57. package/fields/types/decimal/views/dist/nixxie-cms-core-fields-types-decimal-views.cjs.js +10 -1
  58. package/fields/types/decimal/views/dist/nixxie-cms-core-fields-types-decimal-views.esm.js +10 -2
  59. package/fields/types/file/views/dist/nixxie-cms-core-fields-types-file-views.cjs.js +1 -1
  60. package/fields/types/file/views/dist/nixxie-cms-core-fields-types-file-views.esm.js +2 -2
  61. package/fields/types/float/views/dist/nixxie-cms-core-fields-types-float-views.cjs.js +10 -1
  62. package/fields/types/float/views/dist/nixxie-cms-core-fields-types-float-views.esm.js +10 -2
  63. package/fields/types/image/views/dist/nixxie-cms-core-fields-types-image-views.cjs.js +2 -1
  64. package/fields/types/image/views/dist/nixxie-cms-core-fields-types-image-views.esm.js +2 -1
  65. package/fields/types/integer/views/dist/nixxie-cms-core-fields-types-integer-views.cjs.js +8 -0
  66. package/fields/types/integer/views/dist/nixxie-cms-core-fields-types-integer-views.esm.js +8 -1
  67. package/fields/types/json/views/dist/nixxie-cms-core-fields-types-json-views.cjs.js +19 -4
  68. package/fields/types/json/views/dist/nixxie-cms-core-fields-types-json-views.esm.js +19 -4
  69. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +18 -3
  70. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +18 -3
  71. package/fields/types/password/views/dist/nixxie-cms-core-fields-types-password-views.cjs.js +1 -1
  72. package/fields/types/password/views/dist/nixxie-cms-core-fields-types-password-views.esm.js +1 -1
  73. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +9 -7
  74. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +9 -7
  75. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +3 -2
  76. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +3 -2
  77. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +14 -3
  78. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +15 -5
  79. package/fields/types/timestamp/views/dist/nixxie-cms-core-fields-types-timestamp-views.cjs.js +2 -1
  80. package/fields/types/timestamp/views/dist/nixxie-cms-core-fields-types-timestamp-views.esm.js +2 -1
  81. package/fields/types/virtual/views/dist/nixxie-cms-core-fields-types-virtual-views.cjs.js +13 -1
  82. package/fields/types/virtual/views/dist/nixxie-cms-core-fields-types-virtual-views.esm.js +14 -2
  83. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +3 -3
  84. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +3 -3
  85. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +7 -7
  86. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +6 -6
  87. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +34 -33
  88. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +35 -34
  89. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +53 -13
  90. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +52 -12
  91. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +36 -25
  92. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +36 -25
  93. package/package.json +2 -2
  94. package/src/admin-ui/components/CommandPalette.tsx +134 -27
  95. package/src/admin-ui/components/CreateButtonLink.tsx +20 -46
  96. package/src/admin-ui/components/GraphQLErrorNotice.tsx +39 -33
  97. package/src/admin-ui/components/Logo.tsx +5 -5
  98. package/src/admin-ui/components/Navigation.tsx +41 -27
  99. package/src/admin-ui/components/PageContainer.tsx +171 -15
  100. package/src/admin-ui/components/WelcomeDialog.tsx +14 -14
  101. package/src/admin-ui/context.tsx +5 -2
  102. package/src/admin-ui/utils/useCreateItem.ts +21 -1
  103. package/src/fields/types/bigInt/views/index.tsx +10 -1
  104. package/src/fields/types/bytes/views/index.tsx +14 -1
  105. package/src/fields/types/calendarDay/views/index.tsx +2 -1
  106. package/src/fields/types/decimal/views/index.tsx +7 -1
  107. package/src/fields/types/file/views/Field.tsx +1 -1
  108. package/src/fields/types/float/views/index.tsx +7 -1
  109. package/src/fields/types/image/views/index.tsx +1 -1
  110. package/src/fields/types/integer/views/index.tsx +5 -0
  111. package/src/fields/types/json/views/index.tsx +20 -2
  112. package/src/fields/types/multiselect/views/index.tsx +7 -3
  113. package/src/fields/types/password/views/index.tsx +1 -1
  114. package/src/fields/types/relationship/views/index.tsx +1 -0
  115. package/src/fields/types/select/views/index.tsx +2 -1
  116. package/src/fields/types/text/views/index.tsx +14 -1
  117. package/src/fields/types/timestamp/views/index.tsx +2 -1
  118. package/src/fields/types/virtual/views/index.tsx +17 -2
  119. package/src/internal-unstable/admin-ui/pages/HomePage/index.tsx +40 -31
  120. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +36 -3
  121. package/src/internal-unstable/admin-ui/pages/ListPage/PaginationControls.tsx +20 -33
  122. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +24 -16
  123. package/dist/GraphQLErrorNotice-cd74180d.cjs.js +0 -57
  124. package/dist/GraphQLErrorNotice-d9f0931b.esm.js +0 -55
  125. package/dist/pick-5fe45878.cjs.js +0 -71
  126. package/dist/pick-b7ef3115.esm.js +0 -68
@@ -1,10 +1,11 @@
1
1
  import NextHead from 'next/head';
2
2
  import { useState, useRef, useMemo, useEffect } from 'react';
3
- import { css } from '@keystar/ui/style';
3
+ import { css, tokenSchema } from '@keystar/ui/style';
4
4
  import NextLink from 'next/link';
5
- import { u as useNixxie } from './context-af9957ed.esm.js';
5
+ import { u as useNixxie } from './context-2924eaaa.esm.js';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import { useRouter } from 'next/router';
8
+ import { useId } from '@react-aria/utils';
8
9
 
9
10
  function Logo() {
10
11
  var _adminConfig$componen;
@@ -25,7 +26,7 @@ function DefaultLogo() {
25
26
  outline: 0,
26
27
  flexShrink: 0,
27
28
  '&:focus-visible': {
28
- outline: '2px solid #000',
29
+ outline: `2px solid ${tokenSchema.color.scale.black}`,
29
30
  outlineOffset: 3,
30
31
  borderRadius: 4
31
32
  }
@@ -38,7 +39,7 @@ function DefaultLogo() {
38
39
  width: 28,
39
40
  height: 28,
40
41
  borderRadius: 6,
41
- backgroundColor: '#000000',
42
+ backgroundColor: tokenSchema.color.scale.black,
42
43
  flexShrink: 0
43
44
  }),
44
45
  children: /*#__PURE__*/jsx("svg", {
@@ -48,7 +49,7 @@ function DefaultLogo() {
48
49
  fill: "none",
49
50
  children: /*#__PURE__*/jsx("path", {
50
51
  d: "M2.5 11V3L11.5 11V3",
51
- stroke: "white",
52
+ stroke: tokenSchema.color.scale.white,
52
53
  strokeWidth: "1.8",
53
54
  strokeLinecap: "round",
54
55
  strokeLinejoin: "round"
@@ -59,7 +60,7 @@ function DefaultLogo() {
59
60
  fontSize: 14,
60
61
  fontWeight: 600,
61
62
  letterSpacing: '-0.03em',
62
- color: '#0a0a0a',
63
+ color: tokenSchema.color.foreground.neutralEmphasis,
63
64
  lineHeight: 1,
64
65
  fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif"
65
66
  }),
@@ -83,8 +84,10 @@ function NavItem({
83
84
  indent
84
85
  }) {
85
86
  const router = useRouter();
86
- const segment = href.split('/')[1];
87
- const isActive = router.pathname === href || segment && router.pathname.split('/')[1] === segment && href !== '/';
87
+ // Active on an exact match, or when the current route is nested under `href`
88
+ // (prefix match on a path boundary). Dashboard ('/') is only active on an
89
+ // exact match so it doesn't light up for every nested route.
90
+ const isActive = router.pathname === href || href !== '/' && router.pathname.startsWith(href + '/');
88
91
  return /*#__PURE__*/jsxs("a", {
89
92
  href: href,
90
93
  onClick: e => {
@@ -101,8 +104,8 @@ function NavItem({
101
104
  paddingBlock: '7px',
102
105
  fontSize: 13,
103
106
  fontWeight: isActive ? 500 : 400,
104
- color: isActive ? '#0a0a0a' : '#636363',
105
- backgroundColor: isActive ? '#f5f5f5' : 'transparent',
107
+ color: isActive ? tokenSchema.color.foreground.neutralEmphasis : tokenSchema.color.foreground.neutralSecondary,
108
+ backgroundColor: isActive ? tokenSchema.color.background.surfaceSecondary : 'transparent',
106
109
  textDecoration: 'none',
107
110
  borderRadius: '0 6px 6px 0',
108
111
  marginRight: '12px',
@@ -111,8 +114,8 @@ function NavItem({
111
114
  userSelect: 'none',
112
115
  transition: 'color 120ms, background-color 120ms',
113
116
  '&:hover': {
114
- color: '#0a0a0a',
115
- backgroundColor: '#f5f5f5'
117
+ color: tokenSchema.color.foreground.neutralEmphasis,
118
+ backgroundColor: tokenSchema.color.background.surfaceSecondary
116
119
  }
117
120
  }),
118
121
  children: [isActive && /*#__PURE__*/jsx("span", {
@@ -123,7 +126,7 @@ function NavItem({
123
126
  bottom: '20%',
124
127
  width: 2,
125
128
  borderRadius: '0 2px 2px 0',
126
- backgroundColor: '#000000'
129
+ backgroundColor: tokenSchema.color.scale.black
127
130
  })
128
131
  }), /*#__PURE__*/jsx("span", {
129
132
  className: css({
@@ -158,7 +161,7 @@ function SectionLabel({
158
161
  fontWeight: 600,
159
162
  letterSpacing: '0.10em',
160
163
  textTransform: 'uppercase',
161
- color: '#b8b8b8'
164
+ color: tokenSchema.color.foreground.neutralSecondary
162
165
  }),
163
166
  children: children
164
167
  }), action]
@@ -175,7 +178,7 @@ function NavDivider() {
175
178
  height: 1,
176
179
  marginInline: '16px',
177
180
  marginBlock: '6px',
178
- backgroundColor: '#f2f2f2'
181
+ backgroundColor: tokenSchema.color.border.muted
179
182
  })
180
183
  });
181
184
  }
@@ -199,20 +202,20 @@ function SearchTrigger({
199
202
  paddingInline: '10px',
200
203
  paddingBlock: '7px',
201
204
  borderRadius: 6,
202
- border: '1px solid #ebebeb',
203
- backgroundColor: '#f5f5f5',
204
- color: '#a3a3a3',
205
+ border: `1px solid ${tokenSchema.color.border.muted}`,
206
+ backgroundColor: tokenSchema.color.background.surfaceSecondary,
207
+ color: tokenSchema.color.foreground.neutralSecondary,
205
208
  fontSize: 12.5,
206
209
  cursor: 'pointer',
207
210
  textAlign: 'left',
208
211
  transition: 'border-color 140ms, background 140ms, color 140ms',
209
212
  '&:hover': {
210
- borderColor: '#dedede',
211
- backgroundColor: '#efefef',
212
- color: '#737373'
213
+ borderColor: tokenSchema.color.border.emphasis,
214
+ backgroundColor: tokenSchema.color.background.surfaceSecondary,
215
+ color: tokenSchema.color.foreground.neutralSecondary
213
216
  },
214
217
  '&:focus-visible': {
215
- outline: '2px solid #000',
218
+ outline: `2px solid ${tokenSchema.color.scale.black}`,
216
219
  outlineOffset: 2
217
220
  }
218
221
  }),
@@ -244,7 +247,7 @@ function SearchTrigger({
244
247
  }), /*#__PURE__*/jsx("kbd", {
245
248
  className: css({
246
249
  fontSize: 10.5,
247
- color: '#c8c8c8',
250
+ color: tokenSchema.color.foreground.neutralSecondary,
248
251
  fontFamily: 'inherit',
249
252
  letterSpacing: '0.02em'
250
253
  }),
@@ -276,18 +279,18 @@ function NavSearch({
276
279
  paddingInline: '10px',
277
280
  paddingBlock: '5px',
278
281
  fontSize: 12.5,
279
- border: '1px solid #ebebeb',
282
+ border: `1px solid ${tokenSchema.color.border.muted}`,
280
283
  borderRadius: 5,
281
- backgroundColor: '#f5f5f5',
282
- color: '#0a0a0a',
284
+ backgroundColor: tokenSchema.color.background.surfaceSecondary,
285
+ color: tokenSchema.color.foreground.neutralEmphasis,
283
286
  outline: 'none',
284
287
  boxSizing: 'border-box',
285
288
  transition: 'border-color 140ms',
286
289
  '&:focus': {
287
- borderColor: '#b0b0b0'
290
+ borderColor: tokenSchema.color.alias.borderHovered
288
291
  },
289
292
  '&::placeholder': {
290
- color: '#c8c8c8'
293
+ color: tokenSchema.color.foreground.neutralTertiary
291
294
  },
292
295
  '&::-webkit-search-cancel-button': {
293
296
  display: 'none'
@@ -321,7 +324,7 @@ function CollectionsSection({
321
324
  className: css({
322
325
  margin: '4px 16px',
323
326
  fontSize: 12.5,
324
- color: '#c8c8c8'
327
+ color: tokenSchema.color.foreground.neutralSecondary
325
328
  }),
326
329
  children: "No results"
327
330
  }) : filtered.map(list => /*#__PURE__*/jsxs(NavItem, {
@@ -332,7 +335,7 @@ function CollectionsSection({
332
335
  marginLeft: 6,
333
336
  fontSize: 10,
334
337
  fontWeight: 500,
335
- color: '#c8c8c8',
338
+ color: tokenSchema.color.foreground.neutralSecondary,
336
339
  letterSpacing: '0.04em',
337
340
  textTransform: 'uppercase'
338
341
  }),
@@ -394,7 +397,7 @@ function Navigation({
394
397
  className: css({
395
398
  margin: '8px 16px',
396
399
  fontSize: 12.5,
397
- color: '#c8c8c8'
400
+ color: tokenSchema.color.foreground.neutralSecondary
398
401
  }),
399
402
  children: "No collections"
400
403
  }), process.env.NODE_ENV !== 'production' && apiPath && /*#__PURE__*/jsxs(Fragment, {
@@ -441,7 +444,7 @@ function NavFooter({
441
444
  className: css({
442
445
  paddingInline: '12px',
443
446
  paddingBlock: '10px',
444
- borderTop: '1px solid #f2f2f2',
447
+ borderTop: `1px solid ${tokenSchema.color.border.muted}`,
445
448
  marginTop: 'auto'
446
449
  }),
447
450
  children: children
@@ -498,6 +501,8 @@ function CommandPalette({
498
501
  const [activeIndex, setActiveIndex] = useState(0);
499
502
  const inputRef = useRef(null);
500
503
  const listRef = useRef(null);
504
+ const panelRef = useRef(null);
505
+ const listboxId = useId();
501
506
  const commands = useCommands();
502
507
  const filtered = useMemo(() => {
503
508
  if (!query.trim()) return commands;
@@ -525,11 +530,29 @@ function CommandPalette({
525
530
  if (!isOpen) return;
526
531
  setQuery('');
527
532
  setActiveIndex(0);
533
+
534
+ // FOCUS RESTORE: remember what was focused before the palette opened.
535
+ const previouslyFocused = document.activeElement;
536
+
537
+ // SCROLL LOCK: prevent the page behind the modal from scrolling.
538
+ const prevOverflow = document.body.style.overflow;
539
+ document.body.style.overflow = 'hidden';
528
540
  const t = setTimeout(() => {
529
541
  var _inputRef$current;
530
542
  return (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
531
543
  }, 10);
532
- return () => clearTimeout(t);
544
+ return () => {
545
+ clearTimeout(t);
546
+ // Restore scroll position behaviour.
547
+ document.body.style.overflow = prevOverflow;
548
+ // Restore focus to whatever held it before we opened.
549
+ try {
550
+ var _previouslyFocused$fo;
551
+ previouslyFocused === null || previouslyFocused === void 0 || (_previouslyFocused$fo = previouslyFocused.focus) === null || _previouslyFocused$fo === void 0 || _previouslyFocused$fo.call(previouslyFocused);
552
+ } catch {
553
+ /* element may be gone from the DOM — ignore */
554
+ }
555
+ };
533
556
  }, [isOpen]);
534
557
  useEffect(() => {
535
558
  setActiveIndex(0);
@@ -551,6 +574,41 @@ function CommandPalette({
551
574
  cmd.action();
552
575
  }
553
576
  }
577
+
578
+ // FOCUS TRAP + panel-level ESCAPE. Runs for any child of the panel so the
579
+ // modal closes regardless of which element currently holds focus, and Tab /
580
+ // Shift+Tab stay contained within the panel's focusable descendants.
581
+ function onPanelKeyDown(e) {
582
+ if (e.key === 'Escape') {
583
+ e.preventDefault();
584
+ onClose();
585
+ return;
586
+ }
587
+ if (e.key !== 'Tab') return;
588
+ const panel = panelRef.current;
589
+ if (!panel) return;
590
+ let focusables;
591
+ try {
592
+ focusables = Array.from(panel.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter(el => el.offsetParent !== null || el === document.activeElement);
593
+ } catch {
594
+ return;
595
+ }
596
+ if (focusables.length === 0) return;
597
+ const first = focusables[0];
598
+ const last = focusables[focusables.length - 1];
599
+ const current = document.activeElement;
600
+ if (e.shiftKey) {
601
+ if (current === first || !panel.contains(current)) {
602
+ e.preventDefault();
603
+ last.focus();
604
+ }
605
+ } else {
606
+ if (current === last || !panel.contains(current)) {
607
+ e.preventDefault();
608
+ first.focus();
609
+ }
610
+ }
611
+ }
554
612
  function onKeyDown(e) {
555
613
  if (e.key === 'ArrowDown') {
556
614
  e.preventDefault();
@@ -574,6 +632,12 @@ function CommandPalette({
574
632
  flatList.forEach((cmd, i) => map.set(cmd.id, i));
575
633
  return map;
576
634
  }, [flatList]);
635
+
636
+ // Stable per-option id derived from the listbox id, used for both the rendered
637
+ // <li role="option"> ids and the input's aria-activedescendant.
638
+ const optionId = cmdId => `${listboxId}-opt-${cmdId}`;
639
+ const activeCommand = flatList[activeIndex];
640
+ const activeOptionId = activeCommand ? optionId(activeCommand.id) : undefined;
577
641
  if (!isOpen) return null;
578
642
  return /*#__PURE__*/jsxs(Fragment, {
579
643
  children: [/*#__PURE__*/jsx("div", {
@@ -581,14 +645,16 @@ function CommandPalette({
581
645
  className: css({
582
646
  position: 'fixed',
583
647
  inset: 0,
584
- backgroundColor: 'rgba(0,0,0,0.45)',
648
+ backgroundColor: tokenSchema.color.alias.blanket,
585
649
  zIndex: 200,
586
650
  backdropFilter: 'blur(3px)'
587
651
  })
588
652
  }), /*#__PURE__*/jsxs("div", {
653
+ ref: panelRef,
589
654
  role: "dialog",
590
655
  "aria-modal": "true",
591
656
  "aria-label": "Command palette",
657
+ onKeyDown: onPanelKeyDown,
592
658
  className: css({
593
659
  position: 'fixed',
594
660
  top: '14vh',
@@ -599,7 +665,7 @@ function CommandPalette({
599
665
  maxHeight: '62vh',
600
666
  display: 'flex',
601
667
  flexDirection: 'column',
602
- backgroundColor: '#ffffff',
668
+ backgroundColor: tokenSchema.color.background.canvas,
603
669
  borderRadius: 10,
604
670
  boxShadow: '0 24px 64px rgba(0,0,0,0.22), 0 0 0 1px rgba(0,0,0,0.07)',
605
671
  zIndex: 201,
@@ -611,16 +677,17 @@ function CommandPalette({
611
677
  alignItems: 'center',
612
678
  gap: 10,
613
679
  padding: '12px 16px',
614
- borderBottom: '1px solid #f0f0f0'
680
+ borderBottom: `1px solid ${tokenSchema.color.border.muted}`
615
681
  }),
616
682
  children: [/*#__PURE__*/jsxs("svg", {
617
683
  width: "15",
618
684
  height: "15",
619
685
  viewBox: "0 0 15 15",
620
686
  fill: "none",
687
+ "aria-hidden": "true",
621
688
  style: {
622
689
  flexShrink: 0,
623
- color: '#a3a3a3'
690
+ color: tokenSchema.color.foreground.neutralSecondary
624
691
  },
625
692
  children: [/*#__PURE__*/jsx("circle", {
626
693
  cx: "6.5",
@@ -643,16 +710,22 @@ function CommandPalette({
643
710
  onChange: e => setQuery(e.target.value),
644
711
  onKeyDown: onKeyDown,
645
712
  placeholder: "Search commands, lists, actions\u2026",
713
+ role: "combobox",
714
+ "aria-label": "Search commands",
715
+ "aria-expanded": flatList.length > 0,
716
+ "aria-controls": listboxId,
717
+ "aria-autocomplete": "list",
718
+ "aria-activedescendant": activeOptionId,
646
719
  className: css({
647
720
  flex: 1,
648
721
  border: 'none',
649
722
  outline: 'none',
650
723
  fontSize: 14.5,
651
- color: '#0a0a0a',
724
+ color: tokenSchema.color.foreground.neutralEmphasis,
652
725
  background: 'transparent',
653
726
  fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
654
727
  '&::placeholder': {
655
- color: '#b8b8b8'
728
+ color: tokenSchema.color.foreground.neutralSecondary
656
729
  }
657
730
  })
658
731
  }), /*#__PURE__*/jsx("kbd", {
@@ -661,11 +734,11 @@ function CommandPalette({
661
734
  alignItems: 'center',
662
735
  gap: 2,
663
736
  padding: '2px 7px',
664
- backgroundColor: '#f5f5f5',
665
- border: '1px solid #e8e8e8',
737
+ backgroundColor: tokenSchema.color.background.surfaceSecondary,
738
+ border: `1px solid ${tokenSchema.color.border.muted}`,
666
739
  borderRadius: 5,
667
740
  fontSize: 11,
668
- color: '#a3a3a3',
741
+ color: tokenSchema.color.foreground.neutralSecondary,
669
742
  fontFamily: 'inherit',
670
743
  flexShrink: 0
671
744
  }),
@@ -673,7 +746,9 @@ function CommandPalette({
673
746
  })]
674
747
  }), /*#__PURE__*/jsx("ul", {
675
748
  ref: listRef,
749
+ id: listboxId,
676
750
  role: "listbox",
751
+ "aria-label": "Commands",
677
752
  className: css({
678
753
  flex: 1,
679
754
  overflowY: 'auto',
@@ -684,7 +759,7 @@ function CommandPalette({
684
759
  width: 4
685
760
  },
686
761
  '&::-webkit-scrollbar-thumb': {
687
- background: '#e8e8e8',
762
+ background: tokenSchema.color.border.muted,
688
763
  borderRadius: 4
689
764
  }
690
765
  }),
@@ -692,124 +767,131 @@ function CommandPalette({
692
767
  className: css({
693
768
  padding: '28px 16px',
694
769
  textAlign: 'center',
695
- color: '#a3a3a3',
770
+ color: tokenSchema.color.foreground.neutralSecondary,
696
771
  fontSize: 13.5
697
772
  }),
698
773
  children: ["No results for \u201C", query, "\u201D"]
699
- }) : Array.from(grouped.entries()).map(([category, items]) => /*#__PURE__*/jsxs("li", {
700
- role: "none",
701
- children: [/*#__PURE__*/jsx("p", {
702
- className: css({
703
- margin: 0,
704
- padding: '8px 16px 3px',
705
- fontSize: 10,
706
- fontWeight: 600,
707
- letterSpacing: '0.10em',
708
- textTransform: 'uppercase',
709
- color: '#b8b8b8'
710
- }),
711
- children: category
712
- }), /*#__PURE__*/jsx("ul", {
713
- role: "group",
714
- className: css({
715
- listStyle: 'none',
716
- margin: 0,
717
- padding: 0
718
- }),
719
- children: items.map(cmd => {
720
- var _cmdFlatIndex$get;
721
- const idx = (_cmdFlatIndex$get = cmdFlatIndex.get(cmd.id)) !== null && _cmdFlatIndex$get !== void 0 ? _cmdFlatIndex$get : 0;
722
- const isActive = idx === activeIndex;
723
- return /*#__PURE__*/jsxs("li", {
724
- role: "option",
725
- "aria-selected": isActive,
726
- "data-active": isActive,
727
- onClick: () => runCommand(cmd),
728
- onMouseEnter: () => setActiveIndex(idx),
729
- className: css({
730
- display: 'flex',
731
- alignItems: 'center',
732
- gap: 10,
733
- padding: '7px 16px',
734
- cursor: 'pointer',
735
- backgroundColor: isActive ? '#0a0a0a' : 'transparent',
736
- transition: 'background 100ms',
737
- '&:hover': {
738
- backgroundColor: isActive ? '#0a0a0a' : '#f5f5f5'
739
- }
740
- }),
741
- children: [/*#__PURE__*/jsx("span", {
774
+ }) : Array.from(grouped.entries()).map(([category, items], groupIdx) => {
775
+ const headerId = `${listboxId}-cat-${groupIdx}`;
776
+ return /*#__PURE__*/jsxs("li", {
777
+ role: "presentation",
778
+ children: [/*#__PURE__*/jsx("p", {
779
+ id: headerId,
780
+ role: "presentation",
781
+ className: css({
782
+ margin: 0,
783
+ padding: '8px 16px 3px',
784
+ fontSize: 10,
785
+ fontWeight: 600,
786
+ letterSpacing: '0.10em',
787
+ textTransform: 'uppercase',
788
+ color: tokenSchema.color.foreground.neutralSecondary
789
+ }),
790
+ children: category
791
+ }), /*#__PURE__*/jsx("ul", {
792
+ role: "group",
793
+ "aria-labelledby": headerId,
794
+ className: css({
795
+ listStyle: 'none',
796
+ margin: 0,
797
+ padding: 0
798
+ }),
799
+ children: items.map(cmd => {
800
+ var _cmdFlatIndex$get;
801
+ const idx = (_cmdFlatIndex$get = cmdFlatIndex.get(cmd.id)) !== null && _cmdFlatIndex$get !== void 0 ? _cmdFlatIndex$get : 0;
802
+ const isActive = idx === activeIndex;
803
+ return /*#__PURE__*/jsxs("li", {
804
+ id: optionId(cmd.id),
805
+ role: "option",
806
+ "aria-selected": isActive,
807
+ "data-active": isActive,
808
+ onClick: () => runCommand(cmd),
809
+ onMouseEnter: () => setActiveIndex(idx),
742
810
  className: css({
743
- display: 'inline-flex',
811
+ display: 'flex',
744
812
  alignItems: 'center',
745
- justifyContent: 'center',
746
- width: 28,
747
- height: 28,
748
- borderRadius: 6,
749
- backgroundColor: isActive ? '#2a2a2a' : '#f0f0f0',
750
- fontSize: 12,
751
- fontWeight: 500,
752
- color: isActive ? '#ffffff' : '#636363',
753
- flexShrink: 0,
754
- transition: 'background 100ms, color 100ms',
755
- fontFamily: 'monospace'
756
- }),
757
- children: cmd.icon
758
- }), /*#__PURE__*/jsxs("span", {
759
- className: css({
760
- flex: 1,
761
- minWidth: 0
813
+ gap: 10,
814
+ padding: '7px 16px',
815
+ cursor: 'pointer',
816
+ backgroundColor: isActive ? tokenSchema.color.scale.black : 'transparent',
817
+ transition: 'background 100ms',
818
+ '&:hover': {
819
+ backgroundColor: isActive ? tokenSchema.color.scale.black : tokenSchema.color.background.surfaceSecondary
820
+ }
762
821
  }),
763
822
  children: [/*#__PURE__*/jsx("span", {
764
823
  className: css({
765
- display: 'block',
766
- fontSize: 13.5,
824
+ display: 'inline-flex',
825
+ alignItems: 'center',
826
+ justifyContent: 'center',
827
+ width: 28,
828
+ height: 28,
829
+ borderRadius: 6,
830
+ backgroundColor: isActive ? '#2a2a2a' : tokenSchema.color.background.surfaceSecondary,
831
+ fontSize: 12,
767
832
  fontWeight: 500,
768
- color: isActive ? '#ffffff' : '#0a0a0a',
769
- whiteSpace: 'nowrap',
770
- overflow: 'hidden',
771
- textOverflow: 'ellipsis',
772
- transition: 'color 100ms'
833
+ color: isActive ? tokenSchema.color.foreground.onEmphasis : tokenSchema.color.foreground.neutralSecondary,
834
+ flexShrink: 0,
835
+ transition: 'background 100ms, color 100ms',
836
+ fontFamily: 'monospace'
773
837
  }),
774
- children: cmd.label
775
- }), cmd.description && /*#__PURE__*/jsx("span", {
838
+ children: cmd.icon
839
+ }), /*#__PURE__*/jsxs("span", {
776
840
  className: css({
777
- display: 'block',
778
- fontSize: 12,
779
- color: isActive ? 'rgba(255,255,255,0.5)' : '#a3a3a3',
780
- whiteSpace: 'nowrap',
781
- overflow: 'hidden',
782
- textOverflow: 'ellipsis',
783
- transition: 'color 100ms'
841
+ flex: 1,
842
+ minWidth: 0
843
+ }),
844
+ children: [/*#__PURE__*/jsx("span", {
845
+ className: css({
846
+ display: 'block',
847
+ fontSize: 13.5,
848
+ fontWeight: 500,
849
+ color: isActive ? tokenSchema.color.foreground.onEmphasis : tokenSchema.color.foreground.neutralEmphasis,
850
+ whiteSpace: 'nowrap',
851
+ overflow: 'hidden',
852
+ textOverflow: 'ellipsis',
853
+ transition: 'color 100ms'
854
+ }),
855
+ children: cmd.label
856
+ }), cmd.description && /*#__PURE__*/jsx("span", {
857
+ className: css({
858
+ display: 'block',
859
+ fontSize: 12,
860
+ color: isActive ? tokenSchema.color.foreground.inverseSecondary : tokenSchema.color.foreground.neutralSecondary,
861
+ whiteSpace: 'nowrap',
862
+ overflow: 'hidden',
863
+ textOverflow: 'ellipsis',
864
+ transition: 'color 100ms'
865
+ }),
866
+ children: cmd.description
867
+ })]
868
+ }), isActive && /*#__PURE__*/jsx("kbd", {
869
+ className: css({
870
+ fontSize: 11,
871
+ color: tokenSchema.color.foreground.inverseSecondary,
872
+ background: '#2a2a2a',
873
+ border: '1px solid #3a3a3a',
874
+ borderRadius: 4,
875
+ padding: '2px 6px',
876
+ fontFamily: 'inherit',
877
+ flexShrink: 0
784
878
  }),
785
- children: cmd.description
879
+ children: "\u21B5"
786
880
  })]
787
- }), isActive && /*#__PURE__*/jsx("kbd", {
788
- className: css({
789
- fontSize: 11,
790
- color: 'rgba(255,255,255,0.6)',
791
- background: '#2a2a2a',
792
- border: '1px solid #3a3a3a',
793
- borderRadius: 4,
794
- padding: '2px 6px',
795
- fontFamily: 'inherit',
796
- flexShrink: 0
797
- }),
798
- children: "\u21B5"
799
- })]
800
- }, cmd.id);
801
- })
802
- })]
803
- }, category))
881
+ }, cmd.id);
882
+ })
883
+ })]
884
+ }, category);
885
+ })
804
886
  }), /*#__PURE__*/jsxs("div", {
805
887
  className: css({
806
888
  display: 'flex',
807
889
  alignItems: 'center',
808
890
  gap: 14,
809
891
  padding: '8px 16px',
810
- borderTop: '1px solid #f0f0f0',
892
+ borderTop: `1px solid ${tokenSchema.color.border.muted}`,
811
893
  fontSize: 11,
812
- color: '#b8b8b8'
894
+ color: tokenSchema.color.foreground.neutralSecondary
813
895
  }),
814
896
  children: [/*#__PURE__*/jsxs("span", {
815
897
  children: [/*#__PURE__*/jsx("kbd", {
@@ -856,13 +938,54 @@ function PageWrapper(props) {
856
938
  display: 'flex',
857
939
  height: '100vh',
858
940
  overflow: 'hidden',
859
- backgroundColor: '#fafafa',
941
+ backgroundColor: tokenSchema.color.background.surface,
860
942
  fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
861
943
  }),
862
944
  ...props
863
945
  });
864
946
  }
865
947
 
948
+ // ---- Skip to content (WCAG 2.4.1 Bypass Blocks) ----
949
+ function SkipLink() {
950
+ return /*#__PURE__*/jsx("a", {
951
+ href: "#main-content",
952
+ className: css({
953
+ // Visually hidden, off-screen by default — kept out of the layout for mouse users.
954
+ position: 'absolute',
955
+ top: 0,
956
+ left: 0,
957
+ width: 1,
958
+ height: 1,
959
+ padding: 0,
960
+ margin: -1,
961
+ overflow: 'hidden',
962
+ clip: 'rect(0 0 0 0)',
963
+ whiteSpace: 'nowrap',
964
+ border: 0,
965
+ zIndex: 60,
966
+ // Reveal on keyboard focus.
967
+ '&:focus, &:focus-visible': {
968
+ width: 'auto',
969
+ height: 'auto',
970
+ padding: '10px 16px',
971
+ margin: 8,
972
+ clip: 'auto',
973
+ overflow: 'visible',
974
+ backgroundColor: tokenSchema.color.background.canvas,
975
+ color: tokenSchema.color.foreground.neutralEmphasis,
976
+ borderRadius: 6,
977
+ boxShadow: `0 2px 8px ${tokenSchema.color.shadow.regular}`,
978
+ outline: `2px solid ${tokenSchema.color.foreground.neutralEmphasis}`,
979
+ outlineOffset: 2,
980
+ textDecoration: 'none',
981
+ fontSize: 14,
982
+ fontWeight: 500
983
+ }
984
+ }),
985
+ children: "Skip to content"
986
+ });
987
+ }
988
+
866
989
  // ---- Mobile overlay ----
867
990
  function Backdrop({
868
991
  isVisible,
@@ -874,7 +997,7 @@ function Backdrop({
874
997
  className: css({
875
998
  position: 'fixed',
876
999
  inset: 0,
877
- backgroundColor: 'rgba(10,10,10,0.35)',
1000
+ backgroundColor: tokenSchema.color.alias.blanket,
878
1001
  zIndex: 40,
879
1002
  transition: 'opacity 200ms',
880
1003
  opacity: isVisible ? 1 : 0,
@@ -888,16 +1011,91 @@ function Backdrop({
888
1011
  }
889
1012
 
890
1013
  // ---- Sidebar — pure white, precise ----
1014
+ const FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),[tabindex]:not([tabindex="-1"])';
891
1015
  function Sidebar({
892
1016
  isOpen,
893
1017
  onClose,
894
1018
  onCmdK
895
1019
  }) {
1020
+ const asideRef = useRef(null);
1021
+ // Element to restore focus to (the hamburger) once the drawer closes.
1022
+ const lastFocusedRef = useRef(null);
1023
+
1024
+ // Mobile drawer a11y: focus trap, Escape-to-close, initial focus, and focus
1025
+ // restore. `isOpen` is only ever true on mobile (the resize handler forces it
1026
+ // false at >=768px), so gating on it doubles as a mobile gate.
1027
+ useEffect(() => {
1028
+ if (!isOpen) return;
1029
+ const aside = asideRef.current;
1030
+ if (!aside) return;
1031
+
1032
+ // Save the currently focused element so we can restore it on close.
1033
+ try {
1034
+ const active = document.activeElement;
1035
+ lastFocusedRef.current = active instanceof HTMLElement ? active : null;
1036
+ } catch {
1037
+ lastFocusedRef.current = null;
1038
+ }
1039
+ const getFocusable = () => {
1040
+ try {
1041
+ return Array.from(aside.querySelectorAll(FOCUSABLE_SELECTOR)).filter(el => el.offsetParent !== null || el === document.activeElement);
1042
+ } catch {
1043
+ return [];
1044
+ }
1045
+ };
1046
+
1047
+ // Move initial focus into the drawer.
1048
+ try {
1049
+ var _getFocusable$;
1050
+ (_getFocusable$ = getFocusable()[0]) === null || _getFocusable$ === void 0 || _getFocusable$.focus();
1051
+ } catch {
1052
+ /* noop */
1053
+ }
1054
+ function onKeyDown(e) {
1055
+ if (e.key === 'Escape') {
1056
+ e.preventDefault();
1057
+ onClose();
1058
+ return;
1059
+ }
1060
+ if (e.key !== 'Tab') return;
1061
+ const focusable = getFocusable();
1062
+ if (focusable.length === 0) {
1063
+ // Nothing focusable inside — keep focus from escaping anyway.
1064
+ e.preventDefault();
1065
+ return;
1066
+ }
1067
+ const first = focusable[0];
1068
+ const last = focusable[focusable.length - 1];
1069
+ const current = document.activeElement;
1070
+ if (e.shiftKey) {
1071
+ if (current === first || !(aside !== null && aside !== void 0 && aside.contains(current))) {
1072
+ e.preventDefault();
1073
+ last.focus();
1074
+ }
1075
+ } else if (current === last || !(aside !== null && aside !== void 0 && aside.contains(current))) {
1076
+ e.preventDefault();
1077
+ first.focus();
1078
+ }
1079
+ }
1080
+ document.addEventListener('keydown', onKeyDown, true);
1081
+ return () => {
1082
+ document.removeEventListener('keydown', onKeyDown, true);
1083
+ // Restore focus to the trigger that opened the drawer.
1084
+ try {
1085
+ var _lastFocusedRef$curre;
1086
+ (_lastFocusedRef$curre = lastFocusedRef.current) === null || _lastFocusedRef$curre === void 0 || _lastFocusedRef$curre.focus();
1087
+ } catch {
1088
+ /* noop */
1089
+ }
1090
+ };
1091
+ }, [isOpen, onClose]);
896
1092
  return /*#__PURE__*/jsxs(Fragment, {
897
1093
  children: [/*#__PURE__*/jsx(Backdrop, {
898
1094
  isVisible: isOpen,
899
1095
  onClick: onClose
900
1096
  }), /*#__PURE__*/jsxs("aside", {
1097
+ id: "nixxie-sidebar",
1098
+ ref: asideRef,
901
1099
  className: css({
902
1100
  position: 'fixed',
903
1101
  top: 0,
@@ -907,13 +1105,18 @@ function Sidebar({
907
1105
  zIndex: 50,
908
1106
  display: 'flex',
909
1107
  flexDirection: 'column',
910
- backgroundColor: '#ffffff',
911
- borderRight: '1px solid #ebebeb',
1108
+ backgroundColor: tokenSchema.color.background.canvas,
1109
+ borderRight: `1px solid ${tokenSchema.color.border.muted}`,
912
1110
  transform: isOpen ? 'translateX(0)' : `translateX(-${SIDEBAR_WIDTH}px)`,
1111
+ // Closed off-canvas drawer must leave the tab order / AT tree on
1112
+ // mobile; `visibility:hidden` removes its descendants from both.
1113
+ visibility: isOpen ? 'visible' : 'hidden',
913
1114
  transition: 'transform 220ms cubic-bezier(0.4,0,0.2,1)',
914
1115
  '@media (min-width: 768px)': {
915
1116
  position: 'relative',
916
1117
  transform: 'translateX(0)',
1118
+ // Persistent desktop rail is always reachable.
1119
+ visibility: 'visible',
917
1120
  flexShrink: 0
918
1121
  }
919
1122
  }),
@@ -923,7 +1126,7 @@ function Sidebar({
923
1126
  alignItems: 'center',
924
1127
  height: TOPBAR_HEIGHT,
925
1128
  paddingInline: '16px',
926
- borderBottom: '1px solid #f2f2f2',
1129
+ borderBottom: `1px solid ${tokenSchema.color.border.muted}`,
927
1130
  flexShrink: 0
928
1131
  }),
929
1132
  children: /*#__PURE__*/jsx(Logo, {})
@@ -937,7 +1140,7 @@ function Sidebar({
937
1140
  width: 3
938
1141
  },
939
1142
  '&::-webkit-scrollbar-thumb': {
940
- background: '#ebebeb',
1143
+ background: tokenSchema.color.border.muted,
941
1144
  borderRadius: 3
942
1145
  }
943
1146
  }),
@@ -963,8 +1166,8 @@ function TopBar({
963
1166
  height: TOPBAR_HEIGHT,
964
1167
  paddingInline: '24px',
965
1168
  gap: '14px',
966
- backgroundColor: '#ffffff',
967
- borderBottom: '1px solid #ebebeb',
1169
+ backgroundColor: tokenSchema.color.background.canvas,
1170
+ borderBottom: `1px solid ${tokenSchema.color.border.muted}`,
968
1171
  flexShrink: 0,
969
1172
  zIndex: 30
970
1173
  }),
@@ -972,6 +1175,7 @@ function TopBar({
972
1175
  onClick: onMenuClick,
973
1176
  "aria-label": isSidebarOpen ? 'Close menu' : 'Open menu',
974
1177
  "aria-expanded": isSidebarOpen,
1178
+ "aria-controls": "nixxie-sidebar",
975
1179
  className: css({
976
1180
  display: 'inline-flex',
977
1181
  alignItems: 'center',
@@ -979,15 +1183,15 @@ function TopBar({
979
1183
  width: 32,
980
1184
  height: 32,
981
1185
  borderRadius: 6,
982
- border: '1px solid #ebebeb',
1186
+ border: `1px solid ${tokenSchema.color.border.muted}`,
983
1187
  background: 'transparent',
984
1188
  cursor: 'pointer',
985
1189
  flexShrink: 0,
986
- color: '#a3a3a3',
1190
+ color: tokenSchema.color.foreground.neutralTertiary,
987
1191
  transition: 'color 130ms, background 130ms',
988
1192
  '&:hover': {
989
- background: '#f5f5f5',
990
- color: '#0a0a0a'
1193
+ background: tokenSchema.color.background.surfaceSecondary,
1194
+ color: tokenSchema.color.foreground.neutralEmphasis
991
1195
  },
992
1196
  '@media (min-width: 768px)': {
993
1197
  display: 'none'
@@ -1048,6 +1252,8 @@ function TopBar({
1048
1252
  // ---- Main content area ----
1049
1253
  function MainContent(props) {
1050
1254
  return /*#__PURE__*/jsx("main", {
1255
+ id: "main-content",
1256
+ tabIndex: -1,
1051
1257
  className: css({
1052
1258
  flex: 1,
1053
1259
  display: 'flex',
@@ -1071,11 +1277,11 @@ function ContentScroller(props) {
1071
1277
  width: 5
1072
1278
  },
1073
1279
  '&::-webkit-scrollbar-thumb': {
1074
- background: '#e8e8e8',
1280
+ background: tokenSchema.color.border.muted,
1075
1281
  borderRadius: 4
1076
1282
  },
1077
1283
  '&::-webkit-scrollbar-thumb:hover': {
1078
- background: '#d4d4d4'
1284
+ background: tokenSchema.color.border.emphasis
1079
1285
  }
1080
1286
  }),
1081
1287
  ...props
@@ -1114,7 +1320,7 @@ function PageContainer({
1114
1320
  children: /*#__PURE__*/jsx("title", {
1115
1321
  children: title ? `Nixxie – ${title}` : 'Nixxie'
1116
1322
  }, "title")
1117
- }), /*#__PURE__*/jsx(Sidebar, {
1323
+ }), /*#__PURE__*/jsx(SkipLink, {}), /*#__PURE__*/jsx(Sidebar, {
1118
1324
  isOpen: isSidebarOpen,
1119
1325
  onClose: () => setSidebarOpen(false),
1120
1326
  onCmdK: () => setCmdOpen(v => !v)