@nixxie-cms/core 2.0.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 (128) 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/__tests__/index.tsx +68 -68
  118. package/src/fields/types/timestamp/views/index.tsx +2 -1
  119. package/src/fields/types/virtual/views/index.tsx +17 -2
  120. package/src/internal-unstable/admin-ui/pages/HomePage/index.tsx +40 -31
  121. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +36 -3
  122. package/src/internal-unstable/admin-ui/pages/ListPage/PaginationControls.tsx +20 -33
  123. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +24 -16
  124. package/tests/conditional-filters.test.ts +333 -326
  125. package/dist/GraphQLErrorNotice-cd74180d.cjs.js +0 -57
  126. package/dist/GraphQLErrorNotice-d9f0931b.esm.js +0 -55
  127. package/dist/pick-5fe45878.cjs.js +0 -71
  128. package/dist/pick-b7ef3115.esm.js +0 -68
@@ -4,9 +4,10 @@ var NextHead = require('next/head');
4
4
  var react = require('react');
5
5
  var style = require('@keystar/ui/style');
6
6
  var NextLink = require('next/link');
7
- var adminUi_context_dist_nixxieCmsCoreAdminUiContext = require('./context-b5204629.cjs.js');
7
+ var adminUi_context_dist_nixxieCmsCoreAdminUiContext = require('./context-2ce61d0b.cjs.js');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
9
  var router = require('next/router');
10
+ var utils = require('@react-aria/utils');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
12
13
 
@@ -32,7 +33,7 @@ function DefaultLogo() {
32
33
  outline: 0,
33
34
  flexShrink: 0,
34
35
  '&:focus-visible': {
35
- outline: '2px solid #000',
36
+ outline: `2px solid ${style.tokenSchema.color.scale.black}`,
36
37
  outlineOffset: 3,
37
38
  borderRadius: 4
38
39
  }
@@ -45,7 +46,7 @@ function DefaultLogo() {
45
46
  width: 28,
46
47
  height: 28,
47
48
  borderRadius: 6,
48
- backgroundColor: '#000000',
49
+ backgroundColor: style.tokenSchema.color.scale.black,
49
50
  flexShrink: 0
50
51
  }),
