@rnine/nineos 0.0.0-stage → 0.1.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 (92) hide show
  1. package/README.md +93 -2
  2. package/dist/_variables.scss +199 -0
  3. package/dist/components/Chrome/Chrome.d.ts +27 -0
  4. package/dist/components/Chrome/Chrome.js +45 -0
  5. package/dist/components/Chrome/Chrome.js.map +1 -0
  6. package/dist/components/Chrome/Chrome.module.js +14 -0
  7. package/dist/components/Chrome/Chrome.module.js.map +1 -0
  8. package/dist/components/CommandLine/ChipBar.d.ts +20 -0
  9. package/dist/components/CommandLine/ChipBar.js +25 -0
  10. package/dist/components/CommandLine/ChipBar.js.map +1 -0
  11. package/dist/components/CommandLine/ChipBar.module.js +9 -0
  12. package/dist/components/CommandLine/ChipBar.module.js.map +1 -0
  13. package/dist/components/CommandLine/CommandLine.d.ts +38 -0
  14. package/dist/components/CommandLine/CommandLine.js +121 -0
  15. package/dist/components/CommandLine/CommandLine.js.map +1 -0
  16. package/dist/components/CommandLine/CommandLine.module.js +13 -0
  17. package/dist/components/CommandLine/CommandLine.module.js.map +1 -0
  18. package/dist/components/Hint/Hint.d.ts +22 -0
  19. package/dist/components/Hint/Hint.js +52 -0
  20. package/dist/components/Hint/Hint.js.map +1 -0
  21. package/dist/components/Hint/Hint.module.js +11 -0
  22. package/dist/components/Hint/Hint.module.js.map +1 -0
  23. package/dist/components/Panel/PanelStage.d.ts +19 -0
  24. package/dist/components/Panel/PanelStage.js +67 -0
  25. package/dist/components/Panel/PanelStage.js.map +1 -0
  26. package/dist/components/Panel/PanelStage.module.js +10 -0
  27. package/dist/components/Panel/PanelStage.module.js.map +1 -0
  28. package/dist/components.css +2 -0
  29. package/dist/index.d.ts +10 -0
  30. package/dist/index.js +9 -0
  31. package/dist/motion/DecryptText.d.ts +9 -0
  32. package/dist/motion/DecryptText.js +87 -0
  33. package/dist/motion/DecryptText.js.map +1 -0
  34. package/dist/motion/DecryptText.module.js +12 -0
  35. package/dist/motion/DecryptText.module.js.map +1 -0
  36. package/dist/motion/DotField.d.ts +8 -0
  37. package/dist/motion/DotField.js +116 -0
  38. package/dist/motion/DotField.js.map +1 -0
  39. package/dist/motion/DotField.module.js +8 -0
  40. package/dist/motion/DotField.module.js.map +1 -0
  41. package/dist/motion/Monogram.d.ts +7 -0
  42. package/dist/motion/Monogram.js +40 -0
  43. package/dist/motion/Monogram.js.map +1 -0
  44. package/dist/motion/Monogram.module.js +10 -0
  45. package/dist/motion/Monogram.module.js.map +1 -0
  46. package/dist/motion/PageEffects.d.ts +2 -0
  47. package/dist/motion/PageEffects.js +45 -0
  48. package/dist/motion/PageEffects.js.map +1 -0
  49. package/dist/motion/PageEffects.module.js +8 -0
  50. package/dist/motion/PageEffects.module.js.map +1 -0
  51. package/dist/motion/Terminal.d.ts +12 -0
  52. package/dist/motion/Terminal.js +57 -0
  53. package/dist/motion/Terminal.js.map +1 -0
  54. package/dist/motion/Terminal.module.js +12 -0
  55. package/dist/motion/Terminal.module.js.map +1 -0
  56. package/dist/motion/VelocityStrip.d.ts +7 -0
  57. package/dist/motion/VelocityStrip.js +46 -0
  58. package/dist/motion/VelocityStrip.js.map +1 -0
  59. package/dist/motion/VelocityStrip.module.js +9 -0
  60. package/dist/motion/VelocityStrip.module.js.map +1 -0
  61. package/dist/motion/signal.d.ts +15 -0
  62. package/dist/motion/signal.js +78 -0
  63. package/dist/motion/signal.js.map +1 -0
  64. package/dist/motion/useDecrypt.d.ts +21 -0
  65. package/dist/motion/useDecrypt.js +45 -0
  66. package/dist/motion/useDecrypt.js.map +1 -0
  67. package/dist/motion.d.ts +10 -0
  68. package/dist/motion.js +10 -0
  69. package/dist/nineos-base.css +54 -0
  70. package/dist/provider/ThemeProvider.d.ts +25 -0
  71. package/dist/provider/ThemeProvider.js +44 -0
  72. package/dist/provider/ThemeProvider.js.map +1 -0
  73. package/dist/styles.css +637 -0
  74. package/dist/tokens.css +470 -0
  75. package/dist/tokens.d.ts +2446 -0
  76. package/dist/tokens.js +2446 -0
  77. package/fonts.css +9 -0
  78. package/package.json +114 -4
  79. package/scss/_glitch.scss +23 -0
  80. package/scss/_index.scss +6 -0
  81. package/scss/base.scss +24 -0
  82. package/theme.json +9 -0
  83. package/tokens/color.json +46 -0
  84. package/tokens/layout.json +29 -0
  85. package/tokens/motion.json +18 -0
  86. package/tokens/palettes/acid.json +65 -0
  87. package/tokens/palettes/volt.json +65 -0
  88. package/tokens/shape.json +26 -0
  89. package/tokens/space.json +19 -0
  90. package/tokens/themes/dark.json +81 -0
  91. package/tokens/themes/light.json +106 -0
  92. package/tokens/typography.json +58 -0
