@kwtv/components 0.7.2 → 0.9.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/README.md +10 -8
- package/package.json +2 -2
- package/src/astro/AccountNav.astro +206 -0
- package/src/astro/Alert.astro +4 -57
- package/src/astro/AuthCard.astro +131 -0
- package/src/astro/EmptyState.astro +5 -32
- package/src/astro/IconButton.astro +33 -14
- package/src/astro/Skeleton.astro +5 -56
- package/src/react/Drawer.css +71 -1
- package/src/react/Drawer.tsx +146 -18
- package/src/react/OrderStatus.css +110 -0
- package/src/react/OrderStatus.tsx +144 -0
- package/src/react/OrderTimeline.css +104 -0
- package/src/react/OrderTimeline.tsx +112 -0
- package/src/styles/controls.css +3 -0
- package/src/styles/feedback.css +148 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import "./OrderTimeline.css";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Order timeline. Where one order is, for the customer: paid, being prepared,
|
|
5
|
+
* ready for pickup, picked up. One step per row, top to bottom, each with the
|
|
6
|
+
* date it happened. A React component because account pages are rendered in
|
|
7
|
+
* the browser from live data.
|
|
8
|
+
*
|
|
9
|
+
* A step is done, current or still to come. "stopped" ends the line for an
|
|
10
|
+
* order that will not go on, such as a cancelled one; the steps after it are
|
|
11
|
+
* left out by the consumer. Every state is a mark plus words, never colour
|
|
12
|
+
* alone: the current step carries aria-current="step" and the state is spelled
|
|
13
|
+
* out for screen readers.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type OrderStepState = "done" | "current" | "todo" | "stopped";
|
|
17
|
+
|
|
18
|
+
export interface OrderStep {
|
|
19
|
+
label: string;
|
|
20
|
+
state: OrderStepState;
|
|
21
|
+
/** When it happened, e.g. "4 januari 2026, 14:32". Empty for steps to come. */
|
|
22
|
+
date?: string;
|
|
23
|
+
/** One line under the date, such as where to pick up. */
|
|
24
|
+
note?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface OrderTimelineProps {
|
|
28
|
+
steps: OrderStep[];
|
|
29
|
+
/** Names the list, e.g. "Verloop van bestelling #1088". */
|
|
30
|
+
label: string;
|
|
31
|
+
/** Words read after each step's label. */
|
|
32
|
+
stateWords?: Record<OrderStepState, string>;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const defaultWords: Record<OrderStepState, string> = {
|
|
37
|
+
done: "afgerond",
|
|
38
|
+
current: "nu",
|
|
39
|
+
todo: "nog niet",
|
|
40
|
+
stopped: "gestopt",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function Mark({ state }: { state: OrderStepState }) {
|
|
44
|
+
if (state === "done") {
|
|
45
|
+
return (
|
|
46
|
+
<svg
|
|
47
|
+
width="12"
|
|
48
|
+
height="12"
|
|
49
|
+
viewBox="0 0 24 24"
|
|
50
|
+
fill="none"
|
|
51
|
+
stroke="currentColor"
|
|
52
|
+
strokeWidth="3"
|
|
53
|
+
strokeLinecap="round"
|
|
54
|
+
strokeLinejoin="round"
|
|
55
|
+
>
|
|
56
|
+
<path d="M5 13l4 4L19 7" />
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (state === "stopped") {
|
|
61
|
+
return (
|
|
62
|
+
<svg
|
|
63
|
+
width="12"
|
|
64
|
+
height="12"
|
|
65
|
+
viewBox="0 0 24 24"
|
|
66
|
+
fill="none"
|
|
67
|
+
stroke="currentColor"
|
|
68
|
+
strokeWidth="3"
|
|
69
|
+
strokeLinecap="round"
|
|
70
|
+
>
|
|
71
|
+
<path d="M7 7l10 10M17 7L7 17" />
|
|
72
|
+
</svg>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function OrderTimeline({
|
|
79
|
+
steps,
|
|
80
|
+
label,
|
|
81
|
+
stateWords = defaultWords,
|
|
82
|
+
className,
|
|
83
|
+
}: OrderTimelineProps) {
|
|
84
|
+
return (
|
|
85
|
+
<ol
|
|
86
|
+
className={["ui-otime", className].filter(Boolean).join(" ")}
|
|
87
|
+
aria-label={label}
|
|
88
|
+
>
|
|
89
|
+
{steps.map((step, i) => (
|
|
90
|
+
<li
|
|
91
|
+
key={i}
|
|
92
|
+
className={`ui-otime__step is-${step.state}`}
|
|
93
|
+
aria-current={step.state === "current" ? "step" : undefined}
|
|
94
|
+
>
|
|
95
|
+
<span className="ui-otime__dot" aria-hidden="true">
|
|
96
|
+
<Mark state={step.state} />
|
|
97
|
+
</span>
|
|
98
|
+
<span className="ui-otime__text">
|
|
99
|
+
<b className="ui-otime__label">
|
|
100
|
+
{step.label}
|
|
101
|
+
<span className="ui-sr-only">, {stateWords[step.state]}</span>
|
|
102
|
+
</b>
|
|
103
|
+
{step.date && <span className="ui-otime__date">{step.date}</span>}
|
|
104
|
+
{step.note && <span className="ui-otime__note">{step.note}</span>}
|
|
105
|
+
</span>
|
|
106
|
+
</li>
|
|
107
|
+
))}
|
|
108
|
+
</ol>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default OrderTimeline;
|
package/src/styles/controls.css
CHANGED
|
@@ -158,6 +158,9 @@
|
|
|
158
158
|
border: 1px solid transparent;
|
|
159
159
|
border-radius: var(--radius-md);
|
|
160
160
|
color: var(--text);
|
|
161
|
+
/* IconButton renders a link when it has an href. Keep the count pill
|
|
162
|
+
free of the link underline. */
|
|
163
|
+
text-decoration: none;
|
|
161
164
|
cursor: pointer;
|
|
162
165
|
transition:
|
|
163
166
|
background-color var(--duration-fast) var(--ease-standard),
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Feedback styles as plain, importable CSS. This is the single copy of the
|
|
3
|
+
* .ui-alert, .ui-empty and .ui-skel rules. Alert.astro, EmptyState.astro and
|
|
4
|
+
* Skeleton.astro import it themselves, so a pure-Astro app needs nothing extra.
|
|
5
|
+
* Bundlers emit it once, however many components and pages pull it in.
|
|
6
|
+
*
|
|
7
|
+
* React and Preact islands cannot render Astro components. An island that
|
|
8
|
+
* shows an alert, an empty state or a loading skeleton writes the same markup
|
|
9
|
+
* as the Astro component and imports this sheet once:
|
|
10
|
+
*
|
|
11
|
+
* import "@kwtv/components/styles/feedback.css";
|
|
12
|
+
*
|
|
13
|
+
* Import `@kwtv/tokens/tokens.css` before this file.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
@layer reset, tokens, base, components, motion, utilities;
|
|
17
|
+
|
|
18
|
+
@layer components {
|
|
19
|
+
/* ---- Alert ---------------------------------------------------------------- */
|
|
20
|
+
.ui-alert {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--space-2-5, 0.625rem);
|
|
24
|
+
padding: var(--space-3) var(--space-4);
|
|
25
|
+
border-left: 3px solid currentColor;
|
|
26
|
+
border-radius: var(--radius-md);
|
|
27
|
+
font-family: var(--font-sans);
|
|
28
|
+
font-size: var(--text-sm);
|
|
29
|
+
line-height: var(--leading-sm);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ui-alert__icon {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
align-self: flex-start;
|
|
36
|
+
margin-top: calc((var(--leading-sm, 1.375rem) - 16px) / 2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Body keeps full strength; the left rule carries the tone. */
|
|
40
|
+
.ui-alert__body {
|
|
41
|
+
color: var(--text);
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: var(--space-1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ui-alert__title {
|
|
48
|
+
font-weight: var(--weight-semibold);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ui-alert--info {
|
|
52
|
+
background: var(--surface-accent);
|
|
53
|
+
color: var(--surface-accent-fg, var(--primary));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ui-alert--success {
|
|
57
|
+
background: var(--success-bg);
|
|
58
|
+
color: var(--success);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ui-alert--warning {
|
|
62
|
+
background: var(--warning-bg);
|
|
63
|
+
color: var(--warning);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ui-alert--danger {
|
|
67
|
+
background: var(--danger-bg);
|
|
68
|
+
color: var(--danger);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ---- Empty state ---------------------------------------------------------- */
|
|
72
|
+
.ui-empty {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
align-items: flex-start;
|
|
76
|
+
gap: var(--space-2);
|
|
77
|
+
padding: var(--space-8) var(--space-6);
|
|
78
|
+
border: 1px dashed var(--border-strong);
|
|
79
|
+
border-radius: var(--radius-lg);
|
|
80
|
+
background: var(--surface);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ui-empty__title {
|
|
84
|
+
font-size: var(--text-lg);
|
|
85
|
+
font-weight: var(--weight-semibold);
|
|
86
|
+
color: var(--text);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.ui-empty__body {
|
|
90
|
+
font-size: var(--text-sm);
|
|
91
|
+
line-height: var(--leading-sm);
|
|
92
|
+
color: var(--text-muted);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* The flex gap above spaces the action from the body. No extra margin. */
|
|
96
|
+
|
|
97
|
+
/* ---- Skeleton ------------------------------------------------------------- */
|
|
98
|
+
.ui-skel {
|
|
99
|
+
display: block;
|
|
100
|
+
background: var(--surface-hover);
|
|
101
|
+
background-image: linear-gradient(
|
|
102
|
+
90deg,
|
|
103
|
+
var(--surface-hover) 0%,
|
|
104
|
+
var(--border) 50%,
|
|
105
|
+
var(--surface-hover) 100%
|
|
106
|
+
);
|
|
107
|
+
background-size: 200% 100%;
|
|
108
|
+
border-radius: var(--radius-md);
|
|
109
|
+
animation: ui-skel-shimmer 1400ms ease-in-out infinite;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.ui-skel--img {
|
|
113
|
+
width: 100%;
|
|
114
|
+
aspect-ratio: 4 / 3;
|
|
115
|
+
border-radius: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ui-skel--title {
|
|
119
|
+
height: 0.875rem;
|
|
120
|
+
border-radius: var(--radius-sm);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ui-skel--line {
|
|
124
|
+
height: 0.625rem;
|
|
125
|
+
border-radius: var(--radius-sm);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.ui-skel--block {
|
|
129
|
+
height: 1rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@keyframes ui-skel-shimmer {
|
|
133
|
+
0% {
|
|
134
|
+
background-position: 200% 0;
|
|
135
|
+
}
|
|
136
|
+
100% {
|
|
137
|
+
background-position: -200% 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* The shimmer stops for readers who ask for reduced motion. */
|
|
142
|
+
@media (prefers-reduced-motion: reduce) {
|
|
143
|
+
.ui-skel {
|
|
144
|
+
animation: none;
|
|
145
|
+
background-image: none;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|