@sarj/docs-ui 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -8,15 +8,17 @@ Shared Astro and Starlight reference UI for Sarj standards sites
8
8
  npm install --save-dev @sarj/docs-ui
9
9
  ```
10
10
 
11
- Import `@sarj/docs-ui/styles.css` once, then compose the typed Astro components. The live component and theme contract is published at [code-standards.sarj.ai/design-system/](https://code-standards.sarj.ai/design-system/).
11
+ Import `@sarj/docs-ui/starlight.css` once, then compose the typed Astro components. The live component and theme contract is published at [docs-ui.sarj.ai](https://docs-ui.sarj.ai/).
12
12
 
13
13
  Public exports:
14
14
 
15
15
  - `@sarj/docs-ui/Breadcrumbs.astro`
16
16
  - `@sarj/docs-ui/PageAnchor.astro`
17
17
  - `@sarj/docs-ui/ReferencePage.astro`
18
+ - `@sarj/docs-ui/RulePager.astro`
18
19
  - `@sarj/docs-ui/catalog`
19
20
  - `@sarj/docs-ui/contracts`
21
+ - `@sarj/docs-ui/starlight.css`
20
22
  - `@sarj/docs-ui/styles.css`
21
23
 
22
- [Documentation](https://code-standards.sarj.ai/) · [Source](https://github.com/sarj-ai/code-standards)
24
+ [Documentation](https://docs-ui.sarj.ai/) · [Source](https://github.com/sarj-ai/code-standards)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/docs-ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "packageManager": "npm@12.0.2",
5
5
  "description": "Shared Astro and Starlight reference UI for Sarj standards sites",
6
6
  "type": "module",
@@ -9,10 +9,12 @@
9
9
  },
10
10
  "exports": {
11
11
  "./styles.css": "./src/styles/theme.css",
12
+ "./starlight.css": "./src/styles/starlight.css",
12
13
  "./catalog": "./src/catalog.ts",
13
14
  "./contracts": "./src/contracts.ts",
14
15
  "./PageAnchor.astro": "./src/components/PageAnchor.astro",
15
16
  "./ReferencePage.astro": "./src/components/ReferencePage.astro",
17
+ "./RulePager.astro": "./src/components/RulePager.astro",
16
18
  "./Breadcrumbs.astro": "./src/components/Breadcrumbs.astro"
17
19
  },
18
20
  "files": [
@@ -21,7 +23,8 @@
21
23
  "LICENSE"
22
24
  ],
23
25
  "sideEffects": [
24
- "./src/styles/theme.css"
26
+ "./src/styles/theme.css",
27
+ "./src/styles/starlight.css"
25
28
  ],
26
29
  "publishConfig": {
27
30
  "access": "public"
@@ -31,7 +34,7 @@
31
34
  "url": "git+https://github.com/sarj-ai/code-standards.git",
32
35
  "directory": "packages/docs-ui"
33
36
  },
34
- "homepage": "https://code-standards.sarj.ai/",
37
+ "homepage": "https://docs-ui.sarj.ai/",
35
38
  "bugs": {
36
39
  "url": "https://github.com/sarj-ai/code-standards/issues"
37
40
  },
package/src/catalog.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BreadcrumbsProps, ReferencePageProps } from './contracts';
1
+ import type { BreadcrumbsProps, ReferencePageProps, RulePagerProps } from './contracts';
2
2
 
3
3
  export interface ComponentDefinition<Props extends object = object> {
4
4
  exportPath: string;
@@ -9,6 +9,8 @@ export interface ComponentDefinition<Props extends object = object> {
9
9
  export interface ThemeTokenDefinition {
10
10
  cssName: `--sarj-${string}`;
11
11
  purpose: string;
12
+ light: `#${string}`;
13
+ dark: `#${string}`;
12
14
  }
13
15
 