package/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ # @rnine/nineos
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ NineOS: a design system library for React, built on [`@rnine/core`](../core). Two moods,
4
+ **Clear** (light) and **Cipher** (dark); cut corners; magenta for the one thing that
5
+ matters; a command line that answers with panels; and the NineOS motion layer.
6
+
7
+ It exports everything `@rnine/core` does, with the same props, in the NineOS look, plus
8
+ its own pieces:
9
+
10
+ - **The shell**: `CommandLine`, `ChipBar`, `PanelStage` (with the signal swap), `Dock`,
11
+ `Hud`, `Kbd`, `Hint`.
12
+ - **Its effects in the shared roles**: `TextReveal` decodes from magenta terminal symbols,
13
+ `Backdrop` is the reactive dot field, `Mark` glitches in cyan and magenta,
14
+ `swapIn`/`swapOut` are the signal swap, `PageEffects` are Cipher's scanlines and the
15
+ magenta click sparks.
16
+ - **NineOS only** in `/motion`: `Terminal`, `VelocityStrip`. In SCSS: `glitch-text()`.
17
+
18
+ ```bash
19
+ yarn workspace @rnine/nineos storybook # Storybook on http://localhost:6006 (NineOS and core stories)
20
+ yarn workspace @rnine/nineos test # component and conformance tests
21
+ yarn workspace @rnine/nineos build-storybook # static Storybook in storybook-static/
22
+ ```
23
+
24
+ Changing token JSON needs `yarn workspace @rnine/nineos build` before Storybook picks it up;
25
+ component and mixin edits reload instantly.
26
+
27
+ ## Use it in an app
28
+
29
+ ```bash
30
+ npm install @rnine/nineos react react-dom # sass too, to use the SCSS API
31
+ ```
32
+
33
+ ```tsx
34
+ // entry file, once
35
+ import '@rnine/nineos/fonts.css'; // Chakra Petch, Hanken Grotesk, Share Tech Mono (self-hosted)
36
+ import '@rnine/nineos/styles.css'; // tokens, Clear/Cipher switching, base styles, keyframes, every component
37
+ import { ThemeProvider } from '@rnine/nineos';
38
+ import markSvg from './mark.svg?raw';
39
+
40
+ createRoot(root).render(
41
+ <ThemeProvider mark={{ full: markSvg }} mood={{ cookieDomain: 'example.com' }}>
42
+ <App />
43
+ </ThemeProvider>,
44
+ );
45
+ ```
46
+
47
+ Auto shows Clear by day (06:00-18:00, the viewer's time) and, at night, Cipher only for a
48
+ dark system setting, until the viewer chooses. Put `moodBootScript()`'s output in an
49
+ inline `<script>` in your `index.html` `<head>` for the right mood on the first paint.
50
+
51
+ ```tsx
52
+ import { Button, CommandLine, Panel, PanelStage } from '@rnine/nineos';
53
+ import { Backdrop, TextReveal, Terminal } from '@rnine/nineos/motion';
54
+ import { tokens } from '@rnine/nineos/tokens'; // tokens as data (docs, charts, canvas effects)
55
+ ```
56
+
57
+ ```scss
58
+ // in any SCSS module
59
+ @use "@rnine/nineos/scss" as r9;
60
+
61
+ .button {
62
+ @include r9.type(label);
63
+ @include r9.corner(md); // NineOS: a cut top-left and bottom-right corner
64
+ background: r9.$accent;
65
+ &::after { @include r9.ornament; }
66
+ }
67
+ ```
68
+
69
+ ## Moods
70
+
71
+ The page carries `data-scheme="light"` (Clear) or `data-scheme="dark"` (Cipher), and any
72
+ element can too. With no attribute (no JavaScript), Cipher follows a dark system setting.
73
+ A mood saved by an older version (`clear`, `cipher`) is read as its scheme.
74
+
75
+ ## Files
76
+
77
+ | Path | What |
78
+ |---|---|
79
+ | `theme.json` | The name, the moods' names (Clear, Cipher) and the Auto rule (night) |
80
+ | `tokens/*.json` | Primitives and scales (DTCG format): color, type, space, corner and shape, motion, layout |
81
+ | `tokens/themes/{light,dark}.json` | Clear's and Cipher's mood roles (bg, fg, accent, signal, ...). Both must define the same roles |
82
+ | `scss/` | The shared SCSS API (from core) plus `glitch-text()`; `base.scss` adds NineOS's keyframes |
83
+ | `src/` | NineOS's components and effects; `index.ts` and `motion.ts` re-export core and replace the role exports |
84
+ | `scripts/build.mjs` | Tokens (checked against core's), `vite build`, then `dist/styles.css` |
85
+ | `test/contrast.test.mjs` | WCAG AA in both moods, and the contract: core's tokens and exports, the keyframes, theme.json |
86
+
87
+ No logo or favicons: an app brings its own mark (`ThemeProvider mark`).
88
+
89
+ ## Rules
90
+
91
+ - Components use mood roles (`r9.$accent`), never primitives or hex values.
92
+ - Every component has at least one magenta element (`r9.$accent`). If none is natural, add `@include r9.ornament`.
93
+ - Cyan (`r9.$signal`) is only for live signals: status, data, the cursor's light.
94
+ - Space siblings with `gap`, not margins. Motion goes inside `@include r9.motion-safe`.
@@ -0,0 +1,199 @@
1
+ // NineOS tokens for SCSS. Generated by @rnine/core/build-tokens. Do not edit.
2
+ // Every variable points at a CSS custom property, so the mood switches at
3
+ // runtime (data-scheme="light" | "dark"). Media queries need plain values: use $breakpoints.
4
+
5
+ // Primitives and scales
6
+ $color-gray-0: var(--r9-color-gray-0);
7
+ $color-gray-10: var(--r9-color-gray-10);
8
+ $color-gray-25: var(--r9-color-gray-25);
9
+ $color-gray-50: var(--r9-color-gray-50);
10
+ $color-gray-100: var(--r9-color-gray-100);
11
+ $color-gray-300: var(--r9-color-gray-300);
12
+ $color-gray-500: var(--r9-color-gray-500);
13
+ $color-gray-600: var(--r9-color-gray-600);
14
+ $color-gray-800: var(--r9-color-gray-800);
15
+ $color-gray-850: var(--r9-color-gray-850);
16
+ $color-gray-900: var(--r9-color-gray-900);
17
+ $color-gray-950: var(--r9-color-gray-950);
18
+ $color-gray-975: var(--r9-color-gray-975);
19
+ $color-accent-300: var(--r9-color-accent-300);
20
+ $color-accent-400: var(--r9-color-accent-400);
21
+ $color-accent-500: var(--r9-color-accent-500);
22
+ $color-accent-600: var(--r9-color-accent-600);
23
+ $color-accent-700: var(--r9-color-accent-700);
24
+ $color-accent-800: var(--r9-color-accent-800);
25
+ $color-signal-300: var(--r9-color-signal-300);
26
+ $color-signal-500: var(--r9-color-signal-500);
27
+ $color-signal-700: var(--r9-color-signal-700);
28
+ $color-green-400: var(--r9-color-green-400);
29
+ $color-green-700: var(--r9-color-green-700);
30
+ $color-amber-400: var(--r9-color-amber-400);
31
+ $color-amber-700: var(--r9-color-amber-700);
32
+ $color-red-400: var(--r9-color-red-400);
33
+ $color-red-700: var(--r9-color-red-700);
34
+ $breakpoint-sm: var(--r9-breakpoint-sm);
35
+ $breakpoint-md: var(--r9-breakpoint-md);
36
+ $breakpoint-lg: var(--r9-breakpoint-lg);
37
+ $container-max: var(--r9-container-max);
38
+ $container-text: var(--r9-container-text);
39
+ $container-gutter: var(--r9-container-gutter);
40
+ $header-height: var(--r9-header-height);
41
+ $z-base: var(--r9-z-base);
42
+ $z-raised: var(--r9-z-raised);
43
+ $z-sticky: var(--r9-z-sticky);
44
+ $z-overlay: var(--r9-z-overlay);
45
+ $z-modal: var(--r9-z-modal);
46
+ $z-toast: var(--r9-z-toast);
47
+ $z-fx: var(--r9-z-fx);
48
+ $duration-instant: var(--r9-duration-instant);
49
+ $duration-fast: var(--r9-duration-fast);
50
+ $duration-base: var(--r9-duration-base);
51
+ $duration-slow: var(--r9-duration-slow);
52
+ $duration-slower: var(--r9-duration-slower);
53
+ $duration-ambient: var(--r9-duration-ambient);
54
+ $easing-out: var(--r9-easing-out);
55
+ $easing-in-out: var(--r9-easing-in-out);
56
+ $easing-linear: var(--r9-easing-linear);
57
+ $corner-sm: var(--r9-corner-sm);
58
+ $corner-md: var(--r9-corner-md);
59
+ $corner-lg: var(--r9-corner-lg);
60
+ $shape-cut: var(--r9-shape-cut);
61
+ $shape-round: var(--r9-shape-round);
62
+ $radius-none: var(--r9-radius-none);
63
+ $radius-xs: var(--r9-radius-xs);
64
+ $radius-full: var(--r9-radius-full);
65
+ $border-hair: var(--r9-border-hair);
66
+ $border-strong: var(--r9-border-strong);
67
+ $space-0: var(--r9-space-0);
68
+ $space-1: var(--r9-space-1);
69
+ $space-2: var(--r9-space-2);
70
+ $space-3: var(--r9-space-3);
71
+ $space-4: var(--r9-space-4);
72
+ $space-5: var(--r9-space-5);
73
+ $space-6: var(--r9-space-6);
74
+ $space-8: var(--r9-space-8);
75
+ $space-10: var(--r9-space-10);
76
+ $space-12: var(--r9-space-12);
77
+ $space-16: var(--r9-space-16);
78
+ $space-20: var(--r9-space-20);
79
+ $space-24: var(--r9-space-24);
80
+ $font-display: var(--r9-font-display);
81
+ $font-body: var(--r9-font-body);
82
+ $font-mono: var(--r9-font-mono);
83
+ $font-size-xs: var(--r9-font-size-xs);
84
+ $font-size-sm: var(--r9-font-size-sm);
85
+ $font-size-base: var(--r9-font-size-base);
86
+ $font-size-md: var(--r9-font-size-md);
87
+ $font-size-lg: var(--r9-font-size-lg);
88
+ $font-size-xl: var(--r9-font-size-xl);
89
+ $font-size-2xl: var(--r9-font-size-2xl);
90
+ $font-size-3xl: var(--r9-font-size-3xl);
91
+ $font-size-4xl: var(--r9-font-size-4xl);
92
+ $font-size-5xl: var(--r9-font-size-5xl);
93
+ $font-weight-regular: var(--r9-font-weight-regular);
94
+ $font-weight-medium: var(--r9-font-weight-medium);
95
+ $font-weight-semibold: var(--r9-font-weight-semibold);
96
+ $font-weight-bold: var(--r9-font-weight-bold);
97
+ $leading-tight: var(--r9-leading-tight);
98
+ $leading-snug: var(--r9-leading-snug);
99
+ $leading-normal: var(--r9-leading-normal);
100
+ $tracking-tight: var(--r9-tracking-tight);
101
+ $tracking-normal: var(--r9-tracking-normal);
102
+ $tracking-wide: var(--r9-tracking-wide);
103
+ $tracking-wider: var(--r9-tracking-wider);
104
+ $tracking-mark: var(--r9-tracking-mark);
105
+
106
+ // Mood roles
107
+ $bg: var(--r9-bg);
108
+ $surface: var(--r9-surface);
109
+ $surface-raised: var(--r9-surface-raised);
110
+ $glass: var(--r9-glass);
111
+ $fg: var(--r9-fg);
112
+ $fg-muted: var(--r9-fg-muted);
113
+ $fg-subtle: var(--r9-fg-subtle);
114
+ $line: var(--r9-line);
115
+ $line-strong: var(--r9-line-strong);
116
+ $accent: var(--r9-accent);
117
+ $accent-hover: var(--r9-accent-hover);
118
+ $accent-text: var(--r9-accent-text);
119
+ $on-accent: var(--r9-on-accent);
120
+ $accent-soft: var(--r9-accent-soft);
121
+ $signal: var(--r9-signal);
122
+ $signal-text: var(--r9-signal-text);
123
+ $inverse-bg: var(--r9-inverse-bg);
124
+ $inverse-fg: var(--r9-inverse-fg);
125
+ $success: var(--r9-success);
126
+ $warning: var(--r9-warning);
127
+ $danger: var(--r9-danger);
128
+ $dot: var(--r9-dot);
129
+ $scan: var(--r9-scan);
130
+ $sheen: var(--r9-sheen);
131
+ $selection: var(--r9-selection);
132
+
133
+ $space: (
134
+ "0": var(--r9-space-0),
135
+ "1": var(--r9-space-1),
136
+ "2": var(--r9-space-2),
137
+ "3": var(--r9-space-3),
138
+ "4": var(--r9-space-4),
139
+ "5": var(--r9-space-5),
140
+ "6": var(--r9-space-6),
141
+ "8": var(--r9-space-8),
142
+ "10": var(--r9-space-10),
143
+ "12": var(--r9-space-12),
144
+ "16": var(--r9-space-16),
145
+ "20": var(--r9-space-20),
146
+ "24": var(--r9-space-24),
147
+ );
148
+
149
+ $corner: (
150
+ "sm": var(--r9-corner-sm),
151
+ "md": var(--r9-corner-md),
152
+ "lg": var(--r9-corner-lg),
153
+ );
154
+
155
+ $durations: (
156
+ "instant": var(--r9-duration-instant),
157
+ "fast": var(--r9-duration-fast),
158
+ "base": var(--r9-duration-base),
159
+ "slow": var(--r9-duration-slow),
160
+ "slower": var(--r9-duration-slower),
161
+ "ambient": var(--r9-duration-ambient),
162
+ );
163
+
164
+ $easings: (
165
+ "out": var(--r9-easing-out),
166
+ "in-out": var(--r9-easing-in-out),
167
+ "linear": var(--r9-easing-linear),
168
+ );
169
+
170
+ $z-index: (
171
+ "base": var(--r9-z-base),
172
+ "raised": var(--r9-z-raised),
173
+ "sticky": var(--r9-z-sticky),
174
+ "overlay": var(--r9-z-overlay),
175
+ "modal": var(--r9-z-modal),
176
+ "toast": var(--r9-z-toast),
177
+ "fx": var(--r9-z-fx),
178
+ );
179
+
180
+ $breakpoints: (
181
+ "sm": 640px,
182
+ "md": 900px,
183
+ "lg": 1200px,
184
+ );
185
+
186
+ $type-roles: (
187
+ "display": ("font-family": var(--r9-type-display-font-family), "font-weight": var(--r9-type-display-font-weight), "font-size": var(--r9-type-display-font-size), "line-height": var(--r9-type-display-line-height), "letter-spacing": var(--r9-type-display-letter-spacing)),
188
+ "h1": ("font-family": var(--r9-type-h1-font-family), "font-weight": var(--r9-type-h1-font-weight), "font-size": var(--r9-type-h1-font-size), "line-height": var(--r9-type-h1-line-height), "letter-spacing": var(--r9-type-h1-letter-spacing)),
189
+ "h2": ("font-family": var(--r9-type-h2-font-family), "font-weight": var(--r9-type-h2-font-weight), "font-size": var(--r9-type-h2-font-size), "line-height": var(--r9-type-h2-line-height), "letter-spacing": var(--r9-type-h2-letter-spacing)),
190
+ "h3": ("font-family": var(--r9-type-h3-font-family), "font-weight": var(--r9-type-h3-font-weight), "font-size": var(--r9-type-h3-font-size), "line-height": var(--r9-type-h3-line-height), "letter-spacing": var(--r9-type-h3-letter-spacing)),
191
+ "h4": ("font-family": var(--r9-type-h4-font-family), "font-weight": var(--r9-type-h4-font-weight), "font-size": var(--r9-type-h4-font-size), "line-height": var(--r9-type-h4-line-height), "letter-spacing": var(--r9-type-h4-letter-spacing)),
192
+ "body-lg": ("font-family": var(--r9-type-body-lg-font-family), "font-weight": var(--r9-type-body-lg-font-weight), "font-size": var(--r9-type-body-lg-font-size), "line-height": var(--r9-type-body-lg-line-height), "letter-spacing": var(--r9-type-body-lg-letter-spacing)),
193
+ "body": ("font-family": var(--r9-type-body-font-family), "font-weight": var(--r9-type-body-font-weight), "font-size": var(--r9-type-body-font-size), "line-height": var(--r9-type-body-line-height), "letter-spacing": var(--r9-type-body-letter-spacing)),
194
+ "body-sm": ("font-family": var(--r9-type-body-sm-font-family), "font-weight": var(--r9-type-body-sm-font-weight), "font-size": var(--r9-type-body-sm-font-size), "line-height": var(--r9-type-body-sm-line-height), "letter-spacing": var(--r9-type-body-sm-letter-spacing)),
195
+ "label": ("font-family": var(--r9-type-label-font-family), "font-weight": var(--r9-type-label-font-weight), "font-size": var(--r9-type-label-font-size), "line-height": var(--r9-type-label-line-height), "letter-spacing": var(--r9-type-label-letter-spacing)),
196
+ "code": ("font-family": var(--r9-type-code-font-family), "font-weight": var(--r9-type-code-font-weight), "font-size": var(--r9-type-code-font-size), "line-height": var(--r9-type-code-line-height), "letter-spacing": var(--r9-type-code-letter-spacing)),
197
+ "caption": ("font-family": var(--r9-type-caption-font-family), "font-weight": var(--r9-type-caption-font-weight), "font-size": var(--r9-type-caption-font-size), "line-height": var(--r9-type-caption-line-height), "letter-spacing": var(--r9-type-caption-letter-spacing)),
198
+ "wordmark": ("font-family": var(--r9-type-wordmark-font-family), "font-weight": var(--r9-type-wordmark-font-weight), "font-size": var(--r9-type-wordmark-font-size), "line-height": var(--r9-type-wordmark-line-height), "letter-spacing": var(--r9-type-wordmark-letter-spacing)),
199
+ );
@@ -0,0 +1,27 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ type DockProps = HTMLAttributes<HTMLDivElement> & {
3
+ children: ReactNode; /** Accessible name of the region. */
4
+ label?: string;
5
+ };
6
+ /**
7
+ * The bar pinned to the bottom of the screen (thumb reach on phones): holds
8
+ * the CommandLine, or menu chips in Menu mode. Glass over the dot field.
9
+ * Publishes its height as --r9-dock-h on <html>, so pages can pad for it.
10
+ */
11
+ export declare function Dock({ children, label, className, ...rest }: DockProps): import("react").JSX.Element;
12
+ type Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
13
+ type HudProps = HTMLAttributes<HTMLDivElement> & {
14
+ corner: Corner;
15
+ children: ReactNode;
16
+ };
17
+ /**
18
+ * A corner slot for the always-there essentials (monogram, clock, switches).
19
+ * NineOS apps use corner HUDs instead of a navbar.
20
+ */
21
+ export declare function Hud({ corner, children, className, ...rest }: HudProps): import("react").JSX.Element;
22
+ /** A keyboard key, e.g. <Kbd>Ctrl K</Kbd>. */
23
+ export declare function Kbd({ children, className }: {
24
+ children: ReactNode;
25
+ className?: string;
26
+ }): import("react").JSX.Element;
27
+ export {};
@@ -0,0 +1,45 @@
1
+ import e from "./Chrome.module.js";
2
+ import { cx as t } from "@rnine/core/kit";
3
+ import { useEffect as n, useRef as r } from "react";
4
+ import { jsx as i } from "react/jsx-runtime";
5
+ //#region src/components/Chrome/Chrome.tsx
6
+ function a({ children: a, label: o = "Command dock", className: s, ...c }) {
7
+ let l = r(null);
8
+ return n(() => {
9
+ let e = l.current;
10
+ if (!e) return;
11
+ let t = document.documentElement, n = () => t.style.setProperty("--r9-dock-h", `${e.offsetHeight}px`);
12
+ n();
13
+ let r = new ResizeObserver(n);
14
+ return r.observe(e), () => {
15
+ r.disconnect(), t.style.removeProperty("--r9-dock-h");
16
+ };
17
+ }, []), /* @__PURE__ */ i("div", {
18
+ ...c,
19
+ ref: l,
20
+ role: "region",
21
+ "aria-label": o,
22
+ className: t(e.dock, s),
23
+ children: /* @__PURE__ */ i("div", {
24
+ className: e.dockInner,
25
+ children: a
26
+ })
27
+ });
28
+ }
29
+ function o({ corner: n, children: r, className: a, ...o }) {
30
+ return /* @__PURE__ */ i("div", {
31
+ ...o,
32
+ className: t(e.hud, e[n.replace("-", "")], a),
33
+ children: r
34
+ });
35
+ }
36
+ function s({ children: n, className: r }) {
37
+ return /* @__PURE__ */ i("kbd", {
38
+ className: t(e.kbd, r),
39
+ children: n
40
+ });
41
+ }
42
+ //#endregion
43
+ export { a as Dock, o as Hud, s as Kbd };
44
+
45
+ //# sourceMappingURL=Chrome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Chrome.js","names":[],"sources":["../../../src/components/Chrome/Chrome.tsx"],"sourcesContent":["import { useEffect, useRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cx } from '@rnine/core/kit';\nimport styles from './Chrome.module.scss';\n\ntype DockProps = HTMLAttributes<HTMLDivElement> & { children: ReactNode; /** Accessible name of the region. */ label?: string };\n\n/**\n * The bar pinned to the bottom of the screen (thumb reach on phones): holds\n * the CommandLine, or menu chips in Menu mode. Glass over the dot field.\n * Publishes its height as --r9-dock-h on <html>, so pages can pad for it.\n */\nexport function Dock({ children, label = 'Command dock', className, ...rest }: DockProps) {\n const ref = useRef<HTMLDivElement>(null);\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const root = document.documentElement;\n const update = () => root.style.setProperty('--r9-dock-h', `${el.offsetHeight}px`);\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => {\n ro.disconnect();\n root.style.removeProperty('--r9-dock-h');\n };\n }, []);\n return (\n <div {...rest} ref={ref} role=\"region\" aria-label={label} className={cx(styles.dock, className)}>\n <div className={styles.dockInner}>{children}</div>\n </div>\n );\n}\n\ntype Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\ntype HudProps = HTMLAttributes<HTMLDivElement> & { corner: Corner; children: ReactNode };\n\n/**\n * A corner slot for the always-there essentials (monogram, clock, switches).\n * NineOS apps use corner HUDs instead of a navbar.\n */\nexport function Hud({ corner, children, className, ...rest }: HudProps) {\n return (\n <div {...rest} className={cx(styles.hud, styles[corner.replace('-', '')], className)}>\n {children}\n </div>\n );\n}\n\n/** A keyboard key, e.g. <Kbd>Ctrl K</Kbd>. */\nexport function Kbd({ children, className }: { children: ReactNode; className?: string }) {\n return <kbd className={cx(styles.kbd, className)}>{children}</kbd>;\n}\n"],"mappings":";;;;;AAWA,SAAgB,EAAK,EAAE,aAAU,WAAQ,gBAAgB,cAAW,GAAG,KAAmB;CACxF,IAAM,IAAM,EAAuB,IAAI;CAcvC,OAbA,QAAgB;EACd,IAAM,IAAK,EAAI;EACf,IAAI,CAAC,GAAI;EACT,IAAM,IAAO,SAAS,iBAChB,UAAe,EAAK,MAAM,YAAY,eAAe,GAAG,EAAG,aAAa,GAAG;EACjF,EAAO;EACP,IAAM,IAAK,IAAI,eAAe,CAAM;EAEpC,OADA,EAAG,QAAQ,CAAE,SACA;GAEX,AADA,EAAG,WAAW,GACd,EAAK,MAAM,eAAe,aAAa;EACzC;CACF,GAAG,CAAC,CAAC,GAEH,kBAAC,OAAD;EAAK,GAAI;EAAW;EAAK,MAAK;EAAS,cAAY;EAAO,WAAW,EAAG,EAAO,MAAM,CAAS;YAC5F,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAY;EAAc,CAAA;CAC9C,CAAA;AAET;AASA,SAAgB,EAAI,EAAE,WAAQ,aAAU,cAAW,GAAG,KAAkB;CACtE,OACE,kBAAC,OAAD;EAAK,GAAI;EAAM,WAAW,EAAG,EAAO,KAAK,EAAO,EAAO,QAAQ,KAAK,EAAE,IAAI,CAAS;EAChF;CACE,CAAA;AAET;AAGA,SAAgB,EAAI,EAAE,aAAU,gBAA0D;CACxF,OAAO,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,KAAK,CAAS;EAAI;CAAc,CAAA;AACnE"}
@@ -0,0 +1,14 @@
1
+ var e = {
2
+ dock: "r9-Chrome-module_dock_laf9m",
3
+ dockInner: "r9-Chrome-module_dockInner_4aQte",
4
+ hud: "r9-Chrome-module_hud_pprK9",
5
+ topleft: "r9-Chrome-module_topleft_b36X-",
6
+ topright: "r9-Chrome-module_topright_PpRWm",
7
+ bottomleft: "r9-Chrome-module_bottomleft_13I-i",
8
+ bottomright: "r9-Chrome-module_bottomright_FhlYK",
9
+ kbd: "r9-Chrome-module_kbd_823sZ"
10
+ };
11
+ //#endregion
12
+ export { e as default };
13
+
14
+ //# sourceMappingURL=Chrome.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Chrome.module.js","names":[],"sources":["../../../src/components/Chrome/Chrome.module.scss"],"sourcesContent":["@use \"@rnine/nineos/scss\" as r9;\n\n.dock {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: r9.$z-sticky;\n @include r9.gutter;\n padding-top: r9.$space-3;\n padding-bottom: calc(#{r9.$space-3} + env(safe-area-inset-bottom, 0px));\n @include r9.glass(12px);\n border-top: r9.$border-hair solid r9.$line;\n}\n\n.dockInner {\n @include r9.container(940px);\n display: flex;\n flex-direction: column;\n gap: r9.$space-2;\n}\n\n.hud {\n position: fixed;\n z-index: r9.$z-overlay;\n display: flex;\n align-items: center;\n gap: r9.$space-3;\n}\n\n.topleft { top: calc(14px + env(safe-area-inset-top, 0px)); left: max(16px, env(safe-area-inset-left, 0px)); }\n.topright { top: calc(14px + env(safe-area-inset-top, 0px)); right: max(16px, env(safe-area-inset-right, 0px)); }\n.bottomleft { bottom: calc(14px + env(safe-area-inset-bottom, 0px)); left: max(16px, env(safe-area-inset-left, 0px)); }\n.bottomright { bottom: calc(14px + env(safe-area-inset-bottom, 0px)); right: max(16px, env(safe-area-inset-right, 0px)); }\n\n.kbd {\n display: inline-block;\n padding: 1px 6px;\n @include r9.type(caption);\n color: r9.$fg-muted;\n white-space: nowrap;\n @include r9.hairline(r9.$line-strong);\n}\n"],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react';
2
+ export type ChipItem = {
3
+ key: string;
4
+ label: ReactNode; /** e.g. "/" before command names, drawn in magenta */
5
+ prefix?: string;
6
+ };
7
+ type Props = {
8
+ items: ChipItem[];
9
+ onSelect: (key: string) => void;
10
+ /** Accessible name of the group. */
11
+ label: string;
12
+ onPointerDown?: () => void;
13
+ className?: string;
14
+ };
15
+ /**
16
+ * A row of tap targets (commands, menu entries) that scrolls sideways when it
17
+ * runs out of room. Magenta element: the prefix, and the hover edge.
18
+ */
19
+ export declare function ChipBar({ items, onSelect, label, onPointerDown, className }: Props): import("react").JSX.Element;
20
+ export {};
@@ -0,0 +1,25 @@
1
+ import e from "./ChipBar.module.js";
2
+ import { cx as t } from "@rnine/core/kit";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
+ //#region src/components/CommandLine/ChipBar.tsx
5
+ function i({ items: i, onSelect: a, label: o, onPointerDown: s, className: c }) {
6
+ return /* @__PURE__ */ n("div", {
7
+ role: "group",
8
+ "aria-label": o,
9
+ className: t(e.bar, c),
10
+ onPointerDown: s,
11
+ children: i.map((t) => /* @__PURE__ */ r("button", {
12
+ type: "button",
13
+ className: e.chip,
14
+ onClick: () => a(t.key),
15
+ children: [t.prefix && /* @__PURE__ */ n("span", {
16
+ className: e.prefix,
17
+ children: t.prefix
18
+ }), t.label]
19
+ }, t.key))
20
+ });
21
+ }
22
+ //#endregion
23
+ export { i as ChipBar };
24
+
25
+ //# sourceMappingURL=ChipBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChipBar.js","names":[],"sources":["../../../src/components/CommandLine/ChipBar.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cx } from '@rnine/core/kit';\nimport styles from './ChipBar.module.scss';\n\nexport type ChipItem = { key: string; label: ReactNode; /** e.g. \"/\" before command names, drawn in magenta */ prefix?: string };\n\ntype Props = {\n items: ChipItem[];\n onSelect: (key: string) => void;\n /** Accessible name of the group. */\n label: string;\n onPointerDown?: () => void;\n className?: string;\n};\n\n/**\n * A row of tap targets (commands, menu entries) that scrolls sideways when it\n * runs out of room. Magenta element: the prefix, and the hover edge.\n */\nexport function ChipBar({ items, onSelect, label, onPointerDown, className }: Props) {\n return (\n <div role=\"group\" aria-label={label} className={cx(styles.bar, className)} onPointerDown={onPointerDown}>\n {items.map((item) => (\n <button key={item.key} type=\"button\" className={styles.chip} onClick={() => onSelect(item.key)}>\n {item.prefix && <span className={styles.prefix}>{item.prefix}</span>}\n {item.label}\n </button>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAmBA,SAAgB,EAAQ,EAAE,UAAO,aAAU,UAAO,kBAAe,gBAAoB;CACnF,OACE,kBAAC,OAAD;EAAK,MAAK;EAAQ,cAAY;EAAO,WAAW,EAAG,EAAO,KAAK,CAAS;EAAkB;YACvF,EAAM,KAAK,MACV,kBAAC,UAAD;GAAuB,MAAK;GAAS,WAAW,EAAO;GAAM,eAAe,EAAS,EAAK,GAAG;aAA7F,CACG,EAAK,UAAU,kBAAC,QAAD;IAAM,WAAW,EAAO;cAAS,EAAK;GAAa,CAAA,GAClE,EAAK,KACA;KAHK,EAAK,GAGV,CACT;CACE,CAAA;AAET"}
@@ -0,0 +1,9 @@
1
+ var e = {
2
+ bar: "r9-ChipBar-module_bar_92cr1",
3
+ chip: "r9-ChipBar-module_chip_UQIxe",
4
+ prefix: "r9-ChipBar-module_prefix_-WFJm"
5
+ };
6
+ //#endregion
7
+ export { e as default };
8
+
9
+ //# sourceMappingURL=ChipBar.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChipBar.module.js","names":[],"sources":["../../../src/components/CommandLine/ChipBar.module.scss"],"sourcesContent":["@use \"@rnine/nineos/scss\" as r9;\n\n.bar {\n display: flex;\n gap: r9.$space-2;\n overflow-x: auto;\n scrollbar-width: none;\n min-width: 0;\n\n &::-webkit-scrollbar { display: none; }\n}\n\n.chip {\n flex: none;\n min-height: 36px;\n padding: r9.$space-2 r9.$space-3;\n border: 0;\n cursor: pointer;\n @include r9.type(code);\n font-size: r9.$font-size-sm;\n color: r9.$fg;\n background: r9.$surface;\n @include r9.hairline;\n transition: box-shadow r9.$duration-fast, color r9.$duration-fast;\n\n &:hover { @include r9.hairline(r9.$accent); color: r9.$accent-text; }\n &:focus-visible { @include r9.focus-ring(2px); }\n}\n\n.prefix { color: r9.$accent-text; }\n"],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { type Ref } from 'react';
2
+ export type CommandSpec = {
3
+ /** e.g. "/projects" */
4
+ name: string;
5
+ description: string;
6
+ /** Completions for an argument, e.g. project slugs for "/open". */
7
+ args?: {
8
+ value: string;
9
+ description?: string;
10
+ }[];
11
+ };
12
+ type Props = {
13
+ commands: CommandSpec[];
14
+ /** Runs a command line exactly as typed or picked, e.g. "/open harbor". */
15
+ onRun: (input: string) => void;
16
+ /** Commands shown as tap-to-run chips above the line (phones never have to type). */
17
+ chips?: string[];
18
+ prompt?: string;
19
+ placeholder?: string;
20
+ /** Accessible name of the input. */
21
+ label?: string;
22
+ /** Ctrl/Cmd+K, or "/" outside a text field, focuses the line. Default true. */
23
+ shortcut?: boolean;
24
+ /** Called once, the first time someone focuses, types or taps a chip (e.g. for onboarding hints). */
25
+ onEngage?: () => void;
26
+ /** Escape on an empty line (e.g. close the open panel). */
27
+ onEscapeEmpty?: () => void;
28
+ runLabel?: string;
29
+ className?: string;
30
+ ref?: Ref<HTMLDivElement>;
31
+ };
32
+ /**
33
+ * The NineOS command line: a prompt with autocomplete, history (↑) and
34
+ * tap-to-run chips. Magenta element: the prompt, caret, focus edge and Run.
35
+ * Always pair it with a way to use the app without typing (Menu mode).
36
+ */
37
+ export declare function CommandLine({ commands, onRun, chips, prompt, placeholder, label, shortcut, onEngage, onEscapeEmpty, runLabel, className, ref, }: Props): import("react").JSX.Element;
38
+ export {};
@@ -0,0 +1,121 @@
1
+ import { ChipBar as e } from "./ChipBar.js";
2
+ import t from "./CommandLine.module.js";
3
+ import { cx as n } from "@rnine/core/kit";
4
+ import { useEffect as r, useId as i, useMemo as a, useRef as o, useState as s } from "react";
5
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
+ //#region src/components/CommandLine/CommandLine.tsx
7
+ function u(e, t) {
8
+ let n = t.trim().toLowerCase();
9
+ if (!n) return [];
10
+ n.startsWith("/") || (n = `/${n}`);
11
+ let r = [];
12
+ for (let t of e) t.args?.length ? t.args.forEach((e) => r.push({
13
+ value: `${t.name} ${e.value}`,
14
+ description: e.description ?? t.description
15
+ })) : r.push({
16
+ value: t.name,
17
+ description: t.description
18
+ });
19
+ return r.filter((e) => e.value.startsWith(n)).slice(0, 8);
20
+ }
21
+ function d({ commands: d, onRun: f, chips: p, prompt: m = ">", placeholder: h = "type /help, or tap a command", label: g = "Command", shortcut: _ = !0, onEngage: v, onEscapeEmpty: y, runLabel: b = "Run", className: x, ref: S }) {
22
+ let C = i(), w = o(null), [T, E] = s(""), [D, O] = s(-1), [k, A] = s(!0), j = o([]), M = o(-1), N = o(!1), P = a(() => k ? u(d, T) : [], [
23
+ d,
24
+ T,
25
+ k
26
+ ]), F = P.length > 0, I = () => {
27
+ N.current || (N.current = !0, v?.());
28
+ }, L = (e) => {
29
+ let t = e.trim();
30
+ t && (j.current[0] !== t && j.current.unshift(t), M.current = -1, E(""), O(-1), f(t));
31
+ };
32
+ return r(() => {
33
+ if (!_) return;
34
+ let e = (e) => {
35
+ let t = document.activeElement, n = t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement || t?.isContentEditable;
36
+ (e.key === "k" && (e.ctrlKey || e.metaKey) || e.key === "/" && !n) && (e.preventDefault(), w.current?.focus(), e.key === "/" && E("/"));
37
+ };
38
+ return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
39
+ }, [_]), /* @__PURE__ */ l("div", {
40
+ ref: S,
41
+ className: n(t.root, x),
42
+ children: [
43
+ F && /* @__PURE__ */ c("ul", {
44
+ className: t.list,
45
+ role: "listbox",
46
+ id: `${C}-list`,
47
+ "aria-label": "Matching commands",
48
+ children: P.map((e, n) => /* @__PURE__ */ l("li", {
49
+ id: `${C}-opt-${n}`,
50
+ role: "option",
51
+ "aria-selected": n === D,
52
+ className: t.option,
53
+ onMouseDown: (t) => {
54
+ t.preventDefault(), L(e.value);
55
+ },
56
+ children: [/* @__PURE__ */ c("code", { children: e.value }), /* @__PURE__ */ c("span", { children: e.description })]
57
+ }, e.value))
58
+ }),
59
+ p && p.length > 0 && /* @__PURE__ */ c(e, {
60
+ label: "Quick commands",
61
+ onPointerDown: I,
62
+ onSelect: (e) => {
63
+ I(), L(e);
64
+ },
65
+ items: p.map((e) => ({
66
+ key: e,
67
+ prefix: e.startsWith("/") ? "/" : void 0,
68
+ label: e.replace(/^\//, "")
69
+ }))
70
+ }),
71
+ /* @__PURE__ */ l("form", {
72
+ className: t.prompt,
73
+ onSubmit: (e) => {
74
+ e.preventDefault(), L(D >= 0 && P[D] ? P[D].value : T), w.current?.blur();
75
+ },
76
+ autoComplete: "off",
77
+ children: [
78
+ /* @__PURE__ */ c("label", {
79
+ htmlFor: `${C}-input`,
80
+ className: t.label,
81
+ children: m
82
+ }),
83
+ /* @__PURE__ */ c("input", {
84
+ ref: w,
85
+ id: `${C}-input`,
86
+ className: t.input,
87
+ type: "text",
88
+ value: T,
89
+ placeholder: h,
90
+ "aria-label": g,
91
+ role: "combobox",
92
+ "aria-expanded": F,
93
+ "aria-controls": `${C}-list`,
94
+ "aria-autocomplete": "list",
95
+ "aria-activedescendant": D >= 0 ? `${C}-opt-${D}` : void 0,
96
+ enterKeyHint: "go",
97
+ spellCheck: !1,
98
+ autoCapitalize: "off",
99
+ onFocus: I,
100
+ onBlur: () => A(!1),
101
+ onChange: (e) => {
102
+ E(e.target.value), O(-1), A(!0);
103
+ },
104
+ onKeyDown: (e) => {
105
+ I(), e.key === "ArrowDown" && F ? (e.preventDefault(), O((e) => (e + 1) % P.length)) : e.key === "ArrowUp" && F ? (e.preventDefault(), O((e) => (e - 1 + P.length) % P.length)) : e.key === "ArrowUp" && !T && j.current.length ? (e.preventDefault(), M.current = Math.min(j.current.length - 1, M.current + 1), E(j.current[M.current])) : e.key === "Tab" && F ? (e.preventDefault(), E(P[Math.max(0, D)].value), O(-1)) : e.key === "Escape" && (T ? (E(""), O(-1)) : y?.());
106
+ }
107
+ }),
108
+ /* @__PURE__ */ c("button", {
109
+ type: "submit",
110
+ className: t.run,
111
+ children: b
112
+ })
113
+ ]
114
+ })
115
+ ]
116
+ });
117
+ }
118
+ //#endregion
119
+ export { d as CommandLine };
120
+
121
+ //# sourceMappingURL=CommandLine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommandLine.js","names":[],"sources":["../../../src/components/CommandLine/CommandLine.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState, type FormEvent, type KeyboardEvent, type Ref } from 'react';\nimport { cx } from '@rnine/core/kit';\nimport { ChipBar } from './ChipBar';\nimport styles from './CommandLine.module.scss';\n\nexport type CommandSpec = {\n /** e.g. \"/projects\" */\n name: string;\n description: string;\n /** Completions for an argument, e.g. project slugs for \"/open\". */\n args?: { value: string; description?: string }[];\n};\n\ntype Props = {\n commands: CommandSpec[];\n /** Runs a command line exactly as typed or picked, e.g. \"/open harbor\". */\n onRun: (input: string) => void;\n /** Commands shown as tap-to-run chips above the line (phones never have to type). */\n chips?: string[];\n prompt?: string;\n placeholder?: string;\n /** Accessible name of the input. */\n label?: string;\n /** Ctrl/Cmd+K, or \"/\" outside a text field, focuses the line. Default true. */\n shortcut?: boolean;\n /** Called once, the first time someone focuses, types or taps a chip (e.g. for onboarding hints). */\n onEngage?: () => void;\n /** Escape on an empty line (e.g. close the open panel). */\n onEscapeEmpty?: () => void;\n runLabel?: string;\n className?: string;\n ref?: Ref<HTMLDivElement>;\n};\n\ntype Suggestion = { value: string; description: string };\n\n/** All completions for what has been typed so far (commands, then their arguments). */\nfunction suggest(commands: CommandSpec[], input: string): Suggestion[] {\n let q = input.trim().toLowerCase();\n if (!q) return [];\n if (!q.startsWith('/')) q = `/${q}`;\n const all: Suggestion[] = [];\n for (const c of commands) {\n if (c.args?.length) c.args.forEach((a) => all.push({ value: `${c.name} ${a.value}`, description: a.description ?? c.description }));\n else all.push({ value: c.name, description: c.description });\n }\n return all.filter((s) => s.value.startsWith(q)).slice(0, 8);\n}\n\n/**\n * The NineOS command line: a prompt with autocomplete, history (↑) and\n * tap-to-run chips. Magenta element: the prompt, caret, focus edge and Run.\n * Always pair it with a way to use the app without typing (Menu mode).\n */\nexport function CommandLine({\n commands,\n onRun,\n chips,\n prompt = '>',\n placeholder = 'type /help, or tap a command',\n label = 'Command',\n shortcut = true,\n onEngage,\n onEscapeEmpty,\n runLabel = 'Run',\n className,\n ref,\n}: Props) {\n const id = useId();\n const inputRef = useRef<HTMLInputElement>(null);\n const [value, setValue] = useState('');\n const [active, setActive] = useState(-1);\n const [open, setOpen] = useState(true);\n const history = useRef<string[]>([]);\n const historyIndex = useRef(-1);\n const engaged = useRef(false);\n\n const options = useMemo(() => (open ? suggest(commands, value) : []), [commands, value, open]);\n const expanded = options.length > 0;\n\n const engage = () => {\n if (engaged.current) return;\n engaged.current = true;\n onEngage?.();\n };\n\n const run = (line: string) => {\n const text = line.trim();\n if (!text) return;\n if (history.current[0] !== text) history.current.unshift(text);\n historyIndex.current = -1;\n setValue('');\n setActive(-1);\n onRun(text);\n };\n\n const onSubmit = (e: FormEvent) => {\n e.preventDefault();\n run(active >= 0 && options[active] ? options[active].value : value);\n inputRef.current?.blur();\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n engage();\n if (e.key === 'ArrowDown' && expanded) {\n e.preventDefault();\n setActive((a) => (a + 1) % options.length);\n } else if (e.key === 'ArrowUp' && expanded) {\n e.preventDefault();\n setActive((a) => (a - 1 + options.length) % options.length);\n } else if (e.key === 'ArrowUp' && !value && history.current.length) {\n e.preventDefault();\n historyIndex.current = Math.min(history.current.length - 1, historyIndex.current + 1);\n setValue(history.current[historyIndex.current]);\n } else if (e.key === 'Tab' && expanded) {\n e.preventDefault();\n setValue(options[Math.max(0, active)].value);\n setActive(-1);\n } else if (e.key === 'Escape') {\n if (value) {\n setValue('');\n setActive(-1);\n } else onEscapeEmpty?.();\n }\n };\n\n // Ctrl/Cmd+K anywhere, or \"/\" when not typing, jumps to the line\n useEffect(() => {\n if (!shortcut) return;\n const onKey = (e: globalThis.KeyboardEvent) => {\n const el = document.activeElement;\n const typing = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || (el as HTMLElement | null)?.isContentEditable;\n if ((e.key === 'k' && (e.ctrlKey || e.metaKey)) || (e.key === '/' && !typing)) {\n e.preventDefault();\n inputRef.current?.focus();\n if (e.key === '/') setValue('/');\n }\n };\n document.addEventListener('keydown', onKey);\n return () => document.removeEventListener('keydown', onKey);\n }, [shortcut]);\n\n return (\n <div ref={ref} className={cx(styles.root, className)}>\n {expanded && (\n <ul className={styles.list} role=\"listbox\" id={`${id}-list`} aria-label=\"Matching commands\">\n {options.map((o, i) => (\n <li\n key={o.value}\n id={`${id}-opt-${i}`}\n role=\"option\"\n aria-selected={i === active}\n className={styles.option}\n onMouseDown={(e) => {\n e.preventDefault();\n run(o.value);\n }}\n >\n <code>{o.value}</code>\n <span>{o.description}</span>\n </li>\n ))}\n </ul>\n )}\n {chips && chips.length > 0 && (\n <ChipBar\n label=\"Quick commands\"\n onPointerDown={engage}\n onSelect={(key) => {\n // Keyboard and pointer alike: picking a chip is using the command line\n engage();\n run(key);\n }}\n items={chips.map((c) => ({ key: c, prefix: c.startsWith('/') ? '/' : undefined, label: c.replace(/^\\//, '') }))}\n />\n )}\n <form className={styles.prompt} onSubmit={onSubmit} autoComplete=\"off\">\n <label htmlFor={`${id}-input`} className={styles.label}>\n {prompt}\n </label>\n <input\n ref={inputRef}\n id={`${id}-input`}\n className={styles.input}\n type=\"text\"\n value={value}\n placeholder={placeholder}\n aria-label={label}\n role=\"combobox\"\n aria-expanded={expanded}\n aria-controls={`${id}-list`}\n aria-autocomplete=\"list\"\n aria-activedescendant={active >= 0 ? `${id}-opt-${active}` : undefined}\n enterKeyHint=\"go\"\n spellCheck={false}\n autoCapitalize=\"off\"\n onFocus={engage}\n onBlur={() => setOpen(false)}\n onChange={(e) => {\n setValue(e.target.value);\n setActive(-1);\n setOpen(true);\n }}\n onKeyDown={onKeyDown}\n />\n <button type=\"submit\" className={styles.run}>\n {runLabel}\n </button>\n </form>\n </div>\n );\n}\n"],"mappings":"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"}