@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,16 @@
1
+ import { bem } from "../../bem.js";
2
+ const breadcrumbs = bem('x-breadcrumbs');
3
+ export const breadcrumbsClass = ({ className } = {}) => breadcrumbs({}, className);
4
+ export const breadcrumbsClasses = {
5
+ list: breadcrumbs.el('list'),
6
+ item: breadcrumbs.el('item'),
7
+ link: breadcrumbs.el('link'),
8
+ text: breadcrumbs.el('text'),
9
+ current: breadcrumbs.el('current'),
10
+ };
11
+ // The last item is the page itself, so it is never a link even when it has an href.
12
+ export const breadcrumbState = (item, index, count) => index === count - 1
13
+ ? { tag: 'span', attrs: { class: breadcrumbsClasses.current, 'aria-current': 'page' } }
14
+ : item.href !== undefined
15
+ ? { tag: 'a', attrs: { class: breadcrumbsClasses.link, href: item.href } }
16
+ : { tag: 'span', attrs: { class: breadcrumbsClasses.text } };
@@ -0,0 +1,36 @@
1
+ ---
2
+ import './button.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { buttonClass, buttonState, type ButtonOptions } from './index.js'
5
+
6
+ type Common = Omit<ButtonOptions, 'className'> & { class?: string }
7
+
8
+ type Props =
9
+ | (Common & Omit<HTMLAttributes<'button'>, 'class'> & { href?: undefined })
10
+ | (Common & Omit<HTMLAttributes<'a'>, 'class' | 'type'> & { href: string; disabled?: boolean; type?: undefined })
11
+
12
+ const { variant, tone, size, fullWidth, loading, disabled, class: className, href, type, slot: _slot, ...rest } =
13
+ Astro.props
14
+ const classes = buttonClass({ variant, tone, size, fullWidth, loading, className })
15
+ const { tag: Tag, attrs: state } = buttonState({ href, disabled: Boolean(disabled), loading, type: type ?? undefined })
16
+ const attrs = { ...rest, ...state }
17
+ ---
18
+
19
+ <Tag {...attrs} class={classes}>
20
+ {
21
+ Astro.slots.has('start') && (
22
+ <span class="x-button__start">
23
+ <slot name="start" />
24
+ </span>
25
+ )
26
+ }
27
+ <span class="x-button__label"><slot /></span>
28
+ {
29
+ Astro.slots.has('end') && (
30
+ <span class="x-button__end">
31
+ <slot name="end" />
32
+ </span>
33
+ )
34
+ }
35
+ {loading && <span class="x-button__spinner" aria-hidden="true" />}
36
+ </Tag>
@@ -0,0 +1,117 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-button {
4
+ --x-button-tone: var(--x-color-accent);
5
+ --x-button-on-tone: var(--x-color-on-accent);
6
+ --x-button-quiet-fg: var(--x-color-text);
7
+ position: relative;
8
+ display: inline-flex;
9
+ gap: var(--x-space-2);
10
+ align-items: center;
11
+ justify-content: center;
12
+ min-height: var(--x-control-md);
13
+ padding: 0 20px;
14
+ font-family: var(--x-button-font, var(--x-font-ui));
15
+ font-size: var(--x-button-fs, 0.875rem);
16
+ font-weight: 600;
17
+ line-height: 1;
18
+ letter-spacing: var(--x-button-tracking, 0.14em);
19
+ text-transform: var(--x-button-case, uppercase);
20
+ text-decoration: none;
21
+ white-space: nowrap;
22
+ cursor: pointer;
23
+ color: var(--x-button-fg);
24
+ background: var(--x-button-bg);
25
+ border: 1px solid var(--x-button-border, transparent);
26
+ transition: background var(--x-duration) var(--x-ease), color var(--x-duration) var(--x-ease), border-color var(--x-duration) var(--x-ease), filter var(--x-duration) var(--x-ease);
27
+ }
28
+ .x-button:disabled, .x-button[aria-disabled=true] {
29
+ cursor: not-allowed;
30
+ }
31
+ .x-button:disabled, .x-button[aria-disabled=true]:not(.x-button--loading) {
32
+ opacity: 0.5;
33
+ }
34
+ .x-button--danger {
35
+ --x-button-tone: var(--x-color-danger);
36
+ --x-button-on-tone: var(--x-color-on-danger);
37
+ --x-button-quiet-fg: var(--x-color-danger);
38
+ }
39
+ .x-button--warning {
40
+ --x-button-tone: var(--x-color-warning);
41
+ --x-button-on-tone: var(--x-color-on-warning);
42
+ --x-button-quiet-fg: var(--x-color-warning);
43
+ }
44
+ .x-button--success {
45
+ --x-button-tone: var(--x-color-success);
46
+ --x-button-on-tone: var(--x-color-on-success);
47
+ --x-button-quiet-fg: var(--x-color-success);
48
+ }
49
+ .x-button--primary {
50
+ --x-button-fg: var(--x-button-on-tone);
51
+ --x-button-bg: var(--x-button-tone);
52
+ }
53
+ .x-button--primary:hover:not(:disabled, [aria-disabled=true]) {
54
+ filter: brightness(1.1);
55
+ }
56
+ .x-button--secondary {
57
+ --x-button-fg: var(--x-button-tone);
58
+ --x-button-bg: transparent;
59
+ --x-button-border: color-mix(in oklab, var(--x-button-tone) 60%, transparent);
60
+ }
61
+ .x-button--secondary:hover:not(:disabled, [aria-disabled=true]) {
62
+ --x-button-bg: color-mix(in oklab, var(--x-button-tone) 12%, transparent);
63
+ --x-button-border: var(--x-button-tone);
64
+ }
65
+ .x-button--tertiary {
66
+ --x-button-fg: var(--x-button-quiet-fg);
67
+ --x-button-bg: transparent;
68
+ }
69
+ .x-button--tertiary:hover:not(:disabled, [aria-disabled=true]) {
70
+ --x-button-bg: color-mix(in oklab, var(--x-button-tone) 12%, transparent);
71
+ }
72
+ .x-button--sm {
73
+ --x-button-fs: var(--x-fs-label);
74
+ min-height: var(--x-control-sm);
75
+ padding: 0 var(--x-space-4);
76
+ }
77
+ .x-button--lg {
78
+ min-height: var(--x-control-lg);
79
+ padding: 0 var(--x-space-6);
80
+ }
81
+ .x-button--full {
82
+ display: flex;
83
+ width: 100%;
84
+ }
85
+ .x-button__start, .x-button__end {
86
+ display: inline-flex;
87
+ flex: none;
88
+ }
89
+ .x-button__start svg, .x-button__end svg {
90
+ width: 1.15em;
91
+ height: 1.15em;
92
+ }
93
+ .x-button--loading {
94
+ cursor: progress;
95
+ }
96
+ .x-button--loading .x-button__start,
97
+ .x-button--loading .x-button__label,
98
+ .x-button--loading .x-button__end {
99
+ opacity: 0;
100
+ }
101
+ .x-button__spinner {
102
+ position: absolute;
103
+ inset: 0;
104
+ width: 1.1em;
105
+ height: 1.1em;
106
+ margin: auto;
107
+ border: 2px solid currentcolor;
108
+ border-right-color: transparent;
109
+ border-radius: 50%;
110
+ animation: x-button-spin 0.8s linear infinite;
111
+ }
112
+ @keyframes x-button-spin {
113
+ to {
114
+ transform: rotate(1turn);
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,18 @@
1
+ import './button.scss';
2
+ import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
3
+ import { type ButtonOptions } from './index.ts';
4
+ type Common = Omit<ButtonOptions, 'className'> & {
5
+ /** Shown before the label, usually an icon. */
6
+ start?: ReactNode;
7
+ /** Shown after the label, usually an icon. */
8
+ end?: ReactNode;
9
+ };
10
+ export type ButtonProps = (Common & ButtonHTMLAttributes<HTMLButtonElement> & {
11
+ href?: undefined;
12
+ }) | (Common & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'type'> & {
13
+ href: string;
14
+ disabled?: boolean;
15
+ type?: undefined;
16
+ });
17
+ export declare function Button(props: ButtonProps): import("react").JSX.Element;
18
+ export {};
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import './button.css';
4
+ import { buttonClass, buttonState } from "./index.js";
5
+ export function Button(props) {
6
+ const { variant, tone, size, fullWidth, loading, disabled, className, start, end, children, onClick, href, type, ...rest } = props;
7
+ const classes = buttonClass({ variant, tone, size, fullWidth, loading, className });
8
+ const { tag, attrs } = buttonState({ href, disabled, loading, type });
9
+ // aria-disabled does not stop clicks or Enter, so inactive buttons swallow them here.
10
+ const handleClick = (event) => {
11
+ if (disabled || loading) {
12
+ event.preventDefault();
13
+ return;
14
+ }
15
+ ;
16
+ onClick?.(event);
17
+ };
18
+ const content = (_jsxs(_Fragment, { children: [start != null && _jsx("span", { className: "x-button__start", children: start }), _jsx("span", { className: "x-button__label", children: children }), end != null && _jsx("span", { className: "x-button__end", children: end }), loading && (_jsx("span", { className: "x-button__spinner", "aria-hidden": "true" }))] }));
19
+ if (tag === 'a') {
20
+ return (_jsx("a", { ...rest, ...attrs, className: classes, onClick: handleClick, children: content }));
21
+ }
22
+ return (_jsx("button", { ...rest, ...attrs, className: classes, onClick: handleClick, children: content }));
23
+ }
@@ -0,0 +1,59 @@
1
+ export type ButtonVariant = 'primary' | 'secondary' | 'tertiary';
2
+ export type ButtonTone = 'default' | 'danger' | 'warning' | 'success';
3
+ export type ButtonSize = 'sm' | 'md' | 'lg';
4
+ export type ButtonOptions = {
5
+ variant?: ButtonVariant;
6
+ tone?: ButtonTone;
7
+ size?: ButtonSize;
8
+ fullWidth?: boolean;
9
+ loading?: boolean;
10
+ className?: string;
11
+ };
12
+ export type ButtonStateInput = {
13
+ href?: string;
14
+ disabled?: boolean;
15
+ loading?: boolean;
16
+ type?: 'button' | 'submit' | 'reset';
17
+ };
18
+ export declare const buttonClass: ({ variant, tone, size, fullWidth, loading, className, }?: ButtonOptions) => string;
19
+ export declare const buttonState: ({ href, disabled, loading, type }: ButtonStateInput) => {
20
+ tag: "a";
21
+ attrs: {
22
+ 'aria-busy': "true";
23
+ 'aria-disabled': "true";
24
+ } | {
25
+ 'aria-busy'?: undefined;
26
+ 'aria-disabled': "true";
27
+ } | {
28
+ 'aria-busy': "true";
29
+ href: string;
30
+ } | {
31
+ 'aria-busy'?: undefined;
32
+ href: string;
33
+ };
34
+ } | {
35
+ tag: "button";
36
+ attrs: {
37
+ 'aria-busy': "true";
38
+ disabled: boolean;
39
+ type: "button" | "submit" | "reset";
40
+ } | {
41
+ 'aria-busy'?: undefined;
42
+ disabled: boolean;
43
+ type: "button" | "submit" | "reset";
44
+ } | {
45
+ 'aria-busy': "true";
46
+ 'aria-disabled': "true";
47
+ type: "button" | "submit" | "reset";
48
+ } | {
49
+ 'aria-busy'?: undefined;
50
+ 'aria-disabled': "true";
51
+ type: "button" | "submit" | "reset";
52
+ } | {
53
+ 'aria-busy': "true";
54
+ type: "button" | "submit" | "reset";
55
+ } | {
56
+ 'aria-busy'?: undefined;
57
+ type: "button" | "submit" | "reset";
58
+ };
59
+ };
@@ -0,0 +1,26 @@
1
+ import { bem } from "../../bem.js";
2
+ const button = bem('x-button', { defaults: { tone: 'default', size: 'md' } });
3
+ export const buttonClass = ({ variant = 'primary', tone = 'default', size = 'md', fullWidth, loading, className, } = {}) => button({ variant, tone, size, full: fullWidth, loading }, className);
4
+ // Links cannot be disabled natively, so an inactive link drops its href and is marked aria-disabled.
5
+ // Loading keeps a plain button focusable (aria-disabled instead of disabled) so keyboard focus is not lost
6
+ // mid-action. A loading submit or reset button is disabled, since without JS aria-disabled would not stop it.
7
+ // Keys are only present when set, so they never erase a user attribute.
8
+ export const buttonState = ({ href, disabled, loading, type = 'button' }) => {
9
+ const busy = loading ? { 'aria-busy': 'true' } : {};
10
+ if (href !== undefined) {
11
+ const inactive = Boolean(disabled || loading);
12
+ return {
13
+ tag: 'a',
14
+ attrs: inactive ? { 'aria-disabled': 'true', ...busy } : { href, ...busy },
15
+ };
16
+ }
17
+ const native = Boolean(disabled || (loading && type !== 'button'));
18
+ return {
19
+ tag: 'button',
20
+ attrs: {
21
+ type,
22
+ ...(native ? { disabled: true } : loading ? { 'aria-disabled': 'true' } : {}),
23
+ ...busy,
24
+ },
25
+ };
26
+ };
@@ -0,0 +1,98 @@
1
+ ---
2
+ import './card.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import {
5
+ cardClass,
6
+ cardIndexStyle,
7
+ type CardElement,
8
+ type CardHeadingLevel,
9
+ type CardOptions,
10
+ } from './index.js'
11
+
12
+ type Props = Omit<HTMLAttributes<'article'>, 'class' | 'title'> &
13
+ Omit<CardOptions, 'className' | 'link'> & {
14
+ class?: string
15
+ /** Makes the whole card one link, through the title. Needs a title; keep other content non-interactive. */
16
+ href?: string
17
+ /** Plain-text title; the `title` slot takes markup. */
18
+ title?: string
19
+ index?: number
20
+ as?: CardElement
21
+ headingLevel?: CardHeadingLevel
22
+ }
23
+
24
+ const {
25
+ featured,
26
+ href,
27
+ title,
28
+ index,
29
+ as: El = 'article',
30
+ headingLevel = 3,
31
+ class: className,
32
+ slot: _slot,
33
+ ...rest
34
+ } = Astro.props
35
+ const H = `h${headingLevel}` as const
36
+ const hasTitle = title !== undefined || Astro.slots.has('title')
37
+ const hasStatus = Astro.slots.has('status')
38
+ const hasFootStart = Astro.slots.has('footStart')
39
+ const hasFootEnd = Astro.slots.has('footEnd')
40
+ ---
41
+
42
+ <El {...rest} class={cardClass({ featured, link: href !== undefined, className })}>
43
+ {
44
+ (index !== undefined || hasStatus) && (
45
+ <div class="x-card__head">
46
+ {index !== undefined && <span class="x-card__index" style={cardIndexStyle(index)} aria-hidden="true" />}
47
+ {hasStatus && (
48
+ <span class="x-card__status">
49
+ <slot name="status" />
50
+ </span>
51
+ )}
52
+ </div>
53
+ )
54
+ }
55
+ {
56
+ hasTitle && (
57
+ <H class="x-card__title">
58
+ {href !== undefined ? (
59
+ <a class="x-card__link" href={href}>
60
+ <slot name="title">{title}</slot>
61
+ </a>
62
+ ) : (
63
+ <slot name="title">{title}</slot>
64
+ )}
65
+ </H>
66
+ )
67
+ }
68
+ {
69
+ Astro.slots.has('default') && (
70
+ <div class="x-card__body">
71
+ <slot />
72
+ </div>
73
+ )
74
+ }
75
+ {
76
+ Astro.slots.has('tags') && (
77
+ <div class="x-card__tags">
78
+ <slot name="tags" />
79
+ </div>
80
+ )
81
+ }
82
+ {
83
+ (hasFootStart || hasFootEnd) && (
84
+ <div class="x-card__foot">
85
+ {hasFootStart && (
86
+ <div class="x-card__foot-start">
87
+ <slot name="footStart" />
88
+ </div>
89
+ )}
90
+ {hasFootEnd && (
91
+ <div class="x-card__foot-end">
92
+ <slot name="footEnd" />
93
+ </div>
94
+ )}
95
+ </div>
96
+ )
97
+ }
98
+ </El>
@@ -0,0 +1,91 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-card {
4
+ --x-card-pad: var(--x-space-4);
5
+ position: relative;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: var(--x-card-gap, var(--x-space-3));
9
+ box-sizing: border-box;
10
+ min-width: 0;
11
+ height: 100%;
12
+ padding: var(--x-card-pad);
13
+ color: var(--x-color-text);
14
+ }
15
+ @media (min-width: 640px) {
16
+ .x-card {
17
+ --x-card-pad: var(--x-space-5);
18
+ }
19
+ }
20
+ .x-card__head {
21
+ display: flex;
22
+ gap: var(--x-space-3);
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ }
26
+ .x-card__index {
27
+ counter-reset: x-card-index var(--x-card-index, 0);
28
+ font-size: var(--x-fs-label);
29
+ line-height: 1.3;
30
+ color: var(--x-color-text-faint);
31
+ }
32
+ .x-card__index::before {
33
+ content: var(--x-card-index-prefix, "") counter(x-card-index, var(--x-card-index-style, decimal));
34
+ }
35
+ .x-card__status {
36
+ display: inline-flex;
37
+ margin-left: auto;
38
+ }
39
+ .x-card__title {
40
+ font-size: var(--x-fs-h3);
41
+ line-height: 1.3;
42
+ }
43
+ .x-card__link {
44
+ color: inherit;
45
+ text-decoration: none;
46
+ }
47
+ .x-card__link:hover {
48
+ color: inherit;
49
+ }
50
+ .x-card__link::after {
51
+ content: "";
52
+ position: absolute;
53
+ inset: 0;
54
+ }
55
+ .x-card--link:has(.x-card__link:focus-visible) {
56
+ outline: var(--x-focus-width) solid var(--x-color-focus);
57
+ outline-offset: var(--x-focus-offset);
58
+ }
59
+ .x-card--link:has(.x-card__link:focus-visible) .x-card__link:focus-visible {
60
+ outline: none;
61
+ }
62
+ .x-card__body {
63
+ flex: 1;
64
+ color: var(--x-color-text-muted);
65
+ }
66
+ .x-card__tags {
67
+ display: flex;
68
+ flex-wrap: wrap;
69
+ gap: 6px;
70
+ }
71
+ .x-card__foot {
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ gap: var(--x-space-3);
75
+ align-items: center;
76
+ justify-content: space-between;
77
+ padding-top: var(--x-space-3);
78
+ margin-top: auto;
79
+ border-top: 1px var(--x-card-foot-style, solid) var(--x-color-line-strong);
80
+ }
81
+ .x-card__foot-start {
82
+ display: flex;
83
+ flex-wrap: wrap;
84
+ gap: 6px;
85
+ align-items: center;
86
+ min-width: 0;
87
+ }
88
+ .x-card__foot-end {
89
+ margin-left: auto;
90
+ }
91
+ }
@@ -0,0 +1,19 @@
1
+ import './card.scss';
2
+ import type { HTMLAttributes, ReactNode } from 'react';
3
+ import { type CardElement, type CardHeadingLevel, type CardOptions } from './index.ts';
4
+ export type CardProps = Omit<CardOptions, 'className' | 'link'> & Omit<HTMLAttributes<HTMLElement>, 'title'> & {
5
+ /** Makes the whole card one link, through the title. Needs a title; keep other content non-interactive. */
6
+ href?: string;
7
+ title?: ReactNode;
8
+ /** Number shown in the head; the theme formats it (T-01, IV). */
9
+ index?: number;
10
+ /** Head row, right side. Usually a Badge. */
11
+ status?: ReactNode;
12
+ /** Row of Tag chips under the body. */
13
+ tags?: ReactNode;
14
+ footStart?: ReactNode;
15
+ footEnd?: ReactNode;
16
+ as?: CardElement;
17
+ headingLevel?: CardHeadingLevel;
18
+ };
19
+ export declare function Card({ featured, href, title, index, status, tags, footStart, footEnd, as: El, headingLevel, className, children, ...rest }: CardProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './card.css';
3
+ import { cardClass, cardIndexProperty, cardIndexValue, } from "./index.js";
4
+ export function Card({ featured, href, title, index, status, tags, footStart, footEnd, as: El = 'article', headingLevel = 3, className, children, ...rest }) {
5
+ const H = `h${headingLevel}`;
6
+ return (_jsxs(El, { ...rest, className: cardClass({ featured, link: href !== undefined, className }), children: [(index !== undefined || status != null) && (_jsxs("div", { className: "x-card__head", children: [index !== undefined && (_jsx("span", { className: "x-card__index", style: { [cardIndexProperty]: cardIndexValue(index) }, "aria-hidden": "true" })), status != null && _jsx("span", { className: "x-card__status", children: status })] })), title != null && (_jsx(H, { className: "x-card__title", children: href !== undefined ? (_jsx("a", { className: "x-card__link", href: href, children: title })) : (title) })), children != null && _jsx("div", { className: "x-card__body", children: children }), tags != null && _jsx("div", { className: "x-card__tags", children: tags }), (footStart != null || footEnd != null) && (_jsxs("div", { className: "x-card__foot", children: [footStart != null && _jsx("div", { className: "x-card__foot-start", children: footStart }), footEnd != null && _jsx("div", { className: "x-card__foot-end", children: footEnd })] }))] }));
7
+ }
@@ -0,0 +1,11 @@
1
+ export type CardElement = 'article' | 'div' | 'li';
2
+ export type CardHeadingLevel = 2 | 3 | 4 | 5 | 6;
3
+ export type CardOptions = {
4
+ featured?: boolean;
5
+ link?: boolean;
6
+ className?: string;
7
+ };
8
+ export declare const cardClass: ({ featured, link, className }?: CardOptions) => string;
9
+ export declare const cardIndexProperty = "--x-card-index";
10
+ export declare const cardIndexValue: (index: number) => string;
11
+ export declare const cardIndexStyle: (index: number) => string;
@@ -0,0 +1,7 @@
1
+ import { bem } from "../../bem.js";
2
+ const card = bem('x-card');
3
+ export const cardClass = ({ featured, link, className } = {}) => card({ link, featured }, className);
4
+ // Passed as a number; each theme formats it with a CSS counter (T-01, IV).
5
+ export const cardIndexProperty = '--x-card-index';
6
+ export const cardIndexValue = (index) => String(Math.trunc(index));
7
+ export const cardIndexStyle = (index) => `${cardIndexProperty}:${cardIndexValue(index)}`;
@@ -0,0 +1,39 @@
1
+ ---
2
+ import './description-list.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import {
5
+ descriptionListClass,
6
+ descriptionListValueSlot,
7
+ type DescriptionListItem,
8
+ type DescriptionListOptions,
9
+ } from './index.js'
10
+
11
+ type Props = Omit<HTMLAttributes<'dl'>, 'class'> &
12
+ Omit<DescriptionListOptions, 'className'> & {
13
+ class?: string
14
+ /** Plain values. For markup (links, times), fill the `value-<index>` slot. */
15
+ items: DescriptionListItem[]
16
+ }
17
+
18
+ const { items, layout, dividers, class: className, slot: _slot, ...rest } = Astro.props
19
+ const slotted = new Map<number, string>()
20
+ for (const i of items.keys()) {
21
+ const name = descriptionListValueSlot(i)
22
+ if (Astro.slots.has(name)) {
23
+ slotted.set(i, await Astro.slots.render(name))
24
+ }
25
+ }
26
+ ---
27
+
28
+ <dl {...rest} class={descriptionListClass({ layout, dividers, className })}>
29
+ {
30
+ items.map((item, i) => (
31
+ <div class="x-description-list__item">
32
+ <dt class="x-description-list__term">{item.term}</dt>
33
+ <dd class="x-description-list__value">
34
+ {slotted.has(i) ? <Fragment set:html={slotted.get(i)} /> : item.value}
35
+ </dd>
36
+ </div>
37
+ ))
38
+ }
39
+ </dl>
@@ -0,0 +1,72 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-description-list {
4
+ --x-description-list-term-font: var(--x-font-ui);
5
+ --x-description-list-term-fs: var(--x-fs-ui);
6
+ --x-description-list-term-weight: 400;
7
+ --x-description-list-term-tracking: normal;
8
+ --x-description-list-term-case: none;
9
+ --x-description-list-term-color: var(--x-color-text-muted);
10
+ --x-description-list-value-font: var(--x-font-ui);
11
+ --x-description-list-value-fs: var(--x-fs-ui);
12
+ --x-description-list-value-color: var(--x-color-text);
13
+ --x-description-list-gap: var(--x-space-2);
14
+ --x-description-list-pad: 10px;
15
+ --x-description-list-line: var(--x-color-line);
16
+ --x-description-list-term-width: 7rem;
17
+ display: grid;
18
+ gap: var(--x-description-list-gap);
19
+ min-width: 0;
20
+ margin: 0;
21
+ }
22
+ .x-description-list__item {
23
+ display: grid;
24
+ gap: 2px;
25
+ min-width: 0;
26
+ }
27
+ .x-description-list__term {
28
+ font-family: var(--x-description-list-term-font);
29
+ font-size: var(--x-description-list-term-fs);
30
+ font-weight: var(--x-description-list-term-weight);
31
+ line-height: 1.4;
32
+ letter-spacing: var(--x-description-list-term-tracking);
33
+ text-transform: var(--x-description-list-term-case);
34
+ color: var(--x-description-list-term-color);
35
+ }
36
+ .x-description-list__value {
37
+ min-width: 0;
38
+ margin: 0;
39
+ font-family: var(--x-description-list-value-font);
40
+ font-size: var(--x-description-list-value-fs);
41
+ line-height: 1.4;
42
+ color: var(--x-description-list-value-color);
43
+ overflow-wrap: anywhere;
44
+ }
45
+ .x-description-list--inline .x-description-list__item {
46
+ display: flex;
47
+ gap: var(--x-space-3);
48
+ align-items: baseline;
49
+ justify-content: space-between;
50
+ }
51
+ .x-description-list--inline .x-description-list__term {
52
+ flex: none;
53
+ }
54
+ .x-description-list--inline .x-description-list__value {
55
+ text-align: right;
56
+ }
57
+ @media (min-width: 640px) {
58
+ .x-description-list--columns .x-description-list__item {
59
+ grid-template-columns: var(--x-description-list-term-width) minmax(0, 1fr);
60
+ gap: var(--x-space-3);
61
+ align-items: baseline;
62
+ }
63
+ }
64
+ .x-description-list--dividers {
65
+ gap: 0;
66
+ border-top: 1px solid var(--x-description-list-line);
67
+ }
68
+ .x-description-list--dividers .x-description-list__item {
69
+ padding-block: var(--x-description-list-pad);
70
+ border-bottom: 1px solid var(--x-description-list-line);
71
+ }
72
+ }
@@ -0,0 +1,7 @@
1
+ import './description-list.scss';
2
+ import type { HTMLAttributes, ReactNode } from 'react';
3
+ import { type DescriptionListItem, type DescriptionListOptions } from './index.ts';
4
+ export type DescriptionListProps = Omit<HTMLAttributes<HTMLDListElement>, 'children'> & Omit<DescriptionListOptions, 'className'> & {
5
+ items: DescriptionListItem<ReactNode>[];
6
+ };
7
+ export declare function DescriptionList({ items, layout, dividers, className, ...rest }: DescriptionListProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './description-list.css';
3
+ import { descriptionListClass } from "./index.js";
4
+ export function DescriptionList({ items, layout, dividers, className, ...rest }) {
5
+ return (_jsx("dl", { ...rest, className: descriptionListClass({ layout, dividers, className }), children: items.map((item, i) => (_jsxs("div", { className: "x-description-list__item", children: [_jsx("dt", { className: "x-description-list__term", children: item.term }), _jsx("dd", { className: "x-description-list__value", children: item.value })] }, i))) }));
6
+ }