14
16
  export const componentCatalog = Object.freeze({
@@ -27,25 +29,31 @@ export const componentCatalog = Object.freeze({
27
29
  } satisfies ComponentDefinition,
28
30
  ReferencePage: {
29
31
  exportPath: '@sarj/docs-ui/ReferencePage.astro',
30
- purpose: 'Render a Starlight reference shell with explicit search and robots behavior.',
32
+ purpose: 'Render a Starlight reference shell with explicit robots behavior.',
31
33
  properties: {
32
34
  title: 'Document title and primary accessible page identity.',
33
35
  description: 'Concise page description used by document metadata.',
34
36
  sidebar: 'Starlight sidebar definition for this reference surface.',
35
- searchable: 'Whether Pagefind indexes the page body; defaults to true.',
36
37
  indexable: 'Whether robots may index the public page; defaults to true.',
37
- discovery: 'Deprecated shorthand retained for compatibility; never access control.',
38
38
  hasSidebar: 'Whether the Starlight sidebar is rendered; defaults to true.',
39
39
  template: 'Starlight document or splash template; defaults to doc.',
40
40
  },
41
41
  } satisfies ComponentDefinition<ReferencePageProps>,
42
+ RulePager: {
43
+ exportPath: '@sarj/docs-ui/RulePager.astro',
44
+ purpose: 'Navigate between adjacent rules with accessible links and guarded arrow-key shortcuts.',
45
+ properties: {
46
+ previous: 'Optional previous rule link and label.',
47
+ next: 'Optional next rule link and label.',
48
+ },
49
+ } satisfies ComponentDefinition<RulePagerProps>,
42
50
  });
43
51
 
44
52
  export const themeTokenCatalog = Object.freeze([
45
- { cssName: '--sarj-color-report', purpose: 'Diagnostic and rejection emphasis.' },
46
- { cssName: '--sarj-color-pass', purpose: 'Accepted example and success emphasis.' },
47
- { cssName: '--sarj-color-warning', purpose: 'Warning-level diagnostic emphasis with AA text contrast.' },
48
- { cssName: '--sarj-color-rule', purpose: 'Structural borders and separators.' },
49
- { cssName: '--sarj-color-paper', purpose: 'Reference surface background.' },
50
- { cssName: '--sarj-color-ink', purpose: 'Primary reference text.' },
53
+ { cssName: '--sarj-color-report', purpose: 'Diagnostic and rejection emphasis.', light: '#c34453', dark: '#ff8794' },
54
+ { cssName: '--sarj-color-pass', purpose: 'Accepted example and success emphasis.', light: '#16806d', dark: '#62d5bd' },
55
+ { cssName: '--sarj-color-warning', purpose: 'Warning-level diagnostic emphasis with AA text contrast.', light: '#986a00', dark: '#a97708' },
56
+ { cssName: '--sarj-color-rule', purpose: 'Structural borders and separators.', light: '#e4e6eb', dark: '#272a32' },
57
+ { cssName: '--sarj-color-paper', purpose: 'Reference surface background.', light: '#ffffff', dark: '#0b0c10' },
58
+ { cssName: '--sarj-color-ink', purpose: 'Primary reference text.', light: '#12141a', dark: '#f4f5f8' },
51
59
  ] satisfies readonly ThemeTokenDefinition[]);
@@ -4,7 +4,7 @@ import type { BreadcrumbsProps as Props } from '../contracts';
4
4
  const { ancestors, current } = Astro.props;
5
5
  ---
6
6
 
7
- <nav class="breadcrumbs" aria-label="Breadcrumb">
7
+ <nav class="breadcrumbs sarj-breadcrumbs" aria-label="Breadcrumb">
8
8
  <ol>
9
9
  {ancestors.map((ancestor) => <li><a href={ancestor.href}>{ancestor.label}</a></li>)}
10
10
  <li><span aria-current="page">{current}</span></li>
@@ -6,14 +6,11 @@ const {
6
6
  title,
7
7
  description,
8
8
  sidebar,
9
- searchable: searchableProp,
10
9
  indexable: indexableProp,
11
- discovery,
12
10
  hasSidebar = true,
13
11
  template = 'doc',
14
12
  } = Astro.props;
15
- const searchable = searchableProp ?? (discovery === undefined || discovery === 'searchable');
16
- const indexable = indexableProp ?? discovery !== 'unlisted';
13
+ const indexable = indexableProp ?? true;
17
14
  ---
18
15
 
19
16
  <StarlightPage
@@ -21,7 +18,7 @@ const indexable = indexableProp ?? discovery !== 'unlisted';
21
18
  title,
22
19
  description,
23
20
  template,
24
- pagefind: searchable,
21
+ pagefind: false,
25
22
  prev: false,
26
23
  next: false,
27
24
  head: indexable ? [] : [{ tag: 'meta', attrs: { name: 'robots', content: 'noindex, nofollow' } }],
@@ -29,7 +26,7 @@ const indexable = indexableProp ?? discovery !== 'unlisted';
29
26
  sidebar={sidebar}
30
27
  hasSidebar={hasSidebar}
31
28
  >
32
- <div data-pagefind-body={searchable ? true : undefined} data-pagefind-ignore={searchable ? undefined : true}>
29
+ <div>
33
30
  <slot />
34
31
  </div>
35
32
  </StarlightPage>
@@ -0,0 +1,32 @@
1
+ ---
2
+ import type { RulePagerProps as Props } from '../contracts';
3
+
4
+ const { previous, next } = Astro.props;
5
+ ---
6
+
7
+ <nav class="sarj-rule-pager" aria-label="Rule navigation">
8
+ {previous && (
9
+ <a href={previous.href} rel="prev" data-rule-previous aria-keyshortcuts="ArrowLeft" aria-label={`Previous rule: ${previous.label}`} title={previous.label}>
10
+ <kbd aria-hidden="true">←</kbd><span>Previous</span>
11
+ </a>
12
+ )}
13
+ {next && (
14
+ <a href={next.href} rel="next" data-rule-next aria-keyshortcuts="ArrowRight" aria-label={`Next rule: ${next.label}`} title={next.label}>
15
+ <span>Next</span><kbd aria-hidden="true">→</kbd>
16
+ </a>
17
+ )}
18
+ </nav>
19
+
20
+ <script>
21
+ document.addEventListener('keydown', (event) => {
22
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
23
+ if (event.target instanceof Element && event.target.closest('a, button, input, select, textarea, summary, [contenteditable]')) return;
24
+ const selector = event.key === 'ArrowLeft' ? '[data-rule-previous]' : event.key === 'ArrowRight' ? '[data-rule-next]' : null;
25
+ if (!selector) return;
26
+ const link = document.querySelector(selector);
27
+ if (link instanceof HTMLAnchorElement) {
28
+ event.preventDefault();
29
+ location.assign(link.href);
30
+ }
31
+ });
32
+ </script>
package/src/contracts.ts CHANGED
@@ -12,25 +12,26 @@ export interface BreadcrumbsProps {
12
12
  current: string;
13
13
  }
14
14
 
15
- export type ReferenceSidebar = NonNullable<StarlightUserConfig['sidebar']>;
15
+ export interface RulePagerLink {
16
+ href: string;
17
+ label: string;
18
+ }
16
19
 
17
- /**
18
- * Legacy discovery shorthand. `unlisted` affects search and robots metadata;
19
- * it is not access control and the rendered page remains public.
20
- */
21
- export type ReferenceDiscovery = 'searchable' | 'navigation-only' | 'unlisted';
20
+ /** Public properties accepted by {@link RulePager}. */
21
+ export interface RulePagerProps {
22
+ previous?: RulePagerLink | null;
23
+ next?: RulePagerLink | null;
24
+ }
25
+
26
+ export type ReferenceSidebar = NonNullable<StarlightUserConfig['sidebar']>;
22
27
 
23
28
  /** Public properties accepted by {@link ReferencePage}. */
24
29
  export interface ReferencePageProps {
25
30
  title: string;
26
31
  description: string;
27
32
  sidebar: ReferenceSidebar;
28
- /** Include the page body in Pagefind. Defaults to true. */
29
- searchable?: boolean;
30
33
  /** Allow search engines to index the page. Defaults to true. */
31
34
  indexable?: boolean;
32
- /** @deprecated Prefer the explicit `searchable` and `indexable` properties. */
33
- discovery?: ReferenceDiscovery;
34
35
  hasSidebar?: boolean;
35
36
  template?: 'doc' | 'splash';
36
37
  }
@@ -0,0 +1,73 @@
1
+ @import './theme.css';
2
+
3
+ .main-pane,
4
+ .main-frame,
5
+ .sl-container {
6
+ min-width: 0;
7
+ }
8
+
9
+ .main-pane {
10
+ width: 100%;
11
+ }
12
+
13
+ .content-panel:has(> .reference-page-anchor:only-child) {
14
+ display: none;
15
+ }
16
+
17
+ header.header {
18
+ border-bottom: 1px solid var(--sarj-color-rule);
19
+ box-shadow: none;
20
+ }
21
+
22
+ .sidebar-pane a,
23
+ .social-icons a,
24
+ starlight-menu-button button {
25
+ min-width: 44px;
26
+ min-height: 44px;
27
+ }
28
+
29
+ .sidebar-content a[aria-current='page'] {
30
+ color: var(--sl-color-accent-high);
31
+ }
32
+
33
+ .sarj-rule-pager {
34
+ display: flex;
35
+ flex: 0 0 auto;
36
+ gap: 0.35rem;
37
+ margin: 0;
38
+ }
39
+
40
+ .sarj-rule-pager a {
41
+ display: inline-flex;
42
+ min-height: 44px;
43
+ align-items: center;
44
+ gap: 0.3rem;
45
+ padding: 0.35rem 0.55rem;
46
+ border: 1px solid var(--sarj-color-rule);
47
+ border-radius: 0.4rem;
48
+ color: var(--sl-color-white);
49
+ font-size: 0.75rem;
50
+ text-decoration: none;
51
+ }
52
+
53
+ .sarj-rule-pager a:hover,
54
+ .sarj-rule-pager a:focus-visible {
55
+ border-color: var(--sl-color-accent);
56
+ }
57
+
58
+ .sarj-rule-pager kbd {
59
+ padding: 0.05rem 0.28rem;
60
+ border: 1px solid var(--sarj-color-rule);
61
+ border-radius: 0.2rem;
62
+ background: var(--sl-color-gray-6);
63
+ font-family: var(--sl-font-mono);
64
+ }
65
+
66
+ @media (max-width: 22rem) {
67
+ .sarj-rule-pager a { width: 44px; justify-content: center; padding-inline: 0; }
68
+ .sarj-rule-pager a span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
69
+ }
70
+
71
+ @media (prefers-reduced-motion: reduce) {
72
+ :root { scroll-behavior: auto; }
73
+ }
@@ -48,3 +48,39 @@
48
48
  --sarj-color-paper: #0b0c10;
49
49
  --sarj-color-ink: #f4f5f8;
50
50
  }
51
+
52
+ .breadcrumbs ol {
53
+ display: flex;
54
+ flex-wrap: wrap;
55
+ gap: 0.35rem;
56
+ margin: 0;
57
+ padding: 0;
58
+ list-style: none;
59
+ }
60
+
61
+ .breadcrumbs li {
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: 0.35rem;
65
+ min-width: 0;
66
+ }
67
+
68
+ .breadcrumbs li + li::before {
69
+ color: var(--sl-color-gray-3);
70
+ content: '/';
71
+ }
72
+
73
+ .breadcrumbs a,
74
+ .breadcrumbs [aria-current='page'] {
75
+ overflow-wrap: anywhere;
76
+ }
77
+
78
+ .breadcrumbs a {
79
+ border-radius: 0.2rem;
80
+ color: var(--sl-color-accent-high);
81
+ }
82
+
83
+ .breadcrumbs a:focus-visible {
84
+ outline: 2px solid var(--sl-color-accent);
85
+ outline-offset: 3px;
86
+ }