51
52
  children: /*#__PURE__*/jsxRuntime.jsx("svg", {
@@ -55,7 +56,7 @@ function DefaultLogo() {
55
56
  fill: "none",
56
57
  children: /*#__PURE__*/jsxRuntime.jsx("path", {
57
58
  d: "M2.5 11V3L11.5 11V3",
58
- stroke: "white",
59
+ stroke: style.tokenSchema.color.scale.white,
59
60
  strokeWidth: "1.8",
60
61
  strokeLinecap: "round",
61
62
  strokeLinejoin: "round"
@@ -66,7 +67,7 @@ function DefaultLogo() {
66
67
  fontSize: 14,
67
68
  fontWeight: 600,
68
69
  letterSpacing: '-0.03em',
69
- color: '#0a0a0a',
70
+ color: style.tokenSchema.color.foreground.neutralEmphasis,
70
71
  lineHeight: 1,
71
72
  fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif"
72
73
  }),
@@ -90,8 +91,10 @@ function NavItem({
90
91
  indent
91
92
  }) {
92
93
  const router$1 = router.useRouter();
93
- const segment = href.split('/')[1];
94
- const isActive = router$1.pathname === href || segment && router$1.pathname.split('/')[1] === segment && href !== '/';
94
+ // Active on an exact match, or when the current route is nested under `href`
95
+ // (prefix match on a path boundary). Dashboard ('/') is only active on an
96
+ // exact match so it doesn't light up for every nested route.
97
+ const isActive = router$1.pathname === href || href !== '/' && router$1.pathname.startsWith(href + '/');
95
98
  return /*#__PURE__*/jsxRuntime.jsxs("a", {
96
99
  href: href,
97
100
  onClick: e => {
@@ -108,8 +111,8 @@ function NavItem({
108
111
  paddingBlock: '7px',
109
112
  fontSize: 13,
110
113
  fontWeight: isActive ? 500 : 400,
111
- color: isActive ? '#0a0a0a' : '#636363',
112
- backgroundColor: isActive ? '#f5f5f5' : 'transparent',
114
+ color: isActive ? style.tokenSchema.color.foreground.neutralEmphasis : style.tokenSchema.color.foreground.neutralSecondary,
115
+ backgroundColor: isActive ? style.tokenSchema.color.background.surfaceSecondary : 'transparent',
113
116
  textDecoration: 'none',
114
117
  borderRadius: '0 6px 6px 0',
115
118
  marginRight: '12px',
@@ -118,8 +121,8 @@ function NavItem({
118
121
  userSelect: 'none',
119
122
  transition: 'color 120ms, background-color 120ms',
120
123
  '&:hover': {
121
- color: '#0a0a0a',
122
- backgroundColor: '#f5f5f5'
124
+ color: style.tokenSchema.color.foreground.neutralEmphasis,
125
+ backgroundColor: style.tokenSchema.color.background.surfaceSecondary
123
126
  }
124
127
  }),
125
128
  children: [isActive && /*#__PURE__*/jsxRuntime.jsx("span", {
@@ -130,7 +133,7 @@ function NavItem({
130
133
  bottom: '20%',
131
134
  width: 2,
132
135
  borderRadius: '0 2px 2px 0',
133
- backgroundColor: '#000000'
136
+ backgroundColor: style.tokenSchema.color.scale.black
134
137
  })
135
138
  }), /*#__PURE__*/jsxRuntime.jsx("span", {
136
139
  className: style.css({
@@ -165,7 +168,7 @@ function SectionLabel({
165
168
  fontWeight: 600,
166
169
  letterSpacing: '0.10em',
167
170
  textTransform: 'uppercase',
168
- color: '#b8b8b8'
171
+ color: style.tokenSchema.color.foreground.neutralSecondary
169
172
  }),
170
173
  children: children
171
174
  }), action]
@@ -182,7 +185,7 @@ function NavDivider() {
182
185
  height: 1,
183
186
  marginInline: '16px',
184
187
  marginBlock: '6px',
185
- backgroundColor: '#f2f2f2'
188
+ backgroundColor: style.tokenSchema.color.border.muted
186
189
  })
187
190
  });
188
191
  }
@@ -206,20 +209,20 @@ function SearchTrigger({
206
209
  paddingInline: '10px',
207
210
  paddingBlock: '7px',
208
211
  borderRadius: 6,
209
- border: '1px solid #ebebeb',
210
- backgroundColor: '#f5f5f5',
211
- color: '#a3a3a3',
212
+ border: `1px solid ${style.tokenSchema.color.border.muted}`,
213
+ backgroundColor: style.tokenSchema.color.background.surfaceSecondary,
214
+ color: style.tokenSchema.color.foreground.neutralSecondary,
212
215
  fontSize: 12.5,
213
216
  cursor: 'pointer',
214
217
  textAlign: 'left',
215
218
  transition: 'border-color 140ms, background 140ms, color 140ms',
216
219
  '&:hover': {
217
- borderColor: '#dedede',
218
- backgroundColor: '#efefef',
219
- color: '#737373'
220
+ borderColor: style.tokenSchema.color.border.emphasis,
221
+ backgroundColor: style.tokenSchema.color.background.surfaceSecondary,
222
+ color: style.tokenSchema.color.foreground.neutralSecondary
220
223
  },
221
224
  '&:focus-visible': {
222
- outline: '2px solid #000',
225
+ outline: `2px solid ${style.tokenSchema.color.scale.black}`,
223
226
  outlineOffset: 2
224
227
  }
225
228
  }),
@@ -251,7 +254,7 @@ function SearchTrigger({
251
254
  }), /*#__PURE__*/jsxRuntime.jsx("kbd", {
252
255
  className: style.css({
253
256
  fontSize: 10.5,
254
- color: '#c8c8c8',
257
+ color: style.tokenSchema.color.foreground.neutralSecondary,
255
258
  fontFamily: 'inherit',
256
259
  letterSpacing: '0.02em'
257
260
  }),
@@ -283,18 +286,18 @@ function NavSearch({
283
286
  paddingInline: '10px',
284
287
  paddingBlock: '5px',
285
288
  fontSize: 12.5,
286
- border: '1px solid #ebebeb',
289
+ border: `1px solid ${style.tokenSchema.color.border.muted}`,
287
290
  borderRadius: 5,
288
- backgroundColor: '#f5f5f5',
289
- color: '#0a0a0a',
291
+ backgroundColor: style.tokenSchema.color.background.surfaceSecondary,
292
+ color: style.tokenSchema.color.foreground.neutralEmphasis,
290
293
  outline: 'none',
291
294
  boxSizing: 'border-box',
292
295
  transition: 'border-color 140ms',
293
296
  '&:focus': {
294
- borderColor: '#b0b0b0'
297
+ borderColor: style.tokenSchema.color.alias.borderHovered
295
298
  },
296
299
  '&::placeholder': {
297
- color: '#c8c8c8'
300
+ color: style.tokenSchema.color.foreground.neutralTertiary
298
301
  },
299
302
  '&::-webkit-search-cancel-button': {
300
303
  display: 'none'
@@ -328,7 +331,7 @@ function CollectionsSection({
328
331
  className: style.css({
329
332
  margin: '4px 16px',
330
333
  fontSize: 12.5,
331
- color: '#c8c8c8'
334
+ color: style.tokenSchema.color.foreground.neutralSecondary
332
335
  }),
333
336
  children: "No results"
334
337
  }) : filtered.map(list => /*#__PURE__*/jsxRuntime.jsxs(NavItem, {
@@ -339,7 +342,7 @@ function CollectionsSection({
339
342
  marginLeft: 6,
340
343
  fontSize: 10,
341
344
  fontWeight: 500,
342
- color: '#c8c8c8',
345
+ color: style.tokenSchema.color.foreground.neutralSecondary,
343
346
  letterSpacing: '0.04em',
344
347
  textTransform: 'uppercase'
345
348
  }),
@@ -401,7 +404,7 @@ function Navigation({
401
404
  className: style.css({
402
405
  margin: '8px 16px',
403
406
  fontSize: 12.5,
404
- color: '#c8c8c8'
407
+ color: style.tokenSchema.color.foreground.neutralSecondary
405
408
  }),
406
409
  children: "No collections"
407
410
  }), process.env.NODE_ENV !== 'production' && apiPath && /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
@@ -448,7 +451,7 @@ function NavFooter({
448
451
  className: style.css({
449
452
  paddingInline: '12px',
450
453
  paddingBlock: '10px',
451
- borderTop: '1px solid #f2f2f2',
454
+ borderTop: `1px solid ${style.tokenSchema.color.border.muted}`,
452
455
  marginTop: 'auto'
453
456
  }),
454
457
  children: children
@@ -505,6 +508,8 @@ function CommandPalette({
505
508
  const [activeIndex, setActiveIndex] = react.useState(0);
506
509
  const inputRef = react.useRef(null);
507
510
  const listRef = react.useRef(null);
511
+ const panelRef = react.useRef(null);
512
+ const listboxId = utils.useId();
508
513
  const commands = useCommands();
509
514
  const filtered = react.useMemo(() => {
510
515
  if (!query.trim()) return commands;
@@ -532,11 +537,29 @@ function CommandPalette({
532
537
  if (!isOpen) return;
533
538
  setQuery('');
534
539
  setActiveIndex(0);
540
+
541
+ // FOCUS RESTORE: remember what was focused before the palette opened.
542
+ const previouslyFocused = document.activeElement;
543
+
544
+ // SCROLL LOCK: prevent the page behind the modal from scrolling.
545
+ const prevOverflow = document.body.style.overflow;
546
+ document.body.style.overflow = 'hidden';
535
547
  const t = setTimeout(() => {
536
548
  var _inputRef$current;
537
549
  return (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
538
550
  }, 10);
539
- return () => clearTimeout(t);
551
+ return () => {
552
+ clearTimeout(t);
553
+ // Restore scroll position behaviour.
554
+ document.body.style.overflow = prevOverflow;
555
+ // Restore focus to whatever held it before we opened.
556
+ try {
557
+ var _previouslyFocused$fo;
558
+ previouslyFocused === null || previouslyFocused === void 0 || (_previouslyFocused$fo = previouslyFocused.focus) === null || _previouslyFocused$fo === void 0 || _previouslyFocused$fo.call(previouslyFocused);
559
+ } catch {
560
+ /* element may be gone from the DOM — ignore */
561
+ }
562
+ };
540
563
  }, [isOpen]);
541
564
  react.useEffect(() => {
542
565
  setActiveIndex(0);
@@ -558,6 +581,41 @@ function CommandPalette({
558
581
  cmd.action();
559
582
  }
560
583
  }
584
+
585
+ // FOCUS TRAP + panel-level ESCAPE. Runs for any child of the panel so the
586
+ // modal closes regardless of which element currently holds focus, and Tab /
587
+ // Shift+Tab stay contained within the panel's focusable descendants.
588
+ function onPanelKeyDown(e) {
589
+ if (e.key === 'Escape') {
590
+ e.preventDefault();
591
+ onClose();
592
+ return;
593
+ }
594
+ if (e.key !== 'Tab') return;
595
+ const panel = panelRef.current;
596
+ if (!panel) return;
597
+ let focusables;
598
+ try {
599
+ 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);
600
+ } catch {
601
+ return;
602
+ }
603
+ if (focusables.length === 0) return;
604
+ const first = focusables[0];
605
+ const last = focusables[focusables.length - 1];
606
+ const current = document.activeElement;
607
+ if (e.shiftKey) {
608
+ if (current === first || !panel.contains(current)) {
609
+ e.preventDefault();
610
+ last.focus();
611
+ }
612
+ } else {
613
+ if (current === last || !panel.contains(current)) {
614
+ e.preventDefault();
615
+ first.focus();
616
+ }
617
+ }
618
+ }
561
619
  function onKeyDown(e) {
562
620
  if (e.key === 'ArrowDown') {
563
621
  e.preventDefault();
@@ -581,6 +639,12 @@ function CommandPalette({
581
639
  flatList.forEach((cmd, i) => map.set(cmd.id, i));
582
640
  return map;
583
641
  }, [flatList]);
642
+
643
+ // Stable per-option id derived from the listbox id, used for both the rendered
644
+ // <li role="option"> ids and the input's aria-activedescendant.
645
+ const optionId = cmdId => `${listboxId}-opt-${cmdId}`;
646
+ const activeCommand = flatList[activeIndex];
647
+ const activeOptionId = activeCommand ? optionId(activeCommand.id) : undefined;
584
648
  if (!isOpen) return null;
585
649
  return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
586
650
  children: [/*#__PURE__*/jsxRuntime.jsx("div", {
@@ -588,14 +652,16 @@ function CommandPalette({
588
652
  className: style.css({
589
653
  position: 'fixed',
590
654
  inset: 0,
591
- backgroundColor: 'rgba(0,0,0,0.45)',
655
+ backgroundColor: style.tokenSchema.color.alias.blanket,
592
656
  zIndex: 200,
593
657
  backdropFilter: 'blur(3px)'
594
658
  })
595
659
  }), /*#__PURE__*/jsxRuntime.jsxs("div", {
660
+ ref: panelRef,
596
661
  role: "dialog",
597
662
  "aria-modal": "true",
598
663
  "aria-label": "Command palette",
664
+ onKeyDown: onPanelKeyDown,
599
665
  className: style.css({
600
666
  position: 'fixed',
601
667
  top: '14vh',
@@ -606,7 +672,7 @@ function CommandPalette({
606
672
  maxHeight: '62vh',
607
673
  display: 'flex',
608
674
  flexDirection: 'column',
609
- backgroundColor: '#ffffff',
675
+ backgroundColor: style.tokenSchema.color.background.canvas,
610
676
  borderRadius: 10,
611
677
  boxShadow: '0 24px 64px rgba(0,0,0,0.22), 0 0 0 1px rgba(0,0,0,0.07)',
612
678
  zIndex: 201,
@@ -618,16 +684,17 @@ function CommandPalette({
618
684
  alignItems: 'center',
619
685
  gap: 10,
620
686
  padding: '12px 16px',
621
- borderBottom: '1px solid #f0f0f0'
687
+ borderBottom: `1px solid ${style.tokenSchema.color.border.muted}`
622
688
  }),
623
689
  children: [/*#__PURE__*/jsxRuntime.jsxs("svg", {
624
690
  width: "15",
625
691
  height: "15",
626
692
  viewBox: "0 0 15 15",
627
693
  fill: "none",
694
+ "aria-hidden": "true",
628
695
  style: {
629
696
  flexShrink: 0,
630
- color: '#a3a3a3'
697
+ color: style.tokenSchema.color.foreground.neutralSecondary
631
698
  },
632
699
  children: [/*#__PURE__*/jsxRuntime.jsx("circle", {
633
700
  cx: "6.5",
@@ -650,16 +717,22 @@ function CommandPalette({
650
717
  onChange: e => setQuery(e.target.value),
651
718
  onKeyDown: onKeyDown,
652
719
  placeholder: "Search commands, lists, actions\u2026",
720
+ role: "combobox",
721
+ "aria-label": "Search commands",
722
+ "aria-expanded": flatList.length > 0,
723
+ "aria-controls": listboxId,
724
+ "aria-autocomplete": "list",
725
+ "aria-activedescendant": activeOptionId,
653
726
  className: style.css({
654
727
  flex: 1,
655
728
  border: 'none',
656
729
  outline: 'none',
657
730
  fontSize: 14.5,
658
- color: '#0a0a0a',
731
+ color: style.tokenSchema.color.foreground.neutralEmphasis,
659
732
  background: 'transparent',
660
733
  fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
661
734
  '&::placeholder': {
662
- color: '#b8b8b8'
735
+ color: style.tokenSchema.color.foreground.neutralSecondary
663
736
  }
664
737
  })
665
738
  }), /*#__PURE__*/jsxRuntime.jsx("kbd", {
@@ -668,11 +741,11 @@ function CommandPalette({
668
741
  alignItems: 'center',
669
742
  gap: 2,
670
743
  padding: '2px 7px',
671
- backgroundColor: '#f5f5f5',
672
- border: '1px solid #e8e8e8',
744
+ backgroundColor: style.tokenSchema.color.background.surfaceSecondary,
745
+ border: `1px solid ${style.tokenSchema.color.border.muted}`,
673
746
  borderRadius: 5,
674
747
  fontSize: 11,
675
- color: '#a3a3a3',
748
+ color: style.tokenSchema.color.foreground.neutralSecondary,
676
749
  fontFamily: 'inherit',
677
750
  flexShrink: 0
678
751
  }),
@@ -680,7 +753,9 @@ function CommandPalette({
680
753
  })]
681
754
  }), /*#__PURE__*/jsxRuntime.jsx("ul", {
682
755
  ref: listRef,
756
+ id: listboxId,
683
757
  role: "listbox",
758
+ "aria-label": "Commands",
684
759
  className: style.css({
685
760
  flex: 1,
686
761
  overflowY: 'auto',
@@ -691,7 +766,7 @@ function CommandPalette({
691
766
  width: 4
692
767
  },
693
768
  '&::-webkit-scrollbar-thumb': {
694
- background: '#e8e8e8',
769
+ background: style.tokenSchema.color.border.muted,
695
770
  borderRadius: 4
696
771
  }
697
772
  }),
@@ -699,124 +774,131 @@ function CommandPalette({
699
774
  className: style.css({
700
775
  padding: '28px 16px',
701
776
  textAlign: 'center',
702
- color: '#a3a3a3',
777
+ color: style.tokenSchema.color.foreground.neutralSecondary,
703
778
  fontSize: 13.5
704
779
  }),
705
780
  children: ["No results for \u201C", query, "\u201D"]
706
- }) : Array.from(grouped.entries()).map(([category, items]) => /*#__PURE__*/jsxRuntime.jsxs("li", {
707
- role: "none",
708
- children: [/*#__PURE__*/jsxRuntime.jsx("p", {
709
- className: style.css({
710
- margin: 0,
711
- padding: '8px 16px 3px',
712
- fontSize: 10,
713
- fontWeight: 600,
714
- letterSpacing: '0.10em',
715
- textTransform: 'uppercase',
716
- color: '#b8b8b8'
717
- }),
718
- children: category
719
- }), /*#__PURE__*/jsxRuntime.jsx("ul", {
720
- role: "group",
721
- className: style.css({
722
- listStyle: 'none',
723
- margin: 0,
724
- padding: 0
725
- }),
726
- children: items.map(cmd => {
727
- var _cmdFlatIndex$get;
728
- const idx = (_cmdFlatIndex$get = cmdFlatIndex.get(cmd.id)) !== null && _cmdFlatIndex$get !== void 0 ? _cmdFlatIndex$get : 0;
729
- const isActive = idx === activeIndex;
730
- return /*#__PURE__*/jsxRuntime.jsxs("li", {
731
- role: "option",
732
- "aria-selected": isActive,
733
- "data-active": isActive,
734
- onClick: () => runCommand(cmd),
735
- onMouseEnter: () => setActiveIndex(idx),
736
- className: style.css({
737
- display: 'flex',
738
- alignItems: 'center',
739
- gap: 10,
740
- padding: '7px 16px',
741
- cursor: 'pointer',
742
- backgroundColor: isActive ? '#0a0a0a' : 'transparent',
743
- transition: 'background 100ms',
744
- '&:hover': {
745
- backgroundColor: isActive ? '#0a0a0a' : '#f5f5f5'
746
- }
747
- }),
748
- children: [/*#__PURE__*/jsxRuntime.jsx("span", {
781
+ }) : Array.from(grouped.entries()).map(([category, items], groupIdx) => {
782
+ const headerId = `${listboxId}-cat-${groupIdx}`;
783
+ return /*#__PURE__*/jsxRuntime.jsxs("li", {
784
+ role: "presentation",
785
+ children: [/*#__PURE__*/jsxRuntime.jsx("p", {
786
+ id: headerId,
787
+ role: "presentation",
788
+ className: style.css({
789
+ margin: 0,
790
+ padding: '8px 16px 3px',
791
+ fontSize: 10,
792
+ fontWeight: 600,
793
+ letterSpacing: '0.10em',
794
+ textTransform: 'uppercase',
795
+ color: style.tokenSchema.color.foreground.neutralSecondary
796
+ }),
797
+ children: category
798
+ }), /*#__PURE__*/jsxRuntime.jsx("ul", {
799
+ role: "group",
800
+ "aria-labelledby": headerId,
801
+ className: style.css({
802
+ listStyle: 'none',
803
+ margin: 0,
804
+ padding: 0
805
+ }),
806
+ children: items.map(cmd => {
807
+ var _cmdFlatIndex$get;
808
+ const idx = (_cmdFlatIndex$get = cmdFlatIndex.get(cmd.id)) !== null && _cmdFlatIndex$get !== void 0 ? _cmdFlatIndex$get : 0;
809
+ const isActive = idx === activeIndex;
810
+ return /*#__PURE__*/jsxRuntime.jsxs("li", {
811
+ id: optionId(cmd.id),
812
+ role: "option",
813
+ "aria-selected": isActive,
814
+ "data-active": isActive,
815
+ onClick: () => runCommand(cmd),
816
+ onMouseEnter: () => setActiveIndex(idx),
749
817
  className: style.css({
750
- display: 'inline-flex',
818
+ display: 'flex',
751
819
  alignItems: 'center',
752
- justifyContent: 'center',
753
- width: 28,
754
- height: 28,
755
- borderRadius: 6,
756
- backgroundColor: isActive ? '#2a2a2a' : '#f0f0f0',
757
- fontSize: 12,
758
- fontWeight: 500,
759
- color: isActive ? '#ffffff' : '#636363',
760
- flexShrink: 0,
761
- transition: 'background 100ms, color 100ms',
762
- fontFamily: 'monospace'
763
- }),
764
- children: cmd.icon
765
- }), /*#__PURE__*/jsxRuntime.jsxs("span", {
766
- className: style.css({
767
- flex: 1,
768
- minWidth: 0
820
+ gap: 10,
821
+ padding: '7px 16px',
822
+ cursor: 'pointer',
823
+ backgroundColor: isActive ? style.tokenSchema.color.scale.black : 'transparent',
824
+ transition: 'background 100ms',
825
+ '&:hover': {
826
+ backgroundColor: isActive ? style.tokenSchema.color.scale.black : style.tokenSchema.color.background.surfaceSecondary
827
+ }
769
828
  }),
770
829
  children: [/*#__PURE__*/jsxRuntime.jsx("span", {
771
830
  className: style.css({
772
- display: 'block',
773
- fontSize: 13.5,
831
+ display: 'inline-flex',
832
+ alignItems: 'center',
833
+ justifyContent: 'center',
834
+ width: 28,
835
+ height: 28,
836
+ borderRadius: 6,
837
+ backgroundColor: isActive ? '#2a2a2a' : style.tokenSchema.color.background.surfaceSecondary,
838
+ fontSize: 12,
774
839
  fontWeight: 500,
775
- color: isActive ? '#ffffff' : '#0a0a0a',
776
- whiteSpace: 'nowrap',
777
- overflow: 'hidden',
778
- textOverflow: 'ellipsis',
779
- transition: 'color 100ms'
840
+ color: isActive ? style.tokenSchema.color.foreground.onEmphasis : style.tokenSchema.color.foreground.neutralSecondary,
841
+ flexShrink: 0,
842
+ transition: 'background 100ms, color 100ms',
843
+ fontFamily: 'monospace'
780
844
  }),
781
- children: cmd.label
782
- }), cmd.description && /*#__PURE__*/jsxRuntime.jsx("span", {
845
+ children: cmd.icon
846
+ }), /*#__PURE__*/jsxRuntime.jsxs("span", {
783
847
  className: style.css({
784
- display: 'block',
785
- fontSize: 12,
786
- color: isActive ? 'rgba(255,255,255,0.5)' : '#a3a3a3',
787
- whiteSpace: 'nowrap',
788
- overflow: 'hidden',
789
- textOverflow: 'ellipsis',
790
- transition: 'color 100ms'
848
+ flex: 1,
849
+ minWidth: 0
850
+ }),
851
+ children: [/*#__PURE__*/jsxRuntime.jsx("span", {
852
+ className: style.css({
853
+ display: 'block',
854
+ fontSize: 13.5,
855
+ fontWeight: 500,
856
+ color: isActive ? style.tokenSchema.color.foreground.onEmphasis : style.tokenSchema.color.foreground.neutralEmphasis,
857
+ whiteSpace: 'nowrap',
858
+ overflow: 'hidden',
859
+ textOverflow: 'ellipsis',
860
+ transition: 'color 100ms'
861
+ }),
862
+ children: cmd.label
863
+ }), cmd.description && /*#__PURE__*/jsxRuntime.jsx("span", {
864
+ className: style.css({
865
+ display: 'block',
866
+ fontSize: 12,
867
+ color: isActive ? style.tokenSchema.color.foreground.inverseSecondary : style.tokenSchema.color.foreground.neutralSecondary,
868
+ whiteSpace: 'nowrap',
869
+ overflow: 'hidden',
870
+ textOverflow: 'ellipsis',
871
+ transition: 'color 100ms'
872
+ }),
873
+ children: cmd.description
874
+ })]
875
+ }), isActive && /*#__PURE__*/jsxRuntime.jsx("kbd", {
876
+ className: style.css({
877
+ fontSize: 11,
878
+ color: style.tokenSchema.color.foreground.inverseSecondary,
879
+ background: '#2a2a2a',
880
+ border: '1px solid #3a3a3a',
881
+ borderRadius: 4,
882
+ padding: '2px 6px',
883
+ fontFamily: 'inherit',
884
+ flexShrink: 0
791
885
  }),
792
- children: cmd.description
886
+ children: "\u21B5"
793
887
  })]
794
- }), isActive && /*#__PURE__*/jsxRuntime.jsx("kbd", {
795
- className: style.css({
796
- fontSize: 11,
797
- color: 'rgba(255,255,255,0.6)',
798
- background: '#2a2a2a',
799
- border: '1px solid #3a3a3a',
800
- borderRadius: 4,
801
- padding: '2px 6px',
802
- fontFamily: 'inherit',
803
- flexShrink: 0
804
- }),
805
- children: "\u21B5"
806
- })]
807
- }, cmd.id);
808
- })
809
- })]
810
- }, category))
888
+ }, cmd.id);
889
+ })
890
+ })]
891
+ }, category);
892
+ })
811
893
  }), /*#__PURE__*/jsxRuntime.jsxs("div", {
812
894
  className: style.css({
813
895
  display: 'flex',
814
896
  alignItems: 'center',
815
897
  gap: 14,
816
898
  padding: '8px 16px',
817
- borderTop: '1px solid #f0f0f0',
899
+ borderTop: `1px solid ${style.tokenSchema.color.border.muted}`,
818
900
  fontSize: 11,
819
- color: '#b8b8b8'
901
+ color: style.tokenSchema.color.foreground.neutralSecondary
820
902
  }),
821
903
  children: [/*#__PURE__*/jsxRuntime.jsxs("span", {
822
904
  children: [/*#__PURE__*/jsxRuntime.jsx("kbd", {
@@ -863,13 +945,54 @@ function PageWrapper(props) {
863
945
  display: 'flex',
864
946
  height: '100vh',
865
947
  overflow: 'hidden',
866
- backgroundColor: '#fafafa',
948
+ backgroundColor: style.tokenSchema.color.background.surface,
867
949
  fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
868
950
  }),
869
951
  ...props
870
952
  });
871
953
  }
872
954
 
955
+ // ---- Skip to content (WCAG 2.4.1 Bypass Blocks) ----
956
+ function SkipLink() {
957
+ return /*#__PURE__*/jsxRuntime.jsx("a", {
958
+ href: "#main-content",
959
+ className: style.css({
960
+ // Visually hidden, off-screen by default — kept out of the layout for mouse users.
961
+ position: 'absolute',
962
+ top: 0,
963
+ left: 0,
964
+ width: 1,
965
+ height: 1,
966
+ padding: 0,
967
+ margin: -1,
968
+ overflow: 'hidden',
969
+ clip: 'rect(0 0 0 0)',
970
+ whiteSpace: 'nowrap',
971
+ border: 0,
972
+ zIndex: 60,
973
+ // Reveal on keyboard focus.
974
+ '&:focus, &:focus-visible': {
975
+ width: 'auto',
976
+ height: 'auto',
977
+ padding: '10px 16px',
978
+ margin: 8,
979
+ clip: 'auto',
980
+ overflow: 'visible',
981
+ backgroundColor: style.tokenSchema.color.background.canvas,
982
+ color: style.tokenSchema.color.foreground.neutralEmphasis,
983
+ borderRadius: 6,
984
+ boxShadow: `0 2px 8px ${style.tokenSchema.color.shadow.regular}`,
985
+ outline: `2px solid ${style.tokenSchema.color.foreground.neutralEmphasis}`,
986
+ outlineOffset: 2,
987
+ textDecoration: 'none',
988
+ fontSize: 14,
989
+ fontWeight: 500
990
+ }
991
+ }),
992
+ children: "Skip to content"
993
+ });
994
+ }
995
+
873
996
  // ---- Mobile overlay ----
874
997
  function Backdrop({
875
998
  isVisible,
@@ -881,7 +1004,7 @@ function Backdrop({
881
1004
  className: style.css({
882
1005
  position: 'fixed',
883
1006
  inset: 0,
884
- backgroundColor: 'rgba(10,10,10,0.35)',
1007
+ backgroundColor: style.tokenSchema.color.alias.blanket,
885
1008
  zIndex: 40,
886
1009
  transition: 'opacity 200ms',
887
1010
  opacity: isVisible ? 1 : 0,
@@ -895,16 +1018,91 @@ function Backdrop({
895
1018
  }
896
1019
 
897
1020
  // ---- Sidebar — pure white, precise ----
1021
+ const FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),[tabindex]:not([tabindex="-1"])';
898
1022
  function Sidebar({
899
1023
  isOpen,
900
1024
  onClose,
901
1025
  onCmdK
902
1026
  }) {
1027
+ const asideRef = react.useRef(null);
1028
+ // Element to restore focus to (the hamburger) once the drawer closes.
1029
+ const lastFocusedRef = react.useRef(null);
1030
+
1031
+ // Mobile drawer a11y: focus trap, Escape-to-close, initial focus, and focus
1032
+ // restore. `isOpen` is only ever true on mobile (the resize handler forces it
1033
+ // false at >=768px), so gating on it doubles as a mobile gate.
1034
+ react.useEffect(() => {
1035
+ if (!isOpen) return;
1036
+ const aside = asideRef.current;
1037
+ if (!aside) return;
1038
+
1039
+ // Save the currently focused element so we can restore it on close.
1040
+ try {
1041
+ const active = document.activeElement;
1042
+ lastFocusedRef.current = active instanceof HTMLElement ? active : null;
1043
+ } catch {
1044
+ lastFocusedRef.current = null;
1045
+ }
1046
+ const getFocusable = () => {
1047
+ try {
1048
+ return Array.from(aside.querySelectorAll(FOCUSABLE_SELECTOR)).filter(el => el.offsetParent !== null || el === document.activeElement);
1049
+ } catch {
1050
+ return [];
1051
+ }
1052
+ };
1053
+
1054
+ // Move initial focus into the drawer.
1055
+ try {
1056
+ var _getFocusable$;
1057
+ (_getFocusable$ = getFocusable()[0]) === null || _getFocusable$ === void 0 || _getFocusable$.focus();
1058
+ } catch {
1059
+ /* noop */
1060
+ }
1061
+ function onKeyDown(e) {
1062
+ if (e.key === 'Escape') {
1063
+ e.preventDefault();
1064
+ onClose();
1065
+ return;
1066
+ }
1067
+ if (e.key !== 'Tab') return;
1068
+ const focusable = getFocusable();
1069
+ if (focusable.length === 0) {
1070
+ // Nothing focusable inside — keep focus from escaping anyway.
1071
+ e.preventDefault();
1072
+ return;
1073
+ }
1074
+ const first = focusable[0];
1075
+ const last = focusable[focusable.length - 1];
1076
+ const current = document.activeElement;
1077
+ if (e.shiftKey) {
1078
+ if (current === first || !(aside !== null && aside !== void 0 && aside.contains(current))) {
1079
+ e.preventDefault();
1080
+ last.focus();
1081
+ }
1082
+ } else if (current === last || !(aside !== null && aside !== void 0 && aside.contains(current))) {
1083
+ e.preventDefault();
1084
+ first.focus();
1085
+ }
1086
+ }
1087
+ document.addEventListener('keydown', onKeyDown, true);
1088
+ return () => {
1089
+ document.removeEventListener('keydown', onKeyDown, true);
1090
+ // Restore focus to the trigger that opened the drawer.
1091
+ try {
1092
+ var _lastFocusedRef$curre;
1093
+ (_lastFocusedRef$curre = lastFocusedRef.current) === null || _lastFocusedRef$curre === void 0 || _lastFocusedRef$curre.focus();
1094
+ } catch {
1095
+ /* noop */
1096
+ }
1097
+ };
1098
+ }, [isOpen, onClose]);
903
1099
  return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
904
1100
  children: [/*#__PURE__*/jsxRuntime.jsx(Backdrop, {
905
1101
  isVisible: isOpen,
906
1102
  onClick: onClose
907
1103
  }), /*#__PURE__*/jsxRuntime.jsxs("aside", {
1104
+ id: "nixxie-sidebar",
1105
+ ref: asideRef,
908
1106
  className: style.css({
909
1107
  position: 'fixed',
910
1108
  top: 0,
@@ -914,13 +1112,18 @@ function Sidebar({
914
1112
  zIndex: 50,
915
1113
  display: 'flex',
916
1114
  flexDirection: 'column',
917
- backgroundColor: '#ffffff',
918
- borderRight: '1px solid #ebebeb',
1115
+ backgroundColor: style.tokenSchema.color.background.canvas,
1116
+ borderRight: `1px solid ${style.tokenSchema.color.border.muted}`,
919
1117
  transform: isOpen ? 'translateX(0)' : `translateX(-${SIDEBAR_WIDTH}px)`,
1118
+ // Closed off-canvas drawer must leave the tab order / AT tree on
1119
+ // mobile; `visibility:hidden` removes its descendants from both.
1120
+ visibility: isOpen ? 'visible' : 'hidden',
920
1121
  transition: 'transform 220ms cubic-bezier(0.4,0,0.2,1)',
921
1122
  '@media (min-width: 768px)': {
922
1123
  position: 'relative',
923
1124
  transform: 'translateX(0)',
1125
+ // Persistent desktop rail is always reachable.
1126
+ visibility: 'visible',
924
1127
  flexShrink: 0
925
1128
  }
926
1129
  }),
@@ -930,7 +1133,7 @@ function Sidebar({
930
1133
  alignItems: 'center',
931
1134
  height: TOPBAR_HEIGHT,
932
1135
  paddingInline: '16px',
933
- borderBottom: '1px solid #f2f2f2',
1136
+ borderBottom: `1px solid ${style.tokenSchema.color.border.muted}`,
934
1137
  flexShrink: 0
935
1138
  }),
936
1139
  children: /*#__PURE__*/jsxRuntime.jsx(Logo, {})
@@ -944,7 +1147,7 @@ function Sidebar({
944
1147
  width: 3
945
1148
  },
946
1149
  '&::-webkit-scrollbar-thumb': {
947
- background: '#ebebeb',
1150
+ background: style.tokenSchema.color.border.muted,
948
1151
  borderRadius: 3
949
1152
  }
950
1153
  }),
@@ -970,8 +1173,8 @@ function TopBar({
970
1173
  height: TOPBAR_HEIGHT,
971
1174
  paddingInline: '24px',
972
1175
  gap: '14px',
973
- backgroundColor: '#ffffff',
974
- borderBottom: '1px solid #ebebeb',
1176
+ backgroundColor: style.tokenSchema.color.background.canvas,
1177
+ borderBottom: `1px solid ${style.tokenSchema.color.border.muted}`,
975
1178
  flexShrink: 0,
976
1179
  zIndex: 30
977
1180
  }),
@@ -979,6 +1182,7 @@ function TopBar({
979
1182
  onClick: onMenuClick,
980
1183
  "aria-label": isSidebarOpen ? 'Close menu' : 'Open menu',
981
1184
  "aria-expanded": isSidebarOpen,
1185
+ "aria-controls": "nixxie-sidebar",
982
1186
  className: style.css({
983
1187
  display: 'inline-flex',
984
1188
  alignItems: 'center',
@@ -986,15 +1190,15 @@ function TopBar({
986
1190
  width: 32,
987
1191
  height: 32,
988
1192
  borderRadius: 6,
989
- border: '1px solid #ebebeb',
1193
+ border: `1px solid ${style.tokenSchema.color.border.muted}`,
990
1194
  background: 'transparent',
991
1195
  cursor: 'pointer',
992
1196
  flexShrink: 0,
993
- color: '#a3a3a3',
1197
+ color: style.tokenSchema.color.foreground.neutralTertiary,
994
1198
  transition: 'color 130ms, background 130ms',
995
1199
  '&:hover': {
996
- background: '#f5f5f5',
997
- color: '#0a0a0a'
1200
+ background: style.tokenSchema.color.background.surfaceSecondary,
1201
+ color: style.tokenSchema.color.foreground.neutralEmphasis
998
1202
  },
999
1203
  '@media (min-width: 768px)': {
1000
1204
  display: 'none'
@@ -1055,6 +1259,8 @@ function TopBar({
1055
1259
  // ---- Main content area ----
1056
1260
  function MainContent(props) {
1057
1261
  return /*#__PURE__*/jsxRuntime.jsx("main", {
1262
+ id: "main-content",
1263
+ tabIndex: -1,
1058
1264
  className: style.css({
1059
1265
  flex: 1,
1060
1266
  display: 'flex',
@@ -1078,11 +1284,11 @@ function ContentScroller(props) {
1078
1284
  width: 5
1079
1285
  },
1080
1286
  '&::-webkit-scrollbar-thumb': {
1081
- background: '#e8e8e8',
1287
+ background: style.tokenSchema.color.border.muted,
1082
1288
  borderRadius: 4
1083
1289
  },
1084
1290
  '&::-webkit-scrollbar-thumb:hover': {
1085
- background: '#d4d4d4'
1291
+ background: style.tokenSchema.color.border.emphasis
1086
1292
  }
1087
1293
  }),
1088
1294
  ...props
@@ -1121,7 +1327,7 @@ function PageContainer({
1121
1327
  children: /*#__PURE__*/jsxRuntime.jsx("title", {
1122
1328
  children: title ? `Nixxie – ${title}` : 'Nixxie'
1123
1329
  }, "title")
1124
- }), /*#__PURE__*/jsxRuntime.jsx(Sidebar, {
1330
+ }), /*#__PURE__*/jsxRuntime.jsx(SkipLink, {}), /*#__PURE__*/jsxRuntime.jsx(Sidebar, {
1125
1331
  isOpen: isSidebarOpen,
1126
1332
  onClose: () => setSidebarOpen(false),
1127
1333
  onCmdK: () => setCmdOpen(v => !v)