@storyshelf/app 0.5.3

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 (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/app-types.d.mts +54 -0
  4. package/dist/asset-manifest.mjs +41 -0
  5. package/dist/asset-manifest.mjs.map +1 -0
  6. package/dist/asset-paths.mjs +23 -0
  7. package/dist/asset-paths.mjs.map +1 -0
  8. package/dist/assets/htmx.min.js +3304 -0
  9. package/dist/assets/mark.svg +1 -0
  10. package/dist/capture-setup.mjs +139 -0
  11. package/dist/capture-setup.mjs.map +1 -0
  12. package/dist/credential-keys.mjs +20 -0
  13. package/dist/credential-keys.mjs.map +1 -0
  14. package/dist/credentials.mjs +56 -0
  15. package/dist/credentials.mjs.map +1 -0
  16. package/dist/index.d.mts +18 -0
  17. package/dist/index.mjs +310 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/lifecycle.mjs +132 -0
  20. package/dist/lifecycle.mjs.map +1 -0
  21. package/dist/middleware/auth-gate.mjs +36 -0
  22. package/dist/middleware/auth-gate.mjs.map +1 -0
  23. package/dist/middleware/csrf.mjs +75 -0
  24. package/dist/middleware/csrf.mjs.map +1 -0
  25. package/dist/middleware/rate-limit.mjs +68 -0
  26. package/dist/middleware/rate-limit.mjs.map +1 -0
  27. package/dist/middleware/request-log.mjs +30 -0
  28. package/dist/middleware/request-log.mjs.map +1 -0
  29. package/dist/middleware/server-timing.mjs +39 -0
  30. package/dist/middleware/server-timing.mjs.map +1 -0
  31. package/dist/middleware/setup-gate.mjs +37 -0
  32. package/dist/middleware/setup-gate.mjs.map +1 -0
  33. package/dist/middleware/store-scope.mjs +33 -0
  34. package/dist/middleware/store-scope.mjs.map +1 -0
  35. package/dist/notify.d.mts +17 -0
  36. package/dist/notify.mjs +141 -0
  37. package/dist/notify.mjs.map +1 -0
  38. package/dist/pages/admin-system.mjs +243 -0
  39. package/dist/pages/admin-system.mjs.map +1 -0
  40. package/dist/pages/build-detail.mjs +392 -0
  41. package/dist/pages/build-detail.mjs.map +1 -0
  42. package/dist/pages/build-diff-actions.mjs +162 -0
  43. package/dist/pages/build-diff-actions.mjs.map +1 -0
  44. package/dist/pages/build-diff-comments.mjs +104 -0
  45. package/dist/pages/build-diff-comments.mjs.map +1 -0
  46. package/dist/pages/build-diff-header.mjs +117 -0
  47. package/dist/pages/build-diff-header.mjs.map +1 -0
  48. package/dist/pages/build-diff-model.mjs +48 -0
  49. package/dist/pages/build-diff-model.mjs.map +1 -0
  50. package/dist/pages/build-diff-nav.mjs +122 -0
  51. package/dist/pages/build-diff-nav.mjs.map +1 -0
  52. package/dist/pages/build-diff-shortcuts.mjs +78 -0
  53. package/dist/pages/build-diff-shortcuts.mjs.map +1 -0
  54. package/dist/pages/build-diff-stale.mjs +70 -0
  55. package/dist/pages/build-diff-stale.mjs.map +1 -0
  56. package/dist/pages/build-diff-viewer.mjs +70 -0
  57. package/dist/pages/build-diff-viewer.mjs.map +1 -0
  58. package/dist/pages/build-diff.mjs +110 -0
  59. package/dist/pages/build-diff.mjs.map +1 -0
  60. package/dist/pages/compute-jobs.mjs +165 -0
  61. package/dist/pages/compute-jobs.mjs.map +1 -0
  62. package/dist/pages/invite.mjs +91 -0
  63. package/dist/pages/invite.mjs.map +1 -0
  64. package/dist/pages/label-detail.mjs +154 -0
  65. package/dist/pages/label-detail.mjs.map +1 -0
  66. package/dist/pages/library.mjs +343 -0
  67. package/dist/pages/library.mjs.map +1 -0
  68. package/dist/pages/login.mjs +117 -0
  69. package/dist/pages/login.mjs.map +1 -0
  70. package/dist/pages/passkey-ceremony.mjs +97 -0
  71. package/dist/pages/passkey-ceremony.mjs.map +1 -0
  72. package/dist/pages/profile.mjs +364 -0
  73. package/dist/pages/profile.mjs.map +1 -0
  74. package/dist/pages/project-builds-table.mjs +151 -0
  75. package/dist/pages/project-builds-table.mjs.map +1 -0
  76. package/dist/pages/project-builds.mjs +143 -0
  77. package/dist/pages/project-builds.mjs.map +1 -0
  78. package/dist/pages/project-create.mjs +90 -0
  79. package/dist/pages/project-create.mjs.map +1 -0
  80. package/dist/pages/project-settings.mjs +95 -0
  81. package/dist/pages/project-settings.mjs.map +1 -0
  82. package/dist/pages/projects-card.mjs +194 -0
  83. package/dist/pages/projects-card.mjs.map +1 -0
  84. package/dist/pages/projects.mjs +129 -0
  85. package/dist/pages/projects.mjs.map +1 -0
  86. package/dist/pages/root.mjs +98 -0
  87. package/dist/pages/root.mjs.map +1 -0
  88. package/dist/pages/settings-general.mjs +138 -0
  89. package/dist/pages/settings-general.mjs.map +1 -0
  90. package/dist/pages/settings-labels.mjs +107 -0
  91. package/dist/pages/settings-labels.mjs.map +1 -0
  92. package/dist/pages/settings-members.mjs +192 -0
  93. package/dist/pages/settings-members.mjs.map +1 -0
  94. package/dist/pages/settings-notifications.mjs +240 -0
  95. package/dist/pages/settings-notifications.mjs.map +1 -0
  96. package/dist/pages/settings-status.mjs +137 -0
  97. package/dist/pages/settings-status.mjs.map +1 -0
  98. package/dist/pages/settings-tests.mjs +111 -0
  99. package/dist/pages/settings-tests.mjs.map +1 -0
  100. package/dist/pages/settings-tokens.mjs +90 -0
  101. package/dist/pages/settings-tokens.mjs.map +1 -0
  102. package/dist/pages/settings-webhooks.mjs +104 -0
  103. package/dist/pages/settings-webhooks.mjs.map +1 -0
  104. package/dist/pages/storybook.mjs +116 -0
  105. package/dist/pages/storybook.mjs.map +1 -0
  106. package/dist/retention-timer.mjs +65 -0
  107. package/dist/retention-timer.mjs.map +1 -0
  108. package/dist/routers/admin-pages.mjs +48 -0
  109. package/dist/routers/admin-pages.mjs.map +1 -0
  110. package/dist/routers/admin.mjs +113 -0
  111. package/dist/routers/admin.mjs.map +1 -0
  112. package/dist/routers/assets.mjs +64 -0
  113. package/dist/routers/assets.mjs.map +1 -0
  114. package/dist/routers/attempts.mjs +96 -0
  115. package/dist/routers/attempts.mjs.map +1 -0
  116. package/dist/routers/auth-sync.mjs +64 -0
  117. package/dist/routers/auth-sync.mjs.map +1 -0
  118. package/dist/routers/auth.mjs +236 -0
  119. package/dist/routers/auth.mjs.map +1 -0
  120. package/dist/routers/baseline-guard.mjs +83 -0
  121. package/dist/routers/baseline-guard.mjs.map +1 -0
  122. package/dist/routers/builds.handlers.mjs +217 -0
  123. package/dist/routers/builds.handlers.mjs.map +1 -0
  124. package/dist/routers/builds.mjs +381 -0
  125. package/dist/routers/builds.mjs.map +1 -0
  126. package/dist/routers/comments.mjs +109 -0
  127. package/dist/routers/comments.mjs.map +1 -0
  128. package/dist/routers/flash.mjs +35 -0
  129. package/dist/routers/flash.mjs.map +1 -0
  130. package/dist/routers/health-report.mjs +159 -0
  131. package/dist/routers/health-report.mjs.map +1 -0
  132. package/dist/routers/health.mjs +46 -0
  133. package/dist/routers/health.mjs.map +1 -0
  134. package/dist/routers/helpers.mjs +110 -0
  135. package/dist/routers/helpers.mjs.map +1 -0
  136. package/dist/routers/htmx.mjs +24 -0
  137. package/dist/routers/htmx.mjs.map +1 -0
  138. package/dist/routers/labels.mjs +118 -0
  139. package/dist/routers/labels.mjs.map +1 -0
  140. package/dist/routers/media.mjs +53 -0
  141. package/dist/routers/media.mjs.map +1 -0
  142. package/dist/routers/members.mjs +177 -0
  143. package/dist/routers/members.mjs.map +1 -0
  144. package/dist/routers/notification-channels.mjs +190 -0
  145. package/dist/routers/notification-channels.mjs.map +1 -0
  146. package/dist/routers/notification-subscriptions.mjs +120 -0
  147. package/dist/routers/notification-subscriptions.mjs.map +1 -0
  148. package/dist/routers/profile.mjs +276 -0
  149. package/dist/routers/profile.mjs.map +1 -0
  150. package/dist/routers/projects.mjs +133 -0
  151. package/dist/routers/projects.mjs.map +1 -0
  152. package/dist/routers/review-next.mjs +47 -0
  153. package/dist/routers/review-next.mjs.map +1 -0
  154. package/dist/routers/schemas.mjs +337 -0
  155. package/dist/routers/schemas.mjs.map +1 -0
  156. package/dist/routers/settings-general.mjs +104 -0
  157. package/dist/routers/settings-general.mjs.map +1 -0
  158. package/dist/routers/settings-labels.mjs +52 -0
  159. package/dist/routers/settings-labels.mjs.map +1 -0
  160. package/dist/routers/settings-members.mjs +58 -0
  161. package/dist/routers/settings-members.mjs.map +1 -0
  162. package/dist/routers/settings-notifications.mjs +108 -0
  163. package/dist/routers/settings-notifications.mjs.map +1 -0
  164. package/dist/routers/settings-status.mjs +77 -0
  165. package/dist/routers/settings-status.mjs.map +1 -0
  166. package/dist/routers/settings-tokens.mjs +40 -0
  167. package/dist/routers/settings-tokens.mjs.map +1 -0
  168. package/dist/routers/settings-webhooks.mjs +67 -0
  169. package/dist/routers/settings-webhooks.mjs.map +1 -0
  170. package/dist/routers/settings.handlers.mjs +149 -0
  171. package/dist/routers/settings.handlers.mjs.map +1 -0
  172. package/dist/routers/settings.mjs +25 -0
  173. package/dist/routers/settings.mjs.map +1 -0
  174. package/dist/routers/snapshots.mjs +240 -0
  175. package/dist/routers/snapshots.mjs.map +1 -0
  176. package/dist/routers/status-configs.mjs +108 -0
  177. package/dist/routers/status-configs.mjs.map +1 -0
  178. package/dist/routers/storybook.mjs +171 -0
  179. package/dist/routers/storybook.mjs.map +1 -0
  180. package/dist/routers/tokens.mjs +91 -0
  181. package/dist/routers/tokens.mjs.map +1 -0
  182. package/dist/routers/ui.mjs +166 -0
  183. package/dist/routers/ui.mjs.map +1 -0
  184. package/dist/routers/webhooks.mjs +97 -0
  185. package/dist/routers/webhooks.mjs.map +1 -0
  186. package/dist/routers/well-known.mjs +43 -0
  187. package/dist/routers/well-known.mjs.map +1 -0
  188. package/dist/runtime.mjs +52 -0
  189. package/dist/runtime.mjs.map +1 -0
  190. package/dist/store.mjs +20 -0
  191. package/dist/store.mjs.map +1 -0
  192. package/dist/ui/avatar.mjs +76 -0
  193. package/dist/ui/avatar.mjs.map +1 -0
  194. package/dist/ui/buttons.mjs +205 -0
  195. package/dist/ui/buttons.mjs.map +1 -0
  196. package/dist/ui/code-block.mjs +47 -0
  197. package/dist/ui/code-block.mjs.map +1 -0
  198. package/dist/ui/compare-stage.mjs +344 -0
  199. package/dist/ui/compare-stage.mjs.map +1 -0
  200. package/dist/ui/csrf-field.mjs +20 -0
  201. package/dist/ui/csrf-field.mjs.map +1 -0
  202. package/dist/ui/css.mjs +36 -0
  203. package/dist/ui/css.mjs.map +1 -0
  204. package/dist/ui/document.mjs +130 -0
  205. package/dist/ui/document.mjs.map +1 -0
  206. package/dist/ui/dropdown.mjs +181 -0
  207. package/dist/ui/dropdown.mjs.map +1 -0
  208. package/dist/ui/feedback.mjs +341 -0
  209. package/dist/ui/feedback.mjs.map +1 -0
  210. package/dist/ui/filter-input.mjs +58 -0
  211. package/dist/ui/filter-input.mjs.map +1 -0
  212. package/dist/ui/forms.mjs +291 -0
  213. package/dist/ui/forms.mjs.map +1 -0
  214. package/dist/ui/icons/icon.mjs +53 -0
  215. package/dist/ui/icons/icon.mjs.map +1 -0
  216. package/dist/ui/icons/paths.mjs +61 -0
  217. package/dist/ui/icons/paths.mjs.map +1 -0
  218. package/dist/ui/icons/sprite.mjs +26 -0
  219. package/dist/ui/icons/sprite.mjs.map +1 -0
  220. package/dist/ui/kbd.mjs +33 -0
  221. package/dist/ui/kbd.mjs.map +1 -0
  222. package/dist/ui/layout.mjs +173 -0
  223. package/dist/ui/layout.mjs.map +1 -0
  224. package/dist/ui/progress.mjs +44 -0
  225. package/dist/ui/progress.mjs.map +1 -0
  226. package/dist/ui/segmented.mjs +77 -0
  227. package/dist/ui/segmented.mjs.map +1 -0
  228. package/dist/ui/shell/account.mjs +161 -0
  229. package/dist/ui/shell/account.mjs.map +1 -0
  230. package/dist/ui/shell/bare.mjs +69 -0
  231. package/dist/ui/shell/bare.mjs.map +1 -0
  232. package/dist/ui/shell/boot-script.mjs +46 -0
  233. package/dist/ui/shell/boot-script.mjs.map +1 -0
  234. package/dist/ui/shell/client-script.mjs +259 -0
  235. package/dist/ui/shell/client-script.mjs.map +1 -0
  236. package/dist/ui/shell/nav.mjs +111 -0
  237. package/dist/ui/shell/nav.mjs.map +1 -0
  238. package/dist/ui/shell/review-script.mjs +195 -0
  239. package/dist/ui/shell/review-script.mjs.map +1 -0
  240. package/dist/ui/shell/sidebar.mjs +323 -0
  241. package/dist/ui/shell/sidebar.mjs.map +1 -0
  242. package/dist/ui/shell/topbar.mjs +268 -0
  243. package/dist/ui/shell/topbar.mjs.map +1 -0
  244. package/dist/ui/stacks.mjs +82 -0
  245. package/dist/ui/stacks.mjs.map +1 -0
  246. package/dist/ui/styles/base.mjs +41 -0
  247. package/dist/ui/styles/base.mjs.map +1 -0
  248. package/dist/ui/styles/behavior.mjs +43 -0
  249. package/dist/ui/styles/behavior.mjs.map +1 -0
  250. package/dist/ui/styles/review-layout.mjs +347 -0
  251. package/dist/ui/styles/review-layout.mjs.map +1 -0
  252. package/dist/ui/styles/review.mjs +86 -0
  253. package/dist/ui/styles/review.mjs.map +1 -0
  254. package/dist/ui/styles/tokens.mjs +132 -0
  255. package/dist/ui/styles/tokens.mjs.map +1 -0
  256. package/dist/ui/styles.mjs +19 -0
  257. package/dist/ui/styles.mjs.map +1 -0
  258. package/dist/ui/subnav.mjs +91 -0
  259. package/dist/ui/subnav.mjs.map +1 -0
  260. package/dist/ui/table.mjs +73 -0
  261. package/dist/ui/table.mjs.map +1 -0
  262. package/dist/ui/theme.mjs +64 -0
  263. package/dist/ui/theme.mjs.map +1 -0
  264. package/dist/ui/thumbnail.mjs +51 -0
  265. package/dist/ui/thumbnail.mjs.map +1 -0
  266. package/dist/ui/time.mjs +50 -0
  267. package/dist/ui/time.mjs.map +1 -0
  268. package/package.json +92 -0
@@ -0,0 +1,291 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { css } from "./css.mjs";
5
+ import { Icon } from "./icons/icon.mjs";
6
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
7
+ //#region src/ui/forms.tsx
8
+ const fieldWrap = css`
9
+ /* field */
10
+ display: grid;
11
+ gap: var(--space-1);
12
+ /* Don't stretch rows when a neighbouring field is taller (hint text). */
13
+ align-content: start;
14
+ margin-bottom: var(--space-4);
15
+ `;
16
+ const fieldWrapInline = css`
17
+ /* field-inline */
18
+ ${fieldWrap}
19
+ grid-template-columns: auto minmax(0, 1fr);
20
+ align-items: center;
21
+ gap: var(--space-2);
22
+ margin-bottom: 0;
23
+ `;
24
+ const fieldLabel = css`
25
+ /* field-label */
26
+ color: var(--text-primary);
27
+ font-weight: 600;
28
+ font-size: var(--text-sm);
29
+ & span[aria-hidden] {
30
+ color: var(--status-rejected);
31
+ }
32
+ `;
33
+ const fieldInput = css`
34
+ /* field-input */
35
+ width: 100%;
36
+ min-height: 40px;
37
+ padding: 0.5rem 0.75rem;
38
+ border-radius: var(--radius);
39
+ border: 1px solid var(--border);
40
+ background: var(--surface-card);
41
+ color: var(--text-primary);
42
+ font: inherit;
43
+ font-size: var(--text-base);
44
+ box-shadow: var(--shadow);
45
+ transition:
46
+ border-color var(--dur-fast) var(--ease),
47
+ box-shadow var(--dur-fast) var(--ease);
48
+ &::placeholder {
49
+ color: var(--text-muted);
50
+ }
51
+ &:hover:not(:disabled) {
52
+ border-color: color-mix(in srgb, var(--text-secondary) 45%, var(--border));
53
+ }
54
+ &:focus {
55
+ outline: none;
56
+ border-color: var(--ring);
57
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent);
58
+ }
59
+ &:disabled {
60
+ background: var(--surface-muted);
61
+ color: var(--text-secondary);
62
+ cursor: not-allowed;
63
+ }
64
+ `;
65
+ const fieldInputError = css`
66
+ /* field-input-error */
67
+ ${fieldInput}
68
+ border-color: var(--status-rejected);
69
+ &:focus {
70
+ border-color: var(--status-rejected);
71
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-rejected) 25%, transparent);
72
+ }
73
+ `;
74
+ const fieldTextarea = css`
75
+ /* field-textarea */
76
+ ${fieldInput}
77
+ resize: vertical;
78
+ min-height: 5.5rem;
79
+ line-height: var(--leading-normal);
80
+ `;
81
+ const fieldTextareaError = css`
82
+ /* field-textarea-error */
83
+ ${fieldInputError}
84
+ resize: vertical;
85
+ min-height: 5.5rem;
86
+ line-height: var(--leading-normal);
87
+ `;
88
+ const fieldHint = css`
89
+ /* field-hint */
90
+ margin: 0;
91
+ color: var(--text-secondary);
92
+ font-size: var(--text-sm);
93
+ `;
94
+ const fieldError = css`
95
+ /* field-error */
96
+ display: flex;
97
+ gap: var(--space-1);
98
+ align-items: flex-start;
99
+ margin: 0;
100
+ color: var(--status-rejected-fg);
101
+ font-size: var(--text-sm);
102
+ font-weight: 600;
103
+ `;
104
+ const formActions = css`
105
+ /* form-actions */
106
+ display: flex;
107
+ flex-wrap: wrap;
108
+ gap: var(--space-2);
109
+ align-items: center;
110
+ margin-top: var(--space-4);
111
+ `;
112
+ const formActionsEnd = css`
113
+ /* form-actions-end */
114
+ ${formActions}
115
+ justify-content: flex-end;
116
+ `;
117
+ const formActionsSplit = css`
118
+ /* form-actions-split */
119
+ ${formActions}
120
+ justify-content: space-between;
121
+ `;
122
+ /** Row of form buttons: primary action first; `align="end"` right-aligns, `"split"` spreads. */
123
+ const FormActions = ({ align = "start", children }) => {
124
+ return /* @__PURE__ */ jsx("div", {
125
+ class: align === "end" ? formActionsEnd : align === "split" ? formActionsSplit : formActions,
126
+ children
127
+ });
128
+ };
129
+ function fieldDescribedBy(name, error, hint) {
130
+ if (error) return `${name}-error`;
131
+ if (hint) return `${name}-hint`;
132
+ }
133
+ const FieldAssistant = ({ name, error, hint }) => {
134
+ if (error) return /* @__PURE__ */ jsxs("p", {
135
+ class: fieldError,
136
+ id: `${name}-error`,
137
+ role: "alert",
138
+ children: [/* @__PURE__ */ jsx(Icon, {
139
+ name: "x-circle",
140
+ size: "sm"
141
+ }), /* @__PURE__ */ jsx("span", { children: error })]
142
+ });
143
+ if (hint) return /* @__PURE__ */ jsx("p", {
144
+ class: fieldHint,
145
+ id: `${name}-hint`,
146
+ children: hint
147
+ });
148
+ return null;
149
+ };
150
+ /** Labeled text input with error and hint states. */
151
+ const Field = ({ label, name, type = "text", value, placeholder, required, disabled, autofocus, pattern, min, max, step, layout = "stack", error, hint, autocomplete }) => {
152
+ return /* @__PURE__ */ jsxs("div", {
153
+ class: layout === "inline" ? fieldWrapInline : fieldWrap,
154
+ children: [
155
+ /* @__PURE__ */ jsxs("label", {
156
+ class: fieldLabel,
157
+ for: name,
158
+ children: [label, required ? /* @__PURE__ */ jsx("span", {
159
+ "aria-hidden": "true",
160
+ children: " *"
161
+ }) : null]
162
+ }),
163
+ /* @__PURE__ */ jsx("input", {
164
+ class: error ? fieldInputError : fieldInput,
165
+ id: name,
166
+ name,
167
+ type,
168
+ value,
169
+ placeholder,
170
+ required,
171
+ disabled,
172
+ autofocus,
173
+ pattern,
174
+ min,
175
+ max,
176
+ step,
177
+ "aria-invalid": error ? "true" : void 0,
178
+ "aria-describedby": fieldDescribedBy(name, error, hint),
179
+ autocomplete
180
+ }),
181
+ /* @__PURE__ */ jsx(FieldAssistant, {
182
+ name,
183
+ error,
184
+ hint
185
+ })
186
+ ]
187
+ });
188
+ };
189
+ const checkLabel = css`
190
+ /* check-label */
191
+ display: flex;
192
+ gap: var(--space-2);
193
+ align-items: center;
194
+ font-weight: 500;
195
+ font-size: var(--text-base);
196
+ cursor: pointer;
197
+ `;
198
+ const checkInput = css`
199
+ /* check-input */
200
+ width: 1rem;
201
+ height: 1rem;
202
+ accent-color: var(--accent);
203
+ flex: none;
204
+ `;
205
+ /** Inline checkbox row with an optional hint. */
206
+ const CheckField = ({ label, name, value, checked, disabled, hint }) => {
207
+ return /* @__PURE__ */ jsxs("div", {
208
+ class: fieldWrap,
209
+ children: [/* @__PURE__ */ jsxs("label", {
210
+ class: checkLabel,
211
+ for: name,
212
+ children: [/* @__PURE__ */ jsx("input", {
213
+ class: checkInput,
214
+ type: "checkbox",
215
+ id: name,
216
+ name,
217
+ value,
218
+ checked,
219
+ disabled
220
+ }), label]
221
+ }), hint ? /* @__PURE__ */ jsx("p", {
222
+ class: fieldHint,
223
+ id: `${name}-hint`,
224
+ children: hint
225
+ }) : null]
226
+ });
227
+ };
228
+ /** Labeled textarea with error and hint states. */
229
+ const TextareaField = ({ label, name, value, placeholder, rows = 3, required, hint, error }) => {
230
+ return /* @__PURE__ */ jsxs("div", {
231
+ class: fieldWrap,
232
+ children: [
233
+ /* @__PURE__ */ jsx("label", {
234
+ class: fieldLabel,
235
+ for: name,
236
+ children: label
237
+ }),
238
+ /* @__PURE__ */ jsx("textarea", {
239
+ class: error ? fieldTextareaError : fieldTextarea,
240
+ id: name,
241
+ name,
242
+ placeholder,
243
+ rows,
244
+ required,
245
+ "aria-invalid": error ? "true" : void 0,
246
+ "aria-describedby": fieldDescribedBy(name, error, hint),
247
+ children: value
248
+ }),
249
+ /* @__PURE__ */ jsx(FieldAssistant, {
250
+ name,
251
+ error,
252
+ hint
253
+ })
254
+ ]
255
+ });
256
+ };
257
+ /** Labeled select dropdown with an optional hint. */
258
+ const SelectField = ({ label, name, value, options, hint, disabled, required, layout = "stack" }) => {
259
+ return /* @__PURE__ */ jsxs("div", {
260
+ class: layout === "inline" ? fieldWrapInline : fieldWrap,
261
+ children: [
262
+ /* @__PURE__ */ jsx("label", {
263
+ class: fieldLabel,
264
+ for: name,
265
+ children: label
266
+ }),
267
+ /* @__PURE__ */ jsx("select", {
268
+ class: fieldInput,
269
+ id: name,
270
+ name,
271
+ disabled,
272
+ required,
273
+ "aria-describedby": hint ? `${name}-hint` : void 0,
274
+ children: options.map((opt) => /* @__PURE__ */ jsx("option", {
275
+ value: opt.value,
276
+ selected: opt.value === value,
277
+ children: opt.label
278
+ }))
279
+ }),
280
+ hint ? /* @__PURE__ */ jsx("p", {
281
+ class: fieldHint,
282
+ id: `${name}-hint`,
283
+ children: hint
284
+ }) : null
285
+ ]
286
+ });
287
+ };
288
+ //#endregion
289
+ export { CheckField, Field, FormActions, SelectField, TextareaField };
290
+
291
+ //# sourceMappingURL=forms.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forms.mjs","names":[],"sources":["../../src/ui/forms.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype FieldLayout = \"stack\" | \"inline\";\n\nconst fieldWrap = css`\n /* field */\n display: grid;\n gap: var(--space-1);\n /* Don't stretch rows when a neighbouring field is taller (hint text). */\n align-content: start;\n margin-bottom: var(--space-4);\n`;\n\nconst fieldWrapInline = css`\n /* field-inline */\n ${fieldWrap}\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: var(--space-2);\n margin-bottom: 0;\n`;\n\nconst fieldLabel = css`\n /* field-label */\n color: var(--text-primary);\n font-weight: 600;\n font-size: var(--text-sm);\n & span[aria-hidden] {\n color: var(--status-rejected);\n }\n`;\n\nconst fieldInput = css`\n /* field-input */\n width: 100%;\n min-height: 40px;\n padding: 0.5rem 0.75rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: var(--surface-card);\n color: var(--text-primary);\n font: inherit;\n font-size: var(--text-base);\n box-shadow: var(--shadow);\n transition:\n border-color var(--dur-fast) var(--ease),\n box-shadow var(--dur-fast) var(--ease);\n &::placeholder {\n color: var(--text-muted);\n }\n &:hover:not(:disabled) {\n border-color: color-mix(in srgb, var(--text-secondary) 45%, var(--border));\n }\n &:focus {\n outline: none;\n border-color: var(--ring);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent);\n }\n &:disabled {\n background: var(--surface-muted);\n color: var(--text-secondary);\n cursor: not-allowed;\n }\n`;\n\nconst fieldInputError = css`\n /* field-input-error */\n ${fieldInput}\n border-color: var(--status-rejected);\n &:focus {\n border-color: var(--status-rejected);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-rejected) 25%, transparent);\n }\n`;\n\nconst fieldTextarea = css`\n /* field-textarea */\n ${fieldInput}\n resize: vertical;\n min-height: 5.5rem;\n line-height: var(--leading-normal);\n`;\n\nconst fieldTextareaError = css`\n /* field-textarea-error */\n ${fieldInputError}\n resize: vertical;\n min-height: 5.5rem;\n line-height: var(--leading-normal);\n`;\n\nconst fieldHint = css`\n /* field-hint */\n margin: 0;\n color: var(--text-secondary);\n font-size: var(--text-sm);\n`;\n\nconst fieldError = css`\n /* field-error */\n display: flex;\n gap: var(--space-1);\n align-items: flex-start;\n margin: 0;\n color: var(--status-rejected-fg);\n font-size: var(--text-sm);\n font-weight: 600;\n`;\n\nconst formActions = css`\n /* form-actions */\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n align-items: center;\n margin-top: var(--space-4);\n`;\n\nconst formActionsEnd = css`\n /* form-actions-end */\n ${formActions}\n justify-content: flex-end;\n`;\n\nconst formActionsSplit = css`\n /* form-actions-split */\n ${formActions}\n justify-content: space-between;\n`;\n\n/** Row of form buttons: primary action first; `align=\"end\"` right-aligns, `\"split\"` spreads. */\nexport const FormActions: FC<{ align?: \"start\" | \"end\" | \"split\"; children?: unknown }> = ({\n align = \"start\",\n children,\n}) => {\n const cls = align === \"end\" ? formActionsEnd : align === \"split\" ? formActionsSplit : formActions;\n return <div class={cls}>{children}</div>;\n};\n\nfunction fieldDescribedBy(\n name: string,\n error: string | undefined,\n hint: string | undefined,\n): string | undefined {\n if (error) {\n return `${name}-error`;\n }\n if (hint) {\n return `${name}-hint`;\n }\n return undefined;\n}\n\n// eslint-disable-next-line promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString>\nconst FieldAssistant: FC<{ name: string; error: string | undefined; hint: string | undefined }> = ({\n name,\n error,\n hint,\n}) => {\n if (error) {\n return (\n <p class={fieldError} id={`${name}-error`} role=\"alert\">\n <Icon name=\"x-circle\" size=\"sm\" />\n <span>{error}</span>\n </p>\n );\n }\n if (hint) {\n return (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n );\n }\n return null;\n};\n\n/** Labeled text input with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Field: FC<{\n label: string;\n name: string;\n type?: string;\n value?: string;\n placeholder?: string;\n required?: boolean;\n disabled?: boolean;\n autofocus?: boolean;\n pattern?: string;\n min?: string;\n max?: string;\n step?: string;\n layout?: FieldLayout;\n error?: string;\n hint?: string;\n autocomplete?: string;\n}> = ({\n label,\n name,\n type = \"text\",\n value,\n placeholder,\n required,\n disabled,\n autofocus,\n pattern,\n min,\n max,\n step,\n layout = \"stack\",\n error,\n hint,\n autocomplete,\n}) => {\n return (\n <div class={layout === \"inline\" ? fieldWrapInline : fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n {required ? <span aria-hidden=\"true\"> *</span> : null}\n </label>\n <input\n class={error ? fieldInputError : fieldInput}\n id={name}\n name={name}\n type={type}\n value={value}\n placeholder={placeholder}\n required={required}\n disabled={disabled}\n autofocus={autofocus}\n pattern={pattern}\n min={min}\n max={max}\n step={step}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(name, error, hint)}\n autocomplete={autocomplete}\n />\n <FieldAssistant name={name} error={error} hint={hint} />\n </div>\n );\n};\n\nconst checkLabel = css`\n /* check-label */\n display: flex;\n gap: var(--space-2);\n align-items: center;\n font-weight: 500;\n font-size: var(--text-base);\n cursor: pointer;\n`;\n\nconst checkInput = css`\n /* check-input */\n width: 1rem;\n height: 1rem;\n accent-color: var(--accent);\n flex: none;\n`;\n\n/** Inline checkbox row with an optional hint. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const CheckField: FC<{\n label: unknown;\n name: string;\n value?: string;\n checked?: boolean;\n disabled?: boolean;\n hint?: string;\n}> = ({ label, name, value, checked, disabled, hint }) => {\n return (\n <div class={fieldWrap}>\n <label class={checkLabel} for={name}>\n <input\n class={checkInput}\n type=\"checkbox\"\n id={name}\n name={name}\n value={value}\n checked={checked}\n disabled={disabled}\n />\n {label}\n </label>\n {hint ? (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n ) : null}\n </div>\n );\n};\n\n/** Labeled textarea with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const TextareaField: FC<{\n label: string;\n name: string;\n value?: string;\n placeholder?: string;\n rows?: number;\n required?: boolean;\n hint?: string;\n error?: string;\n}> = ({ label, name, value, placeholder, rows = 3, required, hint, error }) => {\n return (\n <div class={fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n </label>\n <textarea\n class={error ? fieldTextareaError : fieldTextarea}\n id={name}\n name={name}\n placeholder={placeholder}\n rows={rows}\n required={required}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(name, error, hint)}\n >\n {value}\n </textarea>\n <FieldAssistant name={name} error={error} hint={hint} />\n </div>\n );\n};\n\n/** Labeled select dropdown with an optional hint. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const SelectField: FC<{\n label: string;\n name: string;\n value?: string;\n options: { value: string; label: string }[];\n hint?: string;\n disabled?: boolean;\n required?: boolean;\n layout?: FieldLayout;\n}> = ({ label, name, value, options, hint, disabled, required, layout = \"stack\" }) => {\n return (\n <div class={layout === \"inline\" ? fieldWrapInline : fieldWrap}>\n <label class={fieldLabel} for={name}>\n {label}\n </label>\n <select\n class={fieldInput}\n id={name}\n name={name}\n disabled={disabled}\n required={required}\n aria-describedby={hint ? `${name}-hint` : undefined}\n >\n {options.map((opt) => (\n <option value={opt.value} selected={opt.value === value}>\n {opt.label}\n </option>\n ))}\n </select>\n {hint ? (\n <p class={fieldHint} id={`${name}-hint`}>\n {hint}\n </p>\n ) : null}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { css } from "../css.mjs";
5
+ import { iconHref } from "./sprite.mjs";
6
+ import { jsx } from "hono/jsx/jsx-runtime";
7
+ //#region src/ui/icons/icon.tsx
8
+ const iconBase = css`
9
+ /* icon */
10
+ flex: none;
11
+ width: 1.125rem;
12
+ height: 1.125rem;
13
+ fill: none;
14
+ stroke: currentColor;
15
+ stroke-width: 2;
16
+ stroke-linecap: round;
17
+ stroke-linejoin: round;
18
+ vertical-align: -0.2em;
19
+ `;
20
+ const iconSizes = {
21
+ sm: css`
22
+ /* icon-sm */
23
+ ${iconBase}
24
+ width: 0.875rem;
25
+ height: 0.875rem;
26
+ `,
27
+ md: iconBase,
28
+ lg: css`
29
+ /* icon-lg */
30
+ ${iconBase}
31
+ width: 1.5rem;
32
+ height: 1.5rem;
33
+ `
34
+ };
35
+ /**
36
+ * Decorative icon from the cached sprite. Pass `label` when the icon is the
37
+ * only content of a control; otherwise it is hidden from assistive tech.
38
+ */
39
+ const Icon = ({ name, size = "md", label }) => {
40
+ return /* @__PURE__ */ jsx("svg", {
41
+ class: iconSizes[size],
42
+ viewBox: "0 0 24 24",
43
+ "aria-hidden": label ? void 0 : "true",
44
+ role: label ? "img" : void 0,
45
+ "aria-label": label,
46
+ focusable: "false",
47
+ children: /* @__PURE__ */ jsx("use", { href: iconHref(name) })
48
+ });
49
+ };
50
+ //#endregion
51
+ export { Icon };
52
+
53
+ //# sourceMappingURL=icon.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon.mjs","names":[],"sources":["../../../src/ui/icons/icon.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"../css.ts\";\nimport type { IconName } from \"./paths.ts\";\nimport { iconHref } from \"./sprite.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype IconSize = \"sm\" | \"md\" | \"lg\";\n\nconst iconBase = css`\n /* icon */\n flex: none;\n width: 1.125rem;\n height: 1.125rem;\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n vertical-align: -0.2em;\n`;\n\nconst iconSm = css`\n /* icon-sm */\n ${iconBase}\n width: 0.875rem;\n height: 0.875rem;\n`;\n\nconst iconLg = css`\n /* icon-lg */\n ${iconBase}\n width: 1.5rem;\n height: 1.5rem;\n`;\n\nconst iconSizes: Record<IconSize, Promise<string>> = { sm: iconSm, md: iconBase, lg: iconLg };\n\n/**\n * Decorative icon from the cached sprite. Pass `label` when the icon is the\n * only content of a control; otherwise it is hidden from assistive tech.\n */\nexport const Icon: FC<{ name: IconName; size?: IconSize; label?: string }> = ({\n name,\n size = \"md\",\n label,\n}) => {\n return (\n <svg\n class={iconSizes[size]}\n viewBox=\"0 0 24 24\"\n aria-hidden={label ? undefined : \"true\"}\n role={label ? \"img\" : undefined}\n aria-label={label}\n focusable=\"false\"\n >\n <use href={iconHref(name)} />\n </svg>\n );\n};\n"],"mappings":";;;;;;;AASA,MAAM,WAAW,GAAG;;;;;;;;;;;;AA2BpB,MAAM,YAA+C;CAAE,IAAI,GAdzC;;IAEd,SAAS;;;;CAYsD,IAAI;CAAU,IAAI,GAPnE;;IAEd,SAAS;;;;AAK+E;;;;;AAM5F,MAAa,QAAiE,EAC5E,MACA,OAAO,MACP,YACI;CACJ,OACE,oBAAC,OAAD;EACE,OAAO,UAAU;EACjB,SAAQ;EACR,eAAa,QAAQ,KAAA,IAAY;EACjC,MAAM,QAAQ,QAAQ,KAAA;EACtB,cAAY;EACZ,WAAU;EAEV,UAAA,oBAAC,OAAD,EAAK,MAAM,SAAS,IAAI,EAAI,CAAA;CACzB,CAAA;AAET"}
@@ -0,0 +1,61 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/ui/icons/paths.ts
5
+ /**
6
+ * Icon set: inner SVG markup per icon on a 24x24 grid, stroke-based
7
+ * (Lucide-style, ISC). Add an icon here and it is in the sprite and the
8
+ * `IconName` union automatically.
9
+ */
10
+ const ICON_PATHS = {
11
+ check: "<path d=\"M20 6 9 17l-5-5\"/>",
12
+ x: "<path d=\"M18 6 6 18M6 6l12 12\"/>",
13
+ plus: "<path d=\"M5 12h14M12 5v14\"/>",
14
+ "chevron-down": "<path d=\"m6 9 6 6 6-6\"/>",
15
+ "chevron-up": "<path d=\"m18 15-6-6-6 6\"/>",
16
+ "chevron-right": "<path d=\"m9 18 6-6-6-6\"/>",
17
+ "chevron-left": "<path d=\"m15 18-6-6 6-6\"/>",
18
+ "chevrons-left": "<path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/>",
19
+ "arrow-right": "<path d=\"M5 12h14M12 5l7 7-7 7\"/>",
20
+ folder: "<path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>",
21
+ layers: "<path d=\"m12 2 10 5-10 5L2 7zM2 17l10 5 10-5M2 12l10 5 10-5\"/>",
22
+ image: "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21\"/>",
23
+ "git-branch": "<path d=\"M6 3v12\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/>",
24
+ "git-commit": "<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M3 12h6M15 12h6\"/>",
25
+ activity: "<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/>",
26
+ tag: "<path d=\"M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z\"/><circle cx=\"7.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/>",
27
+ settings: "<path d=\"M12.2 2h-.4a2 2 0 0 0-2 2v.2a2 2 0 0 1-1 1.7l-.4.3a2 2 0 0 1-2 0l-.2-.1a2 2 0 0 0-2.7.7l-.2.4a2 2 0 0 0 .7 2.7l.2.1a2 2 0 0 1 1 1.7v.5a2 2 0 0 1-1 1.8l-.2.1a2 2 0 0 0-.7 2.7l.2.4a2 2 0 0 0 2.7.7l.2-.1a2 2 0 0 1 2 0l.4.3a2 2 0 0 1 1 1.7v.2a2 2 0 0 0 2 2h.4a2 2 0 0 0 2-2v-.2a2 2 0 0 1 1-1.7l.4-.3a2 2 0 0 1 2 0l.2.1a2 2 0 0 0 2.7-.7l.2-.4a2 2 0 0 0-.7-2.7l-.2-.1a2 2 0 0 1-1-1.8v-.5a2 2 0 0 1 1-1.7l.2-.1a2 2 0 0 0 .7-2.7l-.2-.4a2 2 0 0 0-2.7-.7l-.2.1a2 2 0 0 1-2 0l-.4-.3a2 2 0 0 1-1-1.7V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>",
28
+ users: "<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8\"/>",
29
+ user: "<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>",
30
+ key: "<path d=\"m21 2-9.6 9.6M15.5 7.5l3 3L22 7l-3-3\"/><circle cx=\"7.5\" cy=\"15.5\" r=\"5.5\"/>",
31
+ bell: "<path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10.3 21a1.9 1.9 0 0 0 3.4 0\"/>",
32
+ webhook: "<path d=\"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2M9 17l3.13-5.78c.53-.97.43-2.22-.26-3.07a4 4 0 1 1 6.58-4.2c.38.6.58 1.3.57 2M6 17a4 4 0 0 1 6-3.5\"/><path d=\"M12 17h6a2 2 0 1 1 0 4\"/>",
33
+ code: "<path d=\"m16 18 6-6-6-6M8 6l-6 6 6 6\"/>",
34
+ terminal: "<path d=\"m4 17 6-6-6-6M12 19h8\"/>",
35
+ shield: "<path d=\"M20 13c0 5-3.5 7.5-7.7 8.9a1 1 0 0 1-.6 0C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.2-2.7a1.2 1.2 0 0 1 1.6 0C14.5 3.8 17 5 19 5a1 1 0 0 1 1 1z\"/>",
36
+ search: "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>",
37
+ sun: "<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4\"/>",
38
+ moon: "<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>",
39
+ monitor: "<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/>",
40
+ menu: "<path d=\"M4 6h16M4 12h16M4 18h16\"/>",
41
+ "more-horizontal": "<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>",
42
+ "log-out": "<path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9\"/>",
43
+ "external-link": "<path d=\"M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/>",
44
+ copy: "<rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/>",
45
+ "check-circle": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/>",
46
+ "x-circle": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6M9 9l6 6\"/>",
47
+ "alert-triangle": "<path d=\"m21.7 18-8-14a2 2 0 0 0-3.5 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3M12 9v4M12 17h.01\"/>",
48
+ info: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/>",
49
+ clock: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/>",
50
+ loader: "<path d=\"M21 12a9 9 0 1 1-6.2-8.6\"/>",
51
+ "message-square": "<path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>",
52
+ columns: "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M12 3v18\"/>",
53
+ maximize: "<path d=\"M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3\"/>",
54
+ "zoom-in": "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3M11 8v6M8 11h6\"/>",
55
+ book: "<path d=\"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20\"/>",
56
+ server: "<rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\"/><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/>"
57
+ };
58
+ //#endregion
59
+ export { ICON_PATHS };
60
+
61
+ //# sourceMappingURL=paths.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.mjs","names":[],"sources":["../../../src/ui/icons/paths.ts"],"sourcesContent":["/**\n * Icon set: inner SVG markup per icon on a 24x24 grid, stroke-based\n * (Lucide-style, ISC). Add an icon here and it is in the sprite and the\n * `IconName` union automatically.\n */\nexport const ICON_PATHS = {\n check: '<path d=\"M20 6 9 17l-5-5\"/>',\n x: '<path d=\"M18 6 6 18M6 6l12 12\"/>',\n plus: '<path d=\"M5 12h14M12 5v14\"/>',\n \"chevron-down\": '<path d=\"m6 9 6 6 6-6\"/>',\n \"chevron-up\": '<path d=\"m18 15-6-6-6 6\"/>',\n \"chevron-right\": '<path d=\"m9 18 6-6-6-6\"/>',\n \"chevron-left\": '<path d=\"m15 18-6-6 6-6\"/>',\n \"chevrons-left\": '<path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/>',\n \"arrow-right\": '<path d=\"M5 12h14M12 5l7 7-7 7\"/>',\n folder:\n '<path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>',\n layers: '<path d=\"m12 2 10 5-10 5L2 7zM2 17l10 5 10-5M2 12l10 5 10-5\"/>',\n image:\n '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21\"/>',\n \"git-branch\":\n '<path d=\"M6 3v12\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/>',\n \"git-commit\": '<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M3 12h6M15 12h6\"/>',\n activity: '<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/>',\n tag: '<path d=\"M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z\"/><circle cx=\"7.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/>',\n settings:\n '<path d=\"M12.2 2h-.4a2 2 0 0 0-2 2v.2a2 2 0 0 1-1 1.7l-.4.3a2 2 0 0 1-2 0l-.2-.1a2 2 0 0 0-2.7.7l-.2.4a2 2 0 0 0 .7 2.7l.2.1a2 2 0 0 1 1 1.7v.5a2 2 0 0 1-1 1.8l-.2.1a2 2 0 0 0-.7 2.7l.2.4a2 2 0 0 0 2.7.7l.2-.1a2 2 0 0 1 2 0l.4.3a2 2 0 0 1 1 1.7v.2a2 2 0 0 0 2 2h.4a2 2 0 0 0 2-2v-.2a2 2 0 0 1 1-1.7l.4-.3a2 2 0 0 1 2 0l.2.1a2 2 0 0 0 2.7-.7l.2-.4a2 2 0 0 0-.7-2.7l-.2-.1a2 2 0 0 1-1-1.8v-.5a2 2 0 0 1 1-1.7l.2-.1a2 2 0 0 0 .7-2.7l-.2-.4a2 2 0 0 0-2.7-.7l-.2.1a2 2 0 0 1-2 0l-.4-.3a2 2 0 0 1-1-1.7V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>',\n users:\n '<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8\"/>',\n user: '<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>',\n key: '<path d=\"m21 2-9.6 9.6M15.5 7.5l3 3L22 7l-3-3\"/><circle cx=\"7.5\" cy=\"15.5\" r=\"5.5\"/>',\n bell: '<path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10.3 21a1.9 1.9 0 0 0 3.4 0\"/>',\n webhook:\n '<path d=\"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2M9 17l3.13-5.78c.53-.97.43-2.22-.26-3.07a4 4 0 1 1 6.58-4.2c.38.6.58 1.3.57 2M6 17a4 4 0 0 1 6-3.5\"/><path d=\"M12 17h6a2 2 0 1 1 0 4\"/>',\n code: '<path d=\"m16 18 6-6-6-6M8 6l-6 6 6 6\"/>',\n terminal: '<path d=\"m4 17 6-6-6-6M12 19h8\"/>',\n shield:\n '<path d=\"M20 13c0 5-3.5 7.5-7.7 8.9a1 1 0 0 1-.6 0C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.2-2.7a1.2 1.2 0 0 1 1.6 0C14.5 3.8 17 5 19 5a1 1 0 0 1 1 1z\"/>',\n search: '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>',\n sun: '<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4\"/>',\n moon: '<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>',\n monitor: '<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/>',\n menu: '<path d=\"M4 6h16M4 12h16M4 18h16\"/>',\n \"more-horizontal\":\n '<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>',\n \"log-out\": '<path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9\"/>',\n \"external-link\":\n '<path d=\"M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/>',\n copy: '<rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/>',\n \"check-circle\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/>',\n \"x-circle\": '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6M9 9l6 6\"/>',\n \"alert-triangle\":\n '<path d=\"m21.7 18-8-14a2 2 0 0 0-3.5 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3M12 9v4M12 17h.01\"/>',\n info: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/>',\n clock: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/>',\n loader: '<path d=\"M21 12a9 9 0 1 1-6.2-8.6\"/>',\n \"message-square\": '<path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>',\n columns: '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M12 3v18\"/>',\n maximize:\n '<path d=\"M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3\"/>',\n \"zoom-in\": '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3M11 8v6M8 11h6\"/>',\n book: '<path d=\"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20\"/>',\n server:\n '<rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\"/><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/>',\n} as const;\n\n/** Every icon name available in the sprite. */\nexport type IconName = keyof typeof ICON_PATHS;\n"],"mappings":";;;;;;;;;AAKA,MAAa,aAAa;CACxB,OAAO;CACP,GAAG;CACH,MAAM;CACN,gBAAgB;CAChB,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,iBAAiB;CACjB,eAAe;CACf,QACE;CACF,QAAQ;CACR,OACE;CACF,cACE;CACF,cAAc;CACd,UAAU;CACV,KAAK;CACL,UACE;CACF,OACE;CACF,MAAM;CACN,KAAK;CACL,MAAM;CACN,SACE;CACF,MAAM;CACN,UAAU;CACV,QACE;CACF,QAAQ;CACR,KAAK;CACL,MAAM;CACN,SAAS;CACT,MAAM;CACN,mBACE;CACF,WAAW;CACX,iBACE;CACF,MAAM;CACN,gBAAgB;CAChB,YAAY;CACZ,kBACE;CACF,MAAM;CACN,OAAO;CACP,QAAQ;CACR,kBAAkB;CAClB,SAAS;CACT,UACE;CACF,WAAW;CACX,MAAM;CACN,QACE;AACJ"}
@@ -0,0 +1,26 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { ICON_PATHS } from "./paths.mjs";
5
+ import { createHash } from "node:crypto";
6
+ //#region src/ui/icons/sprite.ts
7
+ /** Assembled `<svg>` sprite of `<symbol id="i-<name>">` entries. */
8
+ function buildSprite(paths = ICON_PATHS) {
9
+ return `<svg xmlns="http://www.w3.org/2000/svg">${Object.entries(paths).toSorted(([a], [b]) => a.localeCompare(b)).map(([name, body]) => `<symbol id="i-${name}" viewBox="0 0 24 24">${body}</symbol>`).join("")}</svg>`;
10
+ }
11
+ /** Short content digest, used in asset URLs so a changed icon set busts caches. */
12
+ function contentHash(content) {
13
+ return createHash("sha256").update(content).digest("hex").slice(0, 10);
14
+ }
15
+ /** The icon sprite and its content-addressed URL (computed once at load). */
16
+ const iconSprite = buildSprite();
17
+ const iconSpriteHash = contentHash(iconSprite);
18
+ const iconSpriteHref = `/assets/icons-${iconSpriteHash}.svg`;
19
+ /** URL of one icon inside the cached sprite (`<use href>` target). */
20
+ function iconHref(name) {
21
+ return `${iconSpriteHref}#i-${name}`;
22
+ }
23
+ //#endregion
24
+ export { buildSprite, contentHash, iconHref, iconSprite, iconSpriteHash, iconSpriteHref };
25
+
26
+ //# sourceMappingURL=sprite.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sprite.mjs","names":[],"sources":["../../../src/ui/icons/sprite.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { ICON_PATHS, type IconName } from \"./paths.ts\";\n\n/** Assembled `<svg>` sprite of `<symbol id=\"i-<name>\">` entries. */\nexport function buildSprite(paths: Record<string, string> = ICON_PATHS): string {\n const symbols = Object.entries(paths)\n .toSorted(([a], [b]) => a.localeCompare(b))\n .map(([name, body]) => `<symbol id=\"i-${name}\" viewBox=\"0 0 24 24\">${body}</symbol>`)\n .join(\"\");\n return `<svg xmlns=\"http://www.w3.org/2000/svg\">${symbols}</svg>`;\n}\n\n/** Short content digest, used in asset URLs so a changed icon set busts caches. */\nexport function contentHash(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 10);\n}\n\n/** The icon sprite and its content-addressed URL (computed once at load). */\nexport const iconSprite = buildSprite();\nexport const iconSpriteHash = contentHash(iconSprite);\nexport const iconSpriteHref = `/assets/icons-${iconSpriteHash}.svg`;\n\n/** URL of one icon inside the cached sprite (`<use href>` target). */\nexport function iconHref(name: IconName): string {\n return `${iconSpriteHref}#i-${name}`;\n}\n"],"mappings":";;;;;;;AAIA,SAAgB,YAAY,QAAgC,YAAoB;CAK9E,OAAO,2CAJS,OAAO,QAAQ,KAAK,CAAC,CAClC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAC1C,KAAK,CAAC,MAAM,UAAU,iBAAiB,KAAK,wBAAwB,KAAK,UAAU,CAAC,CACpF,KAAK,EACgD,EAAE;AAC5D;;AAGA,SAAgB,YAAY,SAAyB;CACnD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AACvE;;AAGA,MAAa,aAAa,YAAY;AACtC,MAAa,iBAAiB,YAAY,UAAU;AACpD,MAAa,iBAAiB,iBAAiB,eAAe;;AAG9D,SAAgB,SAAS,MAAwB;CAC/C,OAAO,GAAG,eAAe,KAAK;AAChC"}
@@ -0,0 +1,33 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { css } from "./css.mjs";
5
+ import { Fragment, jsx } from "hono/jsx/jsx-runtime";
6
+ //#region src/ui/kbd.tsx
7
+ const kbdKey = css`
8
+ /* kbd */
9
+ display: inline-flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ min-width: 1.4rem;
13
+ padding: 0.05rem 0.35rem;
14
+ border: 1px solid var(--border);
15
+ border-bottom-width: 2px;
16
+ border-radius: var(--radius-sm);
17
+ background: var(--surface-muted);
18
+ color: var(--text-secondary);
19
+ font-family: var(--font-mono);
20
+ font-size: var(--text-xs);
21
+ line-height: 1.3;
22
+ `;
23
+ /** Keyboard key chip: one key, or a sequence given as an array. */
24
+ const Kbd = ({ keys }) => {
25
+ return /* @__PURE__ */ jsx(Fragment, { children: (Array.isArray(keys) ? keys : [keys]).map((key, index) => /* @__PURE__ */ jsx("kbd", {
26
+ class: kbdKey,
27
+ children: key
28
+ }, String(index))) });
29
+ };
30
+ //#endregion
31
+ export { Kbd };
32
+
33
+ //# sourceMappingURL=kbd.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kbd.mjs","names":[],"sources":["../../src/ui/kbd.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst kbdKey = css`\n /* kbd */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.4rem;\n padding: 0.05rem 0.35rem;\n border: 1px solid var(--border);\n border-bottom-width: 2px;\n border-radius: var(--radius-sm);\n background: var(--surface-muted);\n color: var(--text-secondary);\n font-family: var(--font-mono);\n font-size: var(--text-xs);\n line-height: 1.3;\n`;\n\n/** Keyboard key chip: one key, or a sequence given as an array. */\nexport const Kbd: FC<{ keys: string | string[] }> = ({ keys }) => {\n const list = Array.isArray(keys) ? keys : [keys];\n return (\n <>\n {list.map((key, index) => (\n <kbd key={String(index)} class={kbdKey}>\n {key}\n </kbd>\n ))}\n </>\n );\n};\n"],"mappings":";;;;;;AAKA,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;AAkBlB,MAAa,OAAwC,EAAE,WAAW;CAEhE,OACE,oBAAA,UAAA,EAAA,WAFW,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI,EAAA,CAGrC,KAAK,KAAK,UACd,oBAAC,OAAD;EAAyB,OAAO;EAC7B,UAAA;CACE,GAFK,OAAO,KAAK,CAEjB,CACN,EACD,CAAA;AAEN"}