@xaroth.nl/design 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 (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/all.css +2536 -0
  4. package/dist/astro.d.ts +41 -0
  5. package/dist/astro.js +41 -0
  6. package/dist/bem.d.ts +12 -0
  7. package/dist/bem.js +19 -0
  8. package/dist/components/alert/alert.astro +46 -0
  9. package/dist/components/alert/alert.css +68 -0
  10. package/dist/components/alert/alert.d.ts +11 -0
  11. package/dist/components/alert/alert.js +6 -0
  12. package/dist/components/alert/index.d.ts +14 -0
  13. package/dist/components/alert/index.js +27 -0
  14. package/dist/components/avatar/avatar.astro +23 -0
  15. package/dist/components/avatar/avatar.css +35 -0
  16. package/dist/components/avatar/avatar.d.ts +5 -0
  17. package/dist/components/avatar/avatar.js +7 -0
  18. package/dist/components/avatar/index.d.ts +32 -0
  19. package/dist/components/avatar/index.js +24 -0
  20. package/dist/components/badge/badge.astro +11 -0
  21. package/dist/components/badge/badge.css +46 -0
  22. package/dist/components/badge/badge.d.ts +5 -0
  23. package/dist/components/badge/badge.js +6 -0
  24. package/dist/components/badge/index.d.ts +6 -0
  25. package/dist/components/badge/index.js +3 -0
  26. package/dist/components/breadcrumbs/breadcrumbs.astro +27 -0
  27. package/dist/components/breadcrumbs/breadcrumbs.css +33 -0
  28. package/dist/components/breadcrumbs/breadcrumbs.d.ts +9 -0
  29. package/dist/components/breadcrumbs/breadcrumbs.js +11 -0
  30. package/dist/components/breadcrumbs/index.d.ts +37 -0
  31. package/dist/components/breadcrumbs/index.js +16 -0
  32. package/dist/components/button/button.astro +36 -0
  33. package/dist/components/button/button.css +117 -0
  34. package/dist/components/button/button.d.ts +18 -0
  35. package/dist/components/button/button.js +23 -0
  36. package/dist/components/button/index.d.ts +59 -0
  37. package/dist/components/button/index.js +26 -0
  38. package/dist/components/card/card.astro +98 -0
  39. package/dist/components/card/card.css +91 -0
  40. package/dist/components/card/card.d.ts +19 -0
  41. package/dist/components/card/card.js +7 -0
  42. package/dist/components/card/index.d.ts +11 -0
  43. package/dist/components/card/index.js +7 -0
  44. package/dist/components/description-list/description-list.astro +39 -0
  45. package/dist/components/description-list/description-list.css +72 -0
  46. package/dist/components/description-list/description-list.d.ts +7 -0
  47. package/dist/components/description-list/description-list.js +6 -0
  48. package/dist/components/description-list/index.d.ts +12 -0
  49. package/dist/components/description-list/index.js +5 -0
  50. package/dist/components/divider/divider.astro +27 -0
  51. package/dist/components/divider/divider.css +40 -0
  52. package/dist/components/divider/divider.d.ts +5 -0
  53. package/dist/components/divider/divider.js +11 -0
  54. package/dist/components/divider/index.d.ts +22 -0
  55. package/dist/components/divider/index.js +11 -0
  56. package/dist/components/empty-state/empty-state.astro +47 -0
  57. package/dist/components/empty-state/empty-state.css +38 -0
  58. package/dist/components/empty-state/empty-state.d.ts +12 -0
  59. package/dist/components/empty-state/empty-state.js +7 -0
  60. package/dist/components/empty-state/index.d.ts +5 -0
  61. package/dist/components/empty-state/index.js +3 -0
  62. package/dist/components/field/checkbox-group.astro +58 -0
  63. package/dist/components/field/checkbox.astro +17 -0
  64. package/dist/components/field/field.astro +51 -0
  65. package/dist/components/field/field.css +336 -0
  66. package/dist/components/field/field.d.ts +47 -0
  67. package/dist/components/field/field.js +51 -0
  68. package/dist/components/field/index.d.ts +53 -0
  69. package/dist/components/field/index.js +54 -0
  70. package/dist/components/field/input.astro +27 -0
  71. package/dist/components/field/radio-group.astro +58 -0
  72. package/dist/components/field/radio.astro +17 -0
  73. package/dist/components/field/select.astro +13 -0
  74. package/dist/components/field/switch.astro +17 -0
  75. package/dist/components/field/textarea.astro +24 -0
  76. package/dist/components/filters/filter-layout.astro +72 -0
  77. package/dist/components/filters/filter-panel.astro +13 -0
  78. package/dist/components/filters/filters.css +230 -0
  79. package/dist/components/filters/filters.d.ts +20 -0
  80. package/dist/components/filters/filters.js +24 -0
  81. package/dist/components/filters/index.d.ts +27 -0
  82. package/dist/components/filters/index.js +25 -0
  83. package/dist/components/footer/footer.astro +70 -0
  84. package/dist/components/footer/footer.css +128 -0
  85. package/dist/components/footer/footer.d.ts +16 -0
  86. package/dist/components/footer/footer.js +7 -0
  87. package/dist/components/footer/index.d.ts +35 -0
  88. package/dist/components/footer/index.js +18 -0
  89. package/dist/components/header/header.astro +89 -0
  90. package/dist/components/header/header.css +163 -0
  91. package/dist/components/header/header.d.ts +18 -0
  92. package/dist/components/header/header.js +9 -0
  93. package/dist/components/header/index.d.ts +39 -0
  94. package/dist/components/header/index.js +25 -0
  95. package/dist/components/icon/icon.astro +21 -0
  96. package/dist/components/icon/icon.css +15 -0
  97. package/dist/components/icon/icon.d.ts +5 -0
  98. package/dist/components/icon/icon.js +6 -0
  99. package/dist/components/icon/index.d.ts +57 -0
  100. package/dist/components/icon/index.js +72 -0
  101. package/dist/components/layout/container.astro +15 -0
  102. package/dist/components/layout/grid.astro +14 -0
  103. package/dist/components/layout/index.d.ts +33 -0
  104. package/dist/components/layout/index.js +13 -0
  105. package/dist/components/layout/layout.css +262 -0
  106. package/dist/components/layout/layout.d.ts +24 -0
  107. package/dist/components/layout/layout.js +17 -0
  108. package/dist/components/layout/section-head.astro +42 -0
  109. package/dist/components/layout/section.astro +15 -0
  110. package/dist/components/page-head/index.d.ts +18 -0
  111. package/dist/components/page-head/index.js +14 -0
  112. package/dist/components/page-head/page-head.astro +60 -0
  113. package/dist/components/page-head/page-head.css +79 -0
  114. package/dist/components/page-head/page-head.d.ts +18 -0
  115. package/dist/components/page-head/page-head.js +8 -0
  116. package/dist/components/pagination/index.d.ts +73 -0
  117. package/dist/components/pagination/index.js +87 -0
  118. package/dist/components/pagination/pagination.astro +81 -0
  119. package/dist/components/pagination/pagination.css +70 -0
  120. package/dist/components/pagination/pagination.d.ts +8 -0
  121. package/dist/components/pagination/pagination.js +19 -0
  122. package/dist/components/panel/index.d.ts +11 -0
  123. package/dist/components/panel/index.js +3 -0
  124. package/dist/components/panel/panel.astro +15 -0
  125. package/dist/components/panel/panel.css +48 -0
  126. package/dist/components/panel/panel.d.ts +7 -0
  127. package/dist/components/panel/panel.js +6 -0
  128. package/dist/components/progress/index.d.ts +37 -0
  129. package/dist/components/progress/index.js +44 -0
  130. package/dist/components/progress/progress.astro +18 -0
  131. package/dist/components/progress/progress.css +115 -0
  132. package/dist/components/progress/progress.d.ts +5 -0
  133. package/dist/components/progress/progress.js +7 -0
  134. package/dist/components/prose/code-block.astro +26 -0
  135. package/dist/components/prose/index.d.ts +14 -0
  136. package/dist/components/prose/index.js +67 -0
  137. package/dist/components/prose/prose.astro +14 -0
  138. package/dist/components/prose/prose.css +156 -0
  139. package/dist/components/prose/prose.d.ts +12 -0
  140. package/dist/components/prose/prose.js +11 -0
  141. package/dist/components/stat/index.d.ts +15 -0
  142. package/dist/components/stat/index.js +5 -0
  143. package/dist/components/stat/stat-group.astro +11 -0
  144. package/dist/components/stat/stat.astro +43 -0
  145. package/dist/components/stat/stat.css +51 -0
  146. package/dist/components/stat/stat.d.ts +15 -0
  147. package/dist/components/stat/stat.js +10 -0
  148. package/dist/components/table/index.d.ts +38 -0
  149. package/dist/components/table/index.js +18 -0
  150. package/dist/components/table/table.astro +111 -0
  151. package/dist/components/table/table.css +78 -0
  152. package/dist/components/table/table.d.ts +11 -0
  153. package/dist/components/table/table.js +10 -0
  154. package/dist/components/tabs/index.d.ts +21 -0
  155. package/dist/components/tabs/index.js +13 -0
  156. package/dist/components/tabs/tabs.astro +30 -0
  157. package/dist/components/tabs/tabs.css +59 -0
  158. package/dist/components/tabs/tabs.d.ts +9 -0
  159. package/dist/components/tabs/tabs.js +6 -0
  160. package/dist/components/tag/index.d.ts +19 -0
  161. package/dist/components/tag/index.js +7 -0
  162. package/dist/components/tag/tag.astro +17 -0
  163. package/dist/components/tag/tag.css +30 -0
  164. package/dist/components/tag/tag.d.ts +9 -0
  165. package/dist/components/tag/tag.js +12 -0
  166. package/dist/components/timeline/index.d.ts +22 -0
  167. package/dist/components/timeline/index.js +5 -0
  168. package/dist/components/timeline/timeline.astro +40 -0
  169. package/dist/components/timeline/timeline.css +92 -0
  170. package/dist/components/timeline/timeline.d.ts +9 -0
  171. package/dist/components/timeline/timeline.js +7 -0
  172. package/dist/components/tooltip/index.d.ts +15 -0
  173. package/dist/components/tooltip/index.js +41 -0
  174. package/dist/components/tooltip/tooltip.astro +25 -0
  175. package/dist/components/tooltip/tooltip.css +84 -0
  176. package/dist/components/tooltip/tooltip.d.ts +12 -0
  177. package/dist/components/tooltip/tooltip.js +34 -0
  178. package/dist/config.d.ts +9 -0
  179. package/dist/config.js +6 -0
  180. package/dist/env.d.ts +2 -0
  181. package/dist/html.d.ts +37 -0
  182. package/dist/html.js +126 -0
  183. package/dist/parts.d.ts +27 -0
  184. package/dist/parts.js +28 -0
  185. package/dist/react.d.ts +27 -0
  186. package/dist/react.js +28 -0
  187. package/dist/scss/_chamfer.scss +37 -0
  188. package/dist/scss/_layers.scss +2 -0
  189. package/dist/scss/_theme.scss +15 -0
  190. package/dist/themes/eve-online.css +1945 -0
  191. package/dist/themes/xaroth.css +1974 -0
  192. package/dist/themes.css +3919 -0
  193. package/package.json +80 -0
@@ -0,0 +1,67 @@
1
+ import { bem } from "../../bem.js";
2
+ const prose = bem('x-prose');
3
+ const codeBlock = bem('x-code');
4
+ export const proseClass = ({ className } = {}) => prose({}, className);
5
+ export const codeBlockClass = ({ className } = {}) => codeBlock({}, className);
6
+ const keywords = new Set(('as async await break case catch class const continue def default del delete do elif else enum export extends ' +
7
+ 'false fi fn for from func function go if impl implements import in instanceof interface is let loop match mod ' +
8
+ 'module mut new nil none not null of or package pass pub raise return self static struct super switch then this ' +
9
+ 'throw trait true try type typeof undefined use var void while with yield True False None and lambda').split(' '));
10
+ // Languages whose line comments start with #. Everything else gets C-style comments.
11
+ const hashComment = new Set(['py', 'python', 'sh', 'bash', 'shell', 'zsh', 'yaml', 'yml', 'toml', 'rb', 'ruby', 'ini']);
12
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
13
+ const pattern = (lang) => {
14
+ const comment = hashComment.has(lang) ? String.raw `#[^\n]*` : String.raw `\/\*[\s\S]*?(?:\*\/|$)|\/\/[^\n]*`;
15
+ return new RegExp([
16
+ `(${comment})`,
17
+ String.raw `("(?:\\[\s\S]|[^"\\\n])*"?|'(?:\\[\s\S]|[^'\\\n])*'?|\`(?:\\[\s\S]|[^\`\\])*\`?)`,
18
+ String.raw `(\b0x[\da-fA-F]+\b|\b\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?\b)`,
19
+ String.raw `([A-Za-z_$][\w$]*)(?=\s*\()`,
20
+ String.raw `([A-Za-z_$][\w$]*)`,
21
+ ].join('|'), 'g');
22
+ };
23
+ // Split source into typed tokens. Tiny on purpose: good enough for articles, not a real lexer.
24
+ export const tokenizeCode = (code, lang = '') => {
25
+ const out = [];
26
+ let last = 0;
27
+ for (const m of code.matchAll(pattern(lang.toLowerCase()))) {
28
+ const index = m.index ?? 0;
29
+ if (index > last) {
30
+ out.push({ text: code.slice(last, index) });
31
+ }
32
+ const word = m[4] ?? m[5];
33
+ const type = m[1]
34
+ ? 'com'
35
+ : m[2]
36
+ ? 'str'
37
+ : m[3]
38
+ ? 'num'
39
+ : word && keywords.has(word)
40
+ ? 'kw'
41
+ : m[4]
42
+ ? 'fn'
43
+ : undefined;
44
+ out.push({ type, text: m[0] });
45
+ last = index + m[0].length;
46
+ }
47
+ if (last < code.length) {
48
+ out.push({ text: code.slice(last) });
49
+ }
50
+ return out;
51
+ };
52
+ // One span per line (for the line number counter) with token spans inside. Tokens that cross a newline,
53
+ // like block comments, are closed and reopened so every line is well formed.
54
+ export const codeHtml = (code, lang) => {
55
+ const lines = [''];
56
+ for (const { type, text } of tokenizeCode(code.replace(/\r\n?/g, '\n').replace(/\n$/, ''), lang)) {
57
+ text.split('\n').forEach((part, i) => {
58
+ if (i > 0) {
59
+ lines.push('');
60
+ }
61
+ if (part) {
62
+ lines[lines.length - 1] += type ? `<span class="x-code__${type}">${esc(part)}</span>` : esc(part);
63
+ }
64
+ });
65
+ }
66
+ return lines.map((l) => `<span class="x-code__line">${l}</span>`).join('\n');
67
+ };
@@ -0,0 +1,14 @@
1
+ ---
2
+ import './prose.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { proseClass } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'div'>, 'class'> & {
7
+ class?: string
8
+ as?: 'div' | 'article' | 'section' | 'main'
9
+ }
10
+
11
+ const { as: Tag = 'div', class: className, slot: _slot, ...rest } = Astro.props
12
+ ---
13
+
14
+ <Tag {...rest} class={proseClass({ className })}><slot /></Tag>
@@ -0,0 +1,156 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-prose {
4
+ --x-prose-block: 2em;
5
+ min-width: 0;
6
+ }
7
+ .x-prose > * + * {
8
+ margin-top: 1em;
9
+ }
10
+ .x-prose > :is(p, ul, ol, h2, h3, h4, blockquote, hr) {
11
+ max-width: var(--x-prose);
12
+ }
13
+ .x-prose > :is(h2, h3, h4) {
14
+ margin-top: 2em;
15
+ scroll-margin-top: var(--x-space-8);
16
+ }
17
+ .x-prose > :is(h2, h3, h4) + * {
18
+ margin-top: 0.5em;
19
+ }
20
+ .x-prose > :first-child {
21
+ margin-top: 0;
22
+ }
23
+ .x-prose > :is(blockquote, figure, table, pre, .x-code, hr) {
24
+ margin-block: var(--x-prose-block);
25
+ }
26
+ .x-prose :where(ul, ol) {
27
+ display: grid;
28
+ gap: var(--x-space-2);
29
+ padding-left: 1.5rem;
30
+ }
31
+ .x-prose :where(li) > :where(ul, ol) {
32
+ margin-top: var(--x-space-2);
33
+ }
34
+ .x-prose :where(blockquote) {
35
+ position: relative;
36
+ }
37
+ .x-prose :where(blockquote) > * + * {
38
+ margin-top: 0.75em;
39
+ }
40
+ .x-prose :where(blockquote) > :is(footer, cite) {
41
+ display: block;
42
+ font-style: normal;
43
+ }
44
+ .x-prose :where(:not(pre)) > :where(code) {
45
+ padding: 0.1em 0.35em;
46
+ font-size: 0.875em;
47
+ overflow-wrap: anywhere;
48
+ }
49
+ .x-prose :where(figure:not(.x-code)) {
50
+ display: grid;
51
+ gap: var(--x-space-3);
52
+ min-width: 0;
53
+ overflow-x: auto;
54
+ }
55
+ .x-prose :where(figure:not(.x-code)) > :is(img, svg, video, picture) {
56
+ width: 100%;
57
+ height: auto;
58
+ }
59
+ .x-prose :where(figcaption) {
60
+ font-size: var(--x-fs-small);
61
+ line-height: 1.5;
62
+ color: var(--x-color-text-muted);
63
+ }
64
+ .x-prose :where(table:not(.x-table)) {
65
+ width: 100%;
66
+ font-size: var(--x-fs-ui);
67
+ line-height: 1.4;
68
+ border-collapse: collapse;
69
+ }
70
+ .x-prose :where(table:not(.x-table)) :where(caption) {
71
+ padding: var(--x-space-3) var(--x-table-pad-x, var(--x-space-4));
72
+ font-size: var(--x-fs-small);
73
+ line-height: 1.5;
74
+ text-align: start;
75
+ caption-side: bottom;
76
+ color: var(--x-color-text-muted);
77
+ }
78
+ .x-prose :where(table:not(.x-table)) :where(thead th) {
79
+ height: var(--x-table-row, var(--x-row));
80
+ padding: 0 var(--x-table-pad-x, var(--x-space-4));
81
+ font-size: var(--x-fs-label);
82
+ line-height: 1.3;
83
+ text-align: start;
84
+ white-space: nowrap;
85
+ vertical-align: middle;
86
+ }
87
+ .x-prose :where(table:not(.x-table)) :where(tbody) :where(td, th) {
88
+ height: var(--x-table-row, var(--x-row));
89
+ padding: var(--x-table-pad-y, var(--x-space-3)) var(--x-table-pad-x, var(--x-space-4));
90
+ text-align: start;
91
+ vertical-align: middle;
92
+ border-bottom: 1px solid var(--x-color-line);
93
+ transition: background-color var(--x-duration) var(--x-ease);
94
+ }
95
+ .x-prose :where(table:not(.x-table)) :where(tbody tr:last-child) > :where(td, th) {
96
+ border-bottom: 0;
97
+ }
98
+ .x-prose :where(table:not(.x-table)) :where(tbody tr:hover) > :where(td, th) {
99
+ background-color: var(--x-table-hover, var(--x-color-accent-wash));
100
+ }
101
+ .x-prose > .x-table-wrap {
102
+ margin-block: var(--x-prose-block);
103
+ }
104
+ .x-prose :where(strong) {
105
+ font-weight: 600;
106
+ color: var(--x-color-text);
107
+ }
108
+ .x-code {
109
+ position: relative;
110
+ min-width: 0;
111
+ margin: 0;
112
+ }
113
+ .x-code__bar {
114
+ display: flex;
115
+ gap: var(--x-space-3);
116
+ align-items: center;
117
+ justify-content: space-between;
118
+ padding: var(--x-space-2) var(--x-space-4);
119
+ font-size: var(--x-fs-label);
120
+ line-height: 1.3;
121
+ }
122
+ .x-code__lang {
123
+ margin-left: auto;
124
+ }
125
+ .x-code__pre {
126
+ padding: var(--x-space-4) var(--x-space-5) var(--x-space-4) 0;
127
+ margin: 0;
128
+ overflow-x: auto;
129
+ font-size: 0.875rem;
130
+ line-height: 1.7;
131
+ tab-size: 2;
132
+ counter-reset: x-line;
133
+ }
134
+ .x-code__pre:focus-visible {
135
+ outline-offset: -2px;
136
+ }
137
+ .x-code code {
138
+ font-size: inherit;
139
+ }
140
+ .x-code__line::before {
141
+ position: sticky;
142
+ left: 0;
143
+ counter-increment: x-line;
144
+ content: counter(x-line);
145
+ display: inline-block;
146
+ box-sizing: content-box;
147
+ width: 3.5ch;
148
+ padding-left: var(--x-code-gutter-start, 1.5ch);
149
+ padding-right: 1.5ch;
150
+ margin-right: 1.5ch;
151
+ text-align: right;
152
+ user-select: none;
153
+ background: var(--x-code-bg, transparent);
154
+ border-right: 1px solid var(--x-color-line-strong);
155
+ }
156
+ }
@@ -0,0 +1,12 @@
1
+ import './prose.scss';
2
+ import type { HTMLAttributes } from 'react';
3
+ export type ProseProps = HTMLAttributes<HTMLElement> & {
4
+ as?: 'div' | 'article' | 'section' | 'main';
5
+ };
6
+ export declare function Prose({ as: Tag, className, ...rest }: ProseProps): import("react").JSX.Element;
7
+ export type CodeBlockProps = Omit<HTMLAttributes<HTMLElement>, 'title' | 'children'> & {
8
+ code: string;
9
+ lang?: string;
10
+ title?: string;
11
+ };
12
+ export declare function CodeBlock({ code, lang, title, className, ...rest }: CodeBlockProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './prose.css';
3
+ import { codeBlockClass, codeHtml, proseClass } from "./index.js";
4
+ export function Prose({ as: Tag = 'div', className, ...rest }) {
5
+ return (_jsx(Tag, { ...rest, className: proseClass({ className }) }));
6
+ }
7
+ export function CodeBlock({ code, lang, title, className, ...rest }) {
8
+ return (_jsxs("figure", { ...rest, className: codeBlockClass({ className }), children: [(title || lang) && (_jsxs("figcaption", { className: "x-code__bar", children: [title && _jsx("span", { className: "x-code__title", children: title }), lang && _jsx("span", { className: "x-code__lang", children: lang })] })), _jsx("pre", { className: "x-code__pre",
9
+ // oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- keyboard users scroll long lines
10
+ tabIndex: 0, children: _jsx("code", { dangerouslySetInnerHTML: { __html: codeHtml(code, lang) } }) })] }));
11
+ }
@@ -0,0 +1,15 @@
1
+ export type StatGroupVariant = 'plain' | 'framed';
2
+ export type StatGroupColumns = 2 | 3 | 4;
3
+ export type StatTone = 'default' | 'muted' | 'success' | 'warning' | 'danger';
4
+ export type StatGroupOptions = {
5
+ variant?: StatGroupVariant;
6
+ /** Columns from 640px up; below that the group is two across. */
7
+ columns?: StatGroupColumns;
8
+ className?: string;
9
+ };
10
+ export type StatOptions = {
11
+ tone?: StatTone;
12
+ className?: string;
13
+ };
14
+ export declare const statGroupClass: ({ variant, columns, className }?: StatGroupOptions) => string;
15
+ export declare const statClass: ({ tone, className }?: StatOptions) => string;
@@ -0,0 +1,5 @@
1
+ import { bem } from "../../bem.js";
2
+ const statGroup = bem('x-stat-group', { defaults: { cols: 4 }, prefixed: ['cols'] });
3
+ const stat = bem('x-stat', { defaults: { tone: 'default' } });
4
+ export const statGroupClass = ({ variant = 'plain', columns = 4, className } = {}) => statGroup({ variant, cols: columns }, className);
5
+ export const statClass = ({ tone = 'default', className } = {}) => stat({ tone }, className);
@@ -0,0 +1,11 @@
1
+ ---
2
+ import './stat.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { statGroupClass, type StatGroupOptions } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'dl'>, 'class'> & Omit<StatGroupOptions, 'className'> & { class?: string }
7
+
8
+ const { variant, columns, class: className, slot: _slot, ...rest } = Astro.props
9
+ ---
10
+
11
+ <dl {...rest} class={statGroupClass({ variant, columns, className })}><slot /></dl>
@@ -0,0 +1,43 @@
1
+ ---
2
+ import './stat.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { statClass, type StatOptions } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'div'>, 'class'> &
7
+ Omit<StatOptions, 'className'> & {
8
+ class?: string
9
+ /** Plain-text label; the `label` slot takes markup. */
10
+ label?: string
11
+ /** The value; the default slot takes markup and precedence. */
12
+ value?: string | number
13
+ /** Small text after the value: a unit or "/ 44". The `unit` slot takes markup. */
14
+ unit?: string
15
+ /** Note under the value. The `hint` slot takes markup. */
16
+ hint?: string
17
+ }
18
+
19
+ const { tone, label, value, unit, hint, class: className, slot: _slot, ...rest } = Astro.props
20
+ const hasUnit = unit !== undefined || Astro.slots.has('unit')
21
+ const hasHint = hint !== undefined || Astro.slots.has('hint')
22
+ ---
23
+
24
+ <div {...rest} class={statClass({ tone, className })}>
25
+ <dt class="x-stat__label"><slot name="label">{label}</slot></dt>
26
+ <dd class="x-stat__value">
27
+ <slot>{value}</slot>
28
+ {
29
+ hasUnit && (
30
+ <span class="x-stat__unit">
31
+ <slot name="unit">{unit}</slot>
32
+ </span>
33
+ )
34
+ }
35
+ </dd>
36
+ {
37
+ hasHint && (
38
+ <dd class="x-stat__hint">
39
+ <slot name="hint">{hint}</slot>
40
+ </dd>
41
+ )
42
+ }
43
+ </div>
@@ -0,0 +1,51 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-stat-group {
4
+ --x-stat-cols: 4;
5
+ display: grid;
6
+ grid-template-columns: repeat(2, minmax(0, 1fr));
7
+ gap: var(--x-stat-gap, var(--x-gap));
8
+ margin: 0;
9
+ }
10
+ @media (min-width: 640px) {
11
+ .x-stat-group {
12
+ grid-template-columns: repeat(var(--x-stat-cols), minmax(0, 1fr));
13
+ }
14
+ }
15
+ .x-stat-group--cols-2 {
16
+ --x-stat-cols: 2;
17
+ }
18
+ .x-stat-group--cols-3 {
19
+ --x-stat-cols: 3;
20
+ }
21
+ .x-stat {
22
+ --x-stat-value: var(--x-color-text);
23
+ position: relative;
24
+ display: grid;
25
+ gap: var(--x-stat-inner-gap, var(--x-space-2));
26
+ align-content: start;
27
+ box-sizing: border-box;
28
+ min-width: 0;
29
+ padding: var(--x-stat-pad, 0);
30
+ }
31
+ .x-stat__label, .x-stat__value, .x-stat__hint {
32
+ min-width: 0;
33
+ margin: 0;
34
+ }
35
+ .x-stat__value {
36
+ line-height: 1.1;
37
+ color: var(--x-stat-value);
38
+ overflow-wrap: anywhere;
39
+ font-variant-numeric: lining-nums tabular-nums;
40
+ }
41
+ .x-stat__unit {
42
+ margin-inline-start: 0.25em;
43
+ font-size: max(0.5em, var(--x-fs-label));
44
+ color: var(--x-color-text-faint);
45
+ }
46
+ .x-stat__hint {
47
+ font-size: var(--x-fs-small);
48
+ line-height: 1.5;
49
+ color: var(--x-color-text-faint);
50
+ }
51
+ }
@@ -0,0 +1,15 @@
1
+ import './stat.scss';
2
+ import type { HTMLAttributes, ReactNode } from 'react';
3
+ import { type StatGroupOptions, type StatOptions } from './index.ts';
4
+ export type StatGroupProps = Omit<StatGroupOptions, 'className'> & HTMLAttributes<HTMLDListElement>;
5
+ export declare function StatGroup({ variant, columns, className, ...rest }: StatGroupProps): import("react").JSX.Element;
6
+ export type StatProps = Omit<StatOptions, 'className'> & HTMLAttributes<HTMLDivElement> & {
7
+ label?: ReactNode;
8
+ /** The value; children take precedence. */
9
+ value?: ReactNode;
10
+ /** Small text after the value: a unit or "/ 44". */
11
+ unit?: ReactNode;
12
+ /** Note under the value. */
13
+ hint?: ReactNode;
14
+ };
15
+ export declare function Stat({ tone, label, value, unit, hint, className, children, ...rest }: StatProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './stat.css';
3
+ import { statClass, statGroupClass } from "./index.js";
4
+ // A dl: each stat is a name and value pair, which screen readers announce as the label followed by its value.
5
+ export function StatGroup({ variant, columns, className, ...rest }) {
6
+ return (_jsx("dl", { ...rest, className: statGroupClass({ variant, columns, className }) }));
7
+ }
8
+ export function Stat({ tone, label, value, unit, hint, className, children, ...rest }) {
9
+ return (_jsxs("div", { ...rest, className: statClass({ tone, className }), children: [_jsx("dt", { className: "x-stat__label", children: label }), _jsxs("dd", { className: "x-stat__value", children: [children ?? value, unit != null && _jsx("span", { className: "x-stat__unit", children: unit })] }), hint != null && _jsx("dd", { className: "x-stat__hint", children: hint })] }));
10
+ }
@@ -0,0 +1,38 @@
1
+ export type TableAlign = 'start' | 'end' | 'center';
2
+ export type TableDensity = 'comfortable' | 'compact';
3
+ export type TableColumn = {
4
+ key: string;
5
+ label: string;
6
+ align?: TableAlign;
7
+ numeric?: boolean;
8
+ width?: string;
9
+ };
10
+ export type TableRow<T = string | number> = Record<string, T | null | undefined>;
11
+ export type TableCell = {
12
+ row: TableRow;
13
+ rowIndex: number;
14
+ column: TableColumn;
15
+ value: TableRow[string];
16
+ };
17
+ export type TableLabel = {
18
+ label: string;
19
+ 'aria-labelledby'?: string;
20
+ } | {
21
+ label?: undefined;
22
+ 'aria-labelledby': string;
23
+ };
24
+ export type TableOptions = {
25
+ density?: TableDensity;
26
+ striped?: boolean;
27
+ hover?: boolean;
28
+ className?: string;
29
+ };
30
+ export type TableWrapOptions = {
31
+ stickyHeader?: boolean;
32
+ className?: string;
33
+ };
34
+ export declare const tableWrapClass: ({ stickyHeader, className }?: TableWrapOptions) => string;
35
+ export declare const tableClass: ({ density, striped, hover }?: TableOptions) => string;
36
+ export declare const tableCellAlign: ({ align, numeric }: Pick<TableColumn, "align" | "numeric">) => TableAlign;
37
+ export declare const tableCellClass: (column: TableColumn, part: "head" | "body") => string | undefined;
38
+ export declare const tableCellSlot: (rowIndex: number, key: string) => string;
@@ -0,0 +1,18 @@
1
+ import { bem } from "../../bem.js";
2
+ const tableWrap = bem('x-table-wrap');
3
+ const table = bem('x-table', { defaults: { density: 'comfortable', align: 'start' } });
4
+ // className goes on the wrapper, the outermost element.
5
+ export const tableWrapClass = ({ stickyHeader, className } = {}) => tableWrap({ sticky: stickyHeader }, className);
6
+ export const tableClass = ({ density = 'comfortable', striped, hover } = {}) => table({ density, striped, hover });
7
+ export const tableCellAlign = ({ align, numeric }) => align ?? (numeric ? 'end' : 'start');
8
+ // Header cells only take the alignment; the numeric look is for body cells.
9
+ export const tableCellClass = (column, part) => {
10
+ const align = tableCellAlign(column);
11
+ const numeric = part === 'body' && column.numeric;
12
+ if (align === 'start' && !numeric) {
13
+ return undefined;
14
+ }
15
+ return table.el('cell', { align, num: numeric });
16
+ };
17
+ // Astro: markup for one cell goes in a named slot, overriding the row value.
18
+ export const tableCellSlot = (rowIndex, key) => `cell-${rowIndex}-${key}`;
@@ -0,0 +1,111 @@
1
+ ---
2
+ import './table.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import {
5
+ tableCellClass,
6
+ tableCellSlot,
7
+ tableClass,
8
+ tableWrapClass,
9
+ type TableCell,
10
+ type TableColumn,
11
+ type TableLabel,
12
+ type TableOptions,
13
+ type TableRow,
14
+ } from './index.js'
15
+
16
+ type Props = Omit<HTMLAttributes<'div'>, 'class' | 'aria-label' | 'aria-labelledby'> &
17
+ Omit<TableOptions, 'className'> &
18
+ TableLabel & {
19
+ class?: string
20
+ columns: TableColumn[]
21
+ /** Plain values. For markup, use a `cell-<rowIndex>-<key>` slot or a default slot function (see below). */
22
+ rows: TableRow[]
23
+ caption?: string
24
+ stickyHeader?: boolean
25
+ }
26
+
27
+ const {
28
+ columns,
29
+ rows,
30
+ caption,
31
+ density,
32
+ striped,
33
+ hover,
34
+ stickyHeader,
35
+ label,
36
+ class: className,
37
+ slot: _slot,
38
+ ...rest
39
+ } = Astro.props
40
+ const attrs = { ...rest, role: 'region' as const, tabindex: '0', 'aria-label': label }
41
+
42
+ // Cell markup, first match wins: the named slot `cell-<rowIndex>-<key>` (static markup), then a default slot
43
+ // function `(cell: TableCell) => markup`, which is called per cell; rendering nothing keeps the plain value.
44
+ // A default slot of plain markup is ignored.
45
+ const renderCell = async (cell: TableCell): Promise<string | undefined> => {
46
+ const args = [cell]
47
+ const probe = { called: false }
48
+ // Astro cannot tell a slot function from plain markup; only a function spreads the arguments it is given.
49
+ Object.defineProperty(args, Symbol.iterator, {
50
+ value: function* () {
51
+ probe.called = true
52
+ yield cell
53
+ },
54
+ })
55
+ const html = await Astro.slots.render('default', args)
56
+ return probe.called ? html : undefined
57
+ }
58
+ let hasRenderer = Astro.slots.has('default')
59
+ const slotted = new Map<string, string>()
60
+ for (const [rowIndex, row] of rows.entries()) {
61
+ for (const column of columns) {
62
+ const name = tableCellSlot(rowIndex, column.key)
63
+ let html = Astro.slots.has(name) ? await Astro.slots.render(name) : undefined
64
+ if (html === undefined && hasRenderer) {
65
+ const rendered = await renderCell({ row, rowIndex, column, value: row[column.key] })
66
+ hasRenderer = rendered !== undefined
67
+ html = rendered?.trim() || undefined
68
+ }
69
+ if (html !== undefined) {
70
+ slotted.set(name, html)
71
+ }
72
+ }
73
+ }
74
+ ---
75
+
76
+ <div {...attrs} class={tableWrapClass({ stickyHeader, className })}>
77
+ <table class={tableClass({ density, striped, hover })}>
78
+ {caption && <caption class="x-table__caption">{caption}</caption>}
79
+ <thead>
80
+ <tr>
81
+ {
82
+ columns.map((column) => (
83
+ <th
84
+ class={tableCellClass(column, 'head')}
85
+ scope="col"
86
+ style={column.width ? `width:${column.width}` : undefined}
87
+ >
88
+ {column.label}
89
+ </th>
90
+ ))
91
+ }
92
+ </tr>
93
+ </thead>
94
+ <tbody>
95
+ {
96
+ rows.map((row, i) => (
97
+ <tr>
98
+ {columns.map((column) => {
99
+ const html = slotted.get(tableCellSlot(i, column.key))
100
+ return (
101
+ <td class={tableCellClass(column, 'body')}>
102
+ {html !== undefined ? <Fragment set:html={html} /> : row[column.key]}
103
+ </td>
104
+ )
105
+ })}
106
+ </tr>
107
+ ))
108
+ }
109
+ </tbody>
110
+ </table>
111
+ </div>
@@ -0,0 +1,78 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-table-wrap {
4
+ --x-table-edge: color-mix(in srgb, var(--x-color-accent) 30%, transparent);
5
+ --x-table-cover: var(--x-color-bg);
6
+ --x-table-hint:
7
+ linear-gradient(to right, var(--x-table-cover) 60%, transparent) left / 28px 100% no-repeat local,
8
+ linear-gradient(to left, var(--x-table-cover) 60%, transparent) right / 28px 100% no-repeat local,
9
+ radial-gradient(farthest-side at 0 50%, var(--x-table-edge), transparent) left / 14px 100% no-repeat scroll,
10
+ radial-gradient(farthest-side at 100% 50%, var(--x-table-edge), transparent) right / 14px 100% no-repeat scroll;
11
+ max-width: 100%;
12
+ overflow: auto;
13
+ background: var(--x-table-hint), var(--x-table-cover);
14
+ }
15
+ .x-table-wrap--sticky {
16
+ max-height: var(--x-table-max-height, 70vh);
17
+ }
18
+ .x-table-wrap--sticky .x-table :where(thead th) {
19
+ position: sticky;
20
+ top: 0;
21
+ z-index: 1;
22
+ }
23
+ .x-table {
24
+ width: 100%;
25
+ font-size: var(--x-fs-ui);
26
+ line-height: 1.4;
27
+ border-collapse: collapse;
28
+ }
29
+ .x-table :where(caption) {
30
+ padding: var(--x-space-3) var(--x-table-pad-x, var(--x-space-4));
31
+ font-size: var(--x-fs-small);
32
+ line-height: 1.5;
33
+ text-align: start;
34
+ caption-side: bottom;
35
+ color: var(--x-color-text-muted);
36
+ }
37
+ .x-table :where(thead th) {
38
+ height: var(--x-table-row, var(--x-row));
39
+ padding: 0 var(--x-table-pad-x, var(--x-space-4));
40
+ font-size: var(--x-fs-label);
41
+ line-height: 1.3;
42
+ text-align: start;
43
+ white-space: nowrap;
44
+ vertical-align: middle;
45
+ }
46
+ .x-table :where(tbody) :where(td, th) {
47
+ height: var(--x-table-row, var(--x-row));
48
+ padding: var(--x-table-pad-y, var(--x-space-3)) var(--x-table-pad-x, var(--x-space-4));
49
+ text-align: start;
50
+ vertical-align: middle;
51
+ border-bottom: 1px solid var(--x-color-line);
52
+ transition: background-color var(--x-duration) var(--x-ease);
53
+ }
54
+ .x-table :where(tbody tr:last-child) > :where(td, th) {
55
+ border-bottom: 0;
56
+ }
57
+ .x-table--compact {
58
+ --x-table-row: var(--x-control-sm);
59
+ --x-table-pad-y: var(--x-space-1);
60
+ --x-table-pad-x: var(--x-space-3);
61
+ }
62
+ .x-table--striped :where(tbody tr:nth-child(even)) > :where(td, th) {
63
+ background-color: var(--x-table-stripe, color-mix(in srgb, var(--x-color-text) 4%, transparent));
64
+ }
65
+ .x-table--hover :where(tbody tr:hover) > :where(td, th) {
66
+ background-color: var(--x-table-hover, var(--x-color-accent-wash));
67
+ }
68
+ .x-table__cell--end {
69
+ text-align: end;
70
+ }
71
+ .x-table__cell--center {
72
+ text-align: center;
73
+ }
74
+ .x-table__cell--num {
75
+ font-variant-numeric: tabular-nums;
76
+ white-space: nowrap;
77
+ }
78
+ }