@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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/all.css +2536 -0
- package/dist/astro.d.ts +41 -0
- package/dist/astro.js +41 -0
- package/dist/bem.d.ts +12 -0
- package/dist/bem.js +19 -0
- package/dist/components/alert/alert.astro +46 -0
- package/dist/components/alert/alert.css +68 -0
- package/dist/components/alert/alert.d.ts +11 -0
- package/dist/components/alert/alert.js +6 -0
- package/dist/components/alert/index.d.ts +14 -0
- package/dist/components/alert/index.js +27 -0
- package/dist/components/avatar/avatar.astro +23 -0
- package/dist/components/avatar/avatar.css +35 -0
- package/dist/components/avatar/avatar.d.ts +5 -0
- package/dist/components/avatar/avatar.js +7 -0
- package/dist/components/avatar/index.d.ts +32 -0
- package/dist/components/avatar/index.js +24 -0
- package/dist/components/badge/badge.astro +11 -0
- package/dist/components/badge/badge.css +46 -0
- package/dist/components/badge/badge.d.ts +5 -0
- package/dist/components/badge/badge.js +6 -0
- package/dist/components/badge/index.d.ts +6 -0
- package/dist/components/badge/index.js +3 -0
- package/dist/components/breadcrumbs/breadcrumbs.astro +27 -0
- package/dist/components/breadcrumbs/breadcrumbs.css +33 -0
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +9 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +11 -0
- package/dist/components/breadcrumbs/index.d.ts +37 -0
- package/dist/components/breadcrumbs/index.js +16 -0
- package/dist/components/button/button.astro +36 -0
- package/dist/components/button/button.css +117 -0
- package/dist/components/button/button.d.ts +18 -0
- package/dist/components/button/button.js +23 -0
- package/dist/components/button/index.d.ts +59 -0
- package/dist/components/button/index.js +26 -0
- package/dist/components/card/card.astro +98 -0
- package/dist/components/card/card.css +91 -0
- package/dist/components/card/card.d.ts +19 -0
- package/dist/components/card/card.js +7 -0
- package/dist/components/card/index.d.ts +11 -0
- package/dist/components/card/index.js +7 -0
- package/dist/components/description-list/description-list.astro +39 -0
- package/dist/components/description-list/description-list.css +72 -0
- package/dist/components/description-list/description-list.d.ts +7 -0
- package/dist/components/description-list/description-list.js +6 -0
- package/dist/components/description-list/index.d.ts +12 -0
- package/dist/components/description-list/index.js +5 -0
- package/dist/components/divider/divider.astro +27 -0
- package/dist/components/divider/divider.css +40 -0
- package/dist/components/divider/divider.d.ts +5 -0
- package/dist/components/divider/divider.js +11 -0
- package/dist/components/divider/index.d.ts +22 -0
- package/dist/components/divider/index.js +11 -0
- package/dist/components/empty-state/empty-state.astro +47 -0
- package/dist/components/empty-state/empty-state.css +38 -0
- package/dist/components/empty-state/empty-state.d.ts +12 -0
- package/dist/components/empty-state/empty-state.js +7 -0
- package/dist/components/empty-state/index.d.ts +5 -0
- package/dist/components/empty-state/index.js +3 -0
- package/dist/components/field/checkbox-group.astro +58 -0
- package/dist/components/field/checkbox.astro +17 -0
- package/dist/components/field/field.astro +51 -0
- package/dist/components/field/field.css +336 -0
- package/dist/components/field/field.d.ts +47 -0
- package/dist/components/field/field.js +51 -0
- package/dist/components/field/index.d.ts +53 -0
- package/dist/components/field/index.js +54 -0
- package/dist/components/field/input.astro +27 -0
- package/dist/components/field/radio-group.astro +58 -0
- package/dist/components/field/radio.astro +17 -0
- package/dist/components/field/select.astro +13 -0
- package/dist/components/field/switch.astro +17 -0
- package/dist/components/field/textarea.astro +24 -0
- package/dist/components/filters/filter-layout.astro +72 -0
- package/dist/components/filters/filter-panel.astro +13 -0
- package/dist/components/filters/filters.css +230 -0
- package/dist/components/filters/filters.d.ts +20 -0
- package/dist/components/filters/filters.js +24 -0
- package/dist/components/filters/index.d.ts +27 -0
- package/dist/components/filters/index.js +25 -0
- package/dist/components/footer/footer.astro +70 -0
- package/dist/components/footer/footer.css +128 -0
- package/dist/components/footer/footer.d.ts +16 -0
- package/dist/components/footer/footer.js +7 -0
- package/dist/components/footer/index.d.ts +35 -0
- package/dist/components/footer/index.js +18 -0
- package/dist/components/header/header.astro +89 -0
- package/dist/components/header/header.css +163 -0
- package/dist/components/header/header.d.ts +18 -0
- package/dist/components/header/header.js +9 -0
- package/dist/components/header/index.d.ts +39 -0
- package/dist/components/header/index.js +25 -0
- package/dist/components/icon/icon.astro +21 -0
- package/dist/components/icon/icon.css +15 -0
- package/dist/components/icon/icon.d.ts +5 -0
- package/dist/components/icon/icon.js +6 -0
- package/dist/components/icon/index.d.ts +57 -0
- package/dist/components/icon/index.js +72 -0
- package/dist/components/layout/container.astro +15 -0
- package/dist/components/layout/grid.astro +14 -0
- package/dist/components/layout/index.d.ts +33 -0
- package/dist/components/layout/index.js +13 -0
- package/dist/components/layout/layout.css +262 -0
- package/dist/components/layout/layout.d.ts +24 -0
- package/dist/components/layout/layout.js +17 -0
- package/dist/components/layout/section-head.astro +42 -0
- package/dist/components/layout/section.astro +15 -0
- package/dist/components/page-head/index.d.ts +18 -0
- package/dist/components/page-head/index.js +14 -0
- package/dist/components/page-head/page-head.astro +60 -0
- package/dist/components/page-head/page-head.css +79 -0
- package/dist/components/page-head/page-head.d.ts +18 -0
- package/dist/components/page-head/page-head.js +8 -0
- package/dist/components/pagination/index.d.ts +73 -0
- package/dist/components/pagination/index.js +87 -0
- package/dist/components/pagination/pagination.astro +81 -0
- package/dist/components/pagination/pagination.css +70 -0
- package/dist/components/pagination/pagination.d.ts +8 -0
- package/dist/components/pagination/pagination.js +19 -0
- package/dist/components/panel/index.d.ts +11 -0
- package/dist/components/panel/index.js +3 -0
- package/dist/components/panel/panel.astro +15 -0
- package/dist/components/panel/panel.css +48 -0
- package/dist/components/panel/panel.d.ts +7 -0
- package/dist/components/panel/panel.js +6 -0
- package/dist/components/progress/index.d.ts +37 -0
- package/dist/components/progress/index.js +44 -0
- package/dist/components/progress/progress.astro +18 -0
- package/dist/components/progress/progress.css +115 -0
- package/dist/components/progress/progress.d.ts +5 -0
- package/dist/components/progress/progress.js +7 -0
- package/dist/components/prose/code-block.astro +26 -0
- package/dist/components/prose/index.d.ts +14 -0
- package/dist/components/prose/index.js +67 -0
- package/dist/components/prose/prose.astro +14 -0
- package/dist/components/prose/prose.css +156 -0
- package/dist/components/prose/prose.d.ts +12 -0
- package/dist/components/prose/prose.js +11 -0
- package/dist/components/stat/index.d.ts +15 -0
- package/dist/components/stat/index.js +5 -0
- package/dist/components/stat/stat-group.astro +11 -0
- package/dist/components/stat/stat.astro +43 -0
- package/dist/components/stat/stat.css +51 -0
- package/dist/components/stat/stat.d.ts +15 -0
- package/dist/components/stat/stat.js +10 -0
- package/dist/components/table/index.d.ts +38 -0
- package/dist/components/table/index.js +18 -0
- package/dist/components/table/table.astro +111 -0
- package/dist/components/table/table.css +78 -0
- package/dist/components/table/table.d.ts +11 -0
- package/dist/components/table/table.js +10 -0
- package/dist/components/tabs/index.d.ts +21 -0
- package/dist/components/tabs/index.js +13 -0
- package/dist/components/tabs/tabs.astro +30 -0
- package/dist/components/tabs/tabs.css +59 -0
- package/dist/components/tabs/tabs.d.ts +9 -0
- package/dist/components/tabs/tabs.js +6 -0
- package/dist/components/tag/index.d.ts +19 -0
- package/dist/components/tag/index.js +7 -0
- package/dist/components/tag/tag.astro +17 -0
- package/dist/components/tag/tag.css +30 -0
- package/dist/components/tag/tag.d.ts +9 -0
- package/dist/components/tag/tag.js +12 -0
- package/dist/components/timeline/index.d.ts +22 -0
- package/dist/components/timeline/index.js +5 -0
- package/dist/components/timeline/timeline.astro +40 -0
- package/dist/components/timeline/timeline.css +92 -0
- package/dist/components/timeline/timeline.d.ts +9 -0
- package/dist/components/timeline/timeline.js +7 -0
- package/dist/components/tooltip/index.d.ts +15 -0
- package/dist/components/tooltip/index.js +41 -0
- package/dist/components/tooltip/tooltip.astro +25 -0
- package/dist/components/tooltip/tooltip.css +84 -0
- package/dist/components/tooltip/tooltip.d.ts +12 -0
- package/dist/components/tooltip/tooltip.js +34 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +6 -0
- package/dist/env.d.ts +2 -0
- package/dist/html.d.ts +37 -0
- package/dist/html.js +126 -0
- package/dist/parts.d.ts +27 -0
- package/dist/parts.js +28 -0
- package/dist/react.d.ts +27 -0
- package/dist/react.js +28 -0
- package/dist/scss/_chamfer.scss +37 -0
- package/dist/scss/_layers.scss +2 -0
- package/dist/scss/_theme.scss +15 -0
- package/dist/themes/eve-online.css +1945 -0
- package/dist/themes/xaroth.css +1974 -0
- package/dist/themes.css +3919 -0
- 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
|
+
}
|