@dxlbnl/ui 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/README.md +94 -0
- package/dist/components/cards/Card.stories.svelte +82 -0
- package/dist/components/cards/Card.stories.svelte.d.ts +19 -0
- package/dist/components/cards/Card.svelte +28 -0
- package/dist/components/cards/Card.svelte.d.ts +12 -0
- package/dist/components/cards/NoteCard.stories.svelte +94 -0
- package/dist/components/cards/NoteCard.stories.svelte.d.ts +19 -0
- package/dist/components/cards/NoteCard.svelte +89 -0
- package/dist/components/cards/NoteCard.svelte.d.ts +18 -0
- package/dist/components/cards/ProductCard.stories.svelte +98 -0
- package/dist/components/cards/ProductCard.stories.svelte.d.ts +19 -0
- package/dist/components/cards/ProductCard.svelte +150 -0
- package/dist/components/cards/ProductCard.svelte.d.ts +22 -0
- package/dist/components/cards/ProjectCard.stories.svelte +88 -0
- package/dist/components/cards/ProjectCard.stories.svelte.d.ts +19 -0
- package/dist/components/cards/ProjectCard.svelte +109 -0
- package/dist/components/cards/ProjectCard.svelte.d.ts +20 -0
- package/dist/components/cards/index.d.ts +4 -0
- package/dist/components/cards/index.js +4 -0
- package/dist/components/data/Accordion.stories.svelte +316 -0
- package/dist/components/data/Accordion.stories.svelte.d.ts +19 -0
- package/dist/components/data/Accordion.svelte +23 -0
- package/dist/components/data/Accordion.svelte.d.ts +9 -0
- package/dist/components/data/AccordionItem.svelte +112 -0
- package/dist/components/data/AccordionItem.svelte.d.ts +11 -0
- package/dist/components/data/Table.composition.stories.svelte +67 -0
- package/dist/components/data/Table.composition.stories.svelte.d.ts +19 -0
- package/dist/components/data/Table.stories.svelte +137 -0
- package/dist/components/data/Table.stories.svelte.d.ts +19 -0
- package/dist/components/data/Table.svelte +83 -0
- package/dist/components/data/Table.svelte.d.ts +14 -0
- package/dist/components/data/Tabs.stories.svelte +386 -0
- package/dist/components/data/Tabs.stories.svelte.d.ts +19 -0
- package/dist/components/data/Tabs.svelte +142 -0
- package/dist/components/data/Tabs.svelte.d.ts +19 -0
- package/dist/components/data/index.d.ts +4 -0
- package/dist/components/data/index.js +4 -0
- package/dist/components/feedback/Modal.stories.svelte +192 -0
- package/dist/components/feedback/Modal.stories.svelte.d.ts +4 -0
- package/dist/components/feedback/Modal.svelte +185 -0
- package/dist/components/feedback/Modal.svelte.d.ts +19 -0
- package/dist/components/feedback/Toast.stories.svelte +203 -0
- package/dist/components/feedback/Toast.stories.svelte.d.ts +19 -0
- package/dist/components/feedback/Toast.svelte +109 -0
- package/dist/components/feedback/Toast.svelte.d.ts +15 -0
- package/dist/components/feedback/ToastRegion.stories.svelte +193 -0
- package/dist/components/feedback/ToastRegion.stories.svelte.d.ts +19 -0
- package/dist/components/feedback/ToastRegion.svelte +102 -0
- package/dist/components/feedback/ToastRegion.svelte.d.ts +9 -0
- package/dist/components/feedback/index.d.ts +3 -0
- package/dist/components/feedback/index.js +3 -0
- package/dist/components/forms/Checkbox.stories.svelte +103 -0
- package/dist/components/forms/Checkbox.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Checkbox.svelte +150 -0
- package/dist/components/forms/Checkbox.svelte.d.ts +11 -0
- package/dist/components/forms/Field.stories.svelte +113 -0
- package/dist/components/forms/Field.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Field.svelte +77 -0
- package/dist/components/forms/Field.svelte.d.ts +17 -0
- package/dist/components/forms/Input.stories.svelte +58 -0
- package/dist/components/forms/Input.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Input.svelte +64 -0
- package/dist/components/forms/Input.svelte.d.ts +9 -0
- package/dist/components/forms/InputWrap.composition.stories.svelte +32 -0
- package/dist/components/forms/InputWrap.composition.stories.svelte.d.ts +19 -0
- package/dist/components/forms/InputWrap.stories.svelte +53 -0
- package/dist/components/forms/InputWrap.stories.svelte.d.ts +19 -0
- package/dist/components/forms/InputWrap.svelte +128 -0
- package/dist/components/forms/InputWrap.svelte.d.ts +21 -0
- package/dist/components/forms/Radio.stories.svelte +70 -0
- package/dist/components/forms/Radio.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Radio.svelte +109 -0
- package/dist/components/forms/Radio.svelte.d.ts +9 -0
- package/dist/components/forms/RadioGroup.stories.svelte +115 -0
- package/dist/components/forms/RadioGroup.stories.svelte.d.ts +19 -0
- package/dist/components/forms/RadioGroup.svelte +116 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts +24 -0
- package/dist/components/forms/Select.stories.svelte +168 -0
- package/dist/components/forms/Select.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Select.svelte +262 -0
- package/dist/components/forms/Select.svelte.d.ts +23 -0
- package/dist/components/forms/Switch.stories.svelte +86 -0
- package/dist/components/forms/Switch.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Switch.svelte +113 -0
- package/dist/components/forms/Switch.svelte.d.ts +11 -0
- package/dist/components/forms/Textarea.stories.svelte +40 -0
- package/dist/components/forms/Textarea.stories.svelte.d.ts +19 -0
- package/dist/components/forms/Textarea.svelte +66 -0
- package/dist/components/forms/Textarea.svelte.d.ts +9 -0
- package/dist/components/forms/field-context.d.ts +7 -0
- package/dist/components/forms/field-context.js +1 -0
- package/dist/components/forms/index.d.ts +9 -0
- package/dist/components/forms/index.js +9 -0
- package/dist/components/layout/Container.stories.svelte +67 -0
- package/dist/components/layout/Container.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Container.svelte +52 -0
- package/dist/components/layout/Container.svelte.d.ts +14 -0
- package/dist/components/layout/Grid.stories.svelte +109 -0
- package/dist/components/layout/Grid.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Grid.svelte +54 -0
- package/dist/components/layout/Grid.svelte.d.ts +19 -0
- package/dist/components/layout/Inline.stories.svelte +136 -0
- package/dist/components/layout/Inline.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Inline.svelte +46 -0
- package/dist/components/layout/Inline.svelte.d.ts +19 -0
- package/dist/components/layout/Prose.stories.svelte +423 -0
- package/dist/components/layout/Prose.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Prose.svelte +176 -0
- package/dist/components/layout/Prose.svelte.d.ts +12 -0
- package/dist/components/layout/Rule.stories.svelte +80 -0
- package/dist/components/layout/Rule.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Rule.svelte +33 -0
- package/dist/components/layout/Rule.svelte.d.ts +9 -0
- package/dist/components/layout/Spread.stories.svelte +118 -0
- package/dist/components/layout/Spread.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Spread.svelte +38 -0
- package/dist/components/layout/Spread.svelte.d.ts +16 -0
- package/dist/components/layout/Stack.stories.svelte +90 -0
- package/dist/components/layout/Stack.stories.svelte.d.ts +19 -0
- package/dist/components/layout/Stack.svelte +37 -0
- package/dist/components/layout/Stack.svelte.d.ts +16 -0
- package/dist/components/layout/index.d.ts +7 -0
- package/dist/components/layout/index.js +7 -0
- package/dist/components/navigation/Breadcrumb.stories.svelte +122 -0
- package/dist/components/navigation/Breadcrumb.stories.svelte.d.ts +19 -0
- package/dist/components/navigation/Breadcrumb.svelte +70 -0
- package/dist/components/navigation/Breadcrumb.svelte.d.ts +13 -0
- package/dist/components/navigation/Nav.stories.svelte +323 -0
- package/dist/components/navigation/Nav.stories.svelte.d.ts +19 -0
- package/dist/components/navigation/Nav.svelte +257 -0
- package/dist/components/navigation/Nav.svelte.d.ts +21 -0
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/patterns/ActivityRow.stories.svelte +45 -0
- package/dist/components/patterns/ActivityRow.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/ActivityRow.svelte +69 -0
- package/dist/components/patterns/ActivityRow.svelte.d.ts +16 -0
- package/dist/components/patterns/Alert.stories.svelte +63 -0
- package/dist/components/patterns/Alert.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/Alert.svelte +91 -0
- package/dist/components/patterns/Alert.svelte.d.ts +16 -0
- package/dist/components/patterns/CtaBlock.stories.svelte +62 -0
- package/dist/components/patterns/CtaBlock.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/CtaBlock.svelte +80 -0
- package/dist/components/patterns/CtaBlock.svelte.d.ts +16 -0
- package/dist/components/patterns/KvList.stories.svelte +48 -0
- package/dist/components/patterns/KvList.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/KvList.svelte +65 -0
- package/dist/components/patterns/KvList.svelte.d.ts +15 -0
- package/dist/components/patterns/PageHero.stories.svelte +62 -0
- package/dist/components/patterns/PageHero.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/PageHero.svelte +62 -0
- package/dist/components/patterns/PageHero.svelte.d.ts +14 -0
- package/dist/components/patterns/ProgressBar.stories.svelte +83 -0
- package/dist/components/patterns/ProgressBar.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/ProgressBar.svelte +71 -0
- package/dist/components/patterns/ProgressBar.svelte.d.ts +13 -0
- package/dist/components/patterns/SectionFoot.stories.svelte +37 -0
- package/dist/components/patterns/SectionFoot.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/SectionFoot.svelte +70 -0
- package/dist/components/patterns/SectionFoot.svelte.d.ts +15 -0
- package/dist/components/patterns/SectionHead.stories.svelte +67 -0
- package/dist/components/patterns/SectionHead.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/SectionHead.svelte +54 -0
- package/dist/components/patterns/SectionHead.svelte.d.ts +14 -0
- package/dist/components/patterns/StatCard.stories.svelte +59 -0
- package/dist/components/patterns/StatCard.stories.svelte.d.ts +19 -0
- package/dist/components/patterns/StatCard.svelte +57 -0
- package/dist/components/patterns/StatCard.svelte.d.ts +15 -0
- package/dist/components/patterns/index.d.ts +9 -0
- package/dist/components/patterns/index.js +9 -0
- package/dist/components/primitives/Button.stories.svelte +132 -0
- package/dist/components/primitives/Button.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Button.svelte +142 -0
- package/dist/components/primitives/Button.svelte.d.ts +16 -0
- package/dist/components/primitives/Heading.stories.svelte +137 -0
- package/dist/components/primitives/Heading.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Heading.svelte +107 -0
- package/dist/components/primitives/Heading.svelte.d.ts +23 -0
- package/dist/components/primitives/Led.stories.svelte +63 -0
- package/dist/components/primitives/Led.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Led.svelte +65 -0
- package/dist/components/primitives/Led.svelte.d.ts +11 -0
- package/dist/components/primitives/TagPill.stories.svelte +90 -0
- package/dist/components/primitives/TagPill.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/TagPill.svelte +44 -0
- package/dist/components/primitives/TagPill.svelte.d.ts +9 -0
- package/dist/components/primitives/Text.stories.svelte +252 -0
- package/dist/components/primitives/Text.stories.svelte.d.ts +19 -0
- package/dist/components/primitives/Text.svelte +101 -0
- package/dist/components/primitives/Text.svelte.d.ts +25 -0
- package/dist/components/primitives/index.d.ts +5 -0
- package/dist/components/primitives/index.js +5 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/stores/toast.d.ts +19 -0
- package/dist/stores/toast.js +22 -0
- package/dist/storybook-utils.d.ts +11 -0
- package/dist/storybook-utils.js +29 -0
- package/dist/tokens/ColorSwatch.svelte +73 -0
- package/dist/tokens/ColorSwatch.svelte.d.ts +10 -0
- package/dist/tokens/layout.css +144 -0
- package/dist/tokens/patterns.css +281 -0
- package/dist/tokens/tokens.css +96 -0
- package/dist/tokens/tokens.stories.svelte +107 -0
- package/dist/tokens/tokens.stories.svelte.d.ts +18 -0
- package/dist/tokens/typography.css +159 -0
- package/package.json +62 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements'
|
|
3
|
+
import Card from './Card.svelte'
|
|
4
|
+
import Stack from '../layout/Stack.svelte'
|
|
5
|
+
import Spread from '../layout/Spread.svelte'
|
|
6
|
+
import Inline from '../layout/Inline.svelte'
|
|
7
|
+
import Led from '../primitives/Led.svelte'
|
|
8
|
+
import Text from '../primitives/Text.svelte'
|
|
9
|
+
import Heading from '../primitives/Heading.svelte'
|
|
10
|
+
|
|
11
|
+
type StockStatus = 'in-stock' | 'coming-soon' | 'low-stock' | 'out-of-stock'
|
|
12
|
+
|
|
13
|
+
interface Props extends HTMLAnchorAttributes {
|
|
14
|
+
/** HTML element to render as. @default 'a' */
|
|
15
|
+
as?: string
|
|
16
|
+
/** Product SKU code, shown in the card header. */
|
|
17
|
+
sku: string
|
|
18
|
+
/** Product display name. */
|
|
19
|
+
name: string
|
|
20
|
+
/** Short product description. */
|
|
21
|
+
description: string
|
|
22
|
+
/** Formatted price string (e.g. `'€200'`). */
|
|
23
|
+
price?: string
|
|
24
|
+
/** Inventory status — drives the LED colour and CTA label. @default 'out-of-stock' */
|
|
25
|
+
status?: StockStatus
|
|
26
|
+
/** Override the generated CTA label. */
|
|
27
|
+
ctaLabel?: string
|
|
28
|
+
[key: string]: unknown
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
as = 'a',
|
|
33
|
+
sku,
|
|
34
|
+
name,
|
|
35
|
+
description,
|
|
36
|
+
price,
|
|
37
|
+
status = 'out-of-stock',
|
|
38
|
+
ctaLabel,
|
|
39
|
+
...rest
|
|
40
|
+
}: Props = $props()
|
|
41
|
+
|
|
42
|
+
const ledColor = $derived(
|
|
43
|
+
status === 'in-stock' ? 'ok' : status === 'out-of-stock' ? 'off' : 'amber'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const stockLabel = $derived(
|
|
47
|
+
status === 'in-stock'
|
|
48
|
+
? 'In Stock'
|
|
49
|
+
: status === 'coming-soon'
|
|
50
|
+
? 'Coming Soon'
|
|
51
|
+
: status === 'low-stock'
|
|
52
|
+
? 'Low Stock'
|
|
53
|
+
: 'Out of Stock'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const resolvedCtaLabel = $derived(
|
|
57
|
+
ctaLabel ??
|
|
58
|
+
(status === 'in-stock' || status === 'low-stock'
|
|
59
|
+
? 'BUY NOW'
|
|
60
|
+
: status === 'coming-soon'
|
|
61
|
+
? 'PRE-ORDER'
|
|
62
|
+
: 'VIEW DETAILS')
|
|
63
|
+
)
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<Card as={as} class="product-card" {...rest}>
|
|
67
|
+
<div class="card-img">
|
|
68
|
+
<Text variant="mono" color="faint">{sku} · PRODUCT</Text>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="card-body">
|
|
71
|
+
<Stack gap="xs">
|
|
72
|
+
<Text variant="eyebrow">{sku}</Text>
|
|
73
|
+
<Heading level={3} size="lg">{name}</Heading>
|
|
74
|
+
<Text variant="mono" case="none" color="dim" class="card-desc">{description}</Text>
|
|
75
|
+
<div class="card-footer-row">
|
|
76
|
+
<Spread>
|
|
77
|
+
{#if price}
|
|
78
|
+
<Inline gap="xs" align="baseline">
|
|
79
|
+
<Text variant="mono" color="amber" size="md">{price}</Text>
|
|
80
|
+
<Text variant="mono" color="faint" size="xs" case="lower">incl. VAT</Text>
|
|
81
|
+
</Inline>
|
|
82
|
+
{/if}
|
|
83
|
+
<Inline gap="xs">
|
|
84
|
+
<Led color={ledColor} aria-hidden="true" />
|
|
85
|
+
<Text variant="mono" size="xs">{stockLabel}</Text>
|
|
86
|
+
</Inline>
|
|
87
|
+
</Spread>
|
|
88
|
+
</div>
|
|
89
|
+
</Stack>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="card-cta">
|
|
92
|
+
<Spread>
|
|
93
|
+
<Text variant="mono">{resolvedCtaLabel}</Text>
|
|
94
|
+
<span aria-hidden="true">→</span>
|
|
95
|
+
</Spread>
|
|
96
|
+
</div>
|
|
97
|
+
</Card>
|
|
98
|
+
|
|
99
|
+
<style>
|
|
100
|
+
.card-body {
|
|
101
|
+
padding: 12px 14px 10px;
|
|
102
|
+
flex: 1;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.card-footer-row {
|
|
108
|
+
align-items: baseline;
|
|
109
|
+
margin-top: auto;
|
|
110
|
+
padding-top: 8px;
|
|
111
|
+
display: flex;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
gap: var(--u2);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* :global needed — .product-card is on Card's root, invisible to Svelte's scoped CSS analysis */
|
|
117
|
+
:global(.product-card) {
|
|
118
|
+
text-decoration: none;
|
|
119
|
+
color: inherit;
|
|
120
|
+
cursor: pointer;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.card-img {
|
|
124
|
+
aspect-ratio: 14 / 9;
|
|
125
|
+
background: repeating-linear-gradient(
|
|
126
|
+
135deg,
|
|
127
|
+
var(--bg-sunken) 0 10px,
|
|
128
|
+
var(--bg-elev) 10px 20px
|
|
129
|
+
);
|
|
130
|
+
border-bottom: 1px solid var(--rule);
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
:global(.card-desc) {
|
|
137
|
+
line-height: 1.4;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.card-cta {
|
|
141
|
+
border-top: 1px solid var(--rule);
|
|
142
|
+
padding: 10px 14px;
|
|
143
|
+
transition: background var(--transition), color var(--transition);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
:global(.product-card):hover .card-cta {
|
|
147
|
+
background: var(--amber);
|
|
148
|
+
color: var(--bg);
|
|
149
|
+
}
|
|
150
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
2
|
+
type StockStatus = 'in-stock' | 'coming-soon' | 'low-stock' | 'out-of-stock';
|
|
3
|
+
interface Props extends HTMLAnchorAttributes {
|
|
4
|
+
/** HTML element to render as. @default 'a' */
|
|
5
|
+
as?: string;
|
|
6
|
+
/** Product SKU code, shown in the card header. */
|
|
7
|
+
sku: string;
|
|
8
|
+
/** Product display name. */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Short product description. */
|
|
11
|
+
description: string;
|
|
12
|
+
/** Formatted price string (e.g. `'€200'`). */
|
|
13
|
+
price?: string;
|
|
14
|
+
/** Inventory status — drives the LED colour and CTA label. @default 'out-of-stock' */
|
|
15
|
+
status?: StockStatus;
|
|
16
|
+
/** Override the generated CTA label. */
|
|
17
|
+
ctaLabel?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
declare const ProductCard: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type ProductCard = ReturnType<typeof ProductCard>;
|
|
22
|
+
export default ProductCard;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
|
+
import { expect, within } from "storybook/test";
|
|
4
|
+
import ProjectCard from "./ProjectCard.svelte";
|
|
5
|
+
|
|
6
|
+
const { Story } = defineMeta({
|
|
7
|
+
title: "Cards/ProjectCard",
|
|
8
|
+
component: ProjectCard,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
});
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<Story name="Open Source" args={{ href: "#zod-fixture", slug: "zod-fixture", title: "zod-fixture", description: "Creating fixtures based on zod schemas automatically.", tags: ["TypeScript", "Open Source"], ctaLabel: "OPEN SOURCE" }}
|
|
14
|
+
play={async ({ canvasElement }) => {
|
|
15
|
+
const canvas = within(canvasElement);
|
|
16
|
+
|
|
17
|
+
// renders as <a> by default
|
|
18
|
+
const root = canvasElement.firstElementChild as HTMLElement;
|
|
19
|
+
await expect(root).toBeVisible();
|
|
20
|
+
|
|
21
|
+
// title renders as h3
|
|
22
|
+
const heading = canvas.getByText("zod-fixture");
|
|
23
|
+
await expect(heading).toBeVisible();
|
|
24
|
+
|
|
25
|
+
// tags render as TagPill spans with tag text
|
|
26
|
+
const tsTag = canvas.getByText("TypeScript");
|
|
27
|
+
await expect(tsTag).toBeVisible();
|
|
28
|
+
const osTag = canvas.getByText("Open Source");
|
|
29
|
+
await expect(osTag).toBeVisible();
|
|
30
|
+
|
|
31
|
+
// footer renders ctaLabel
|
|
32
|
+
const cta = canvas.getByText(/OPEN SOURCE/);
|
|
33
|
+
await expect(cta).toBeVisible();
|
|
34
|
+
|
|
35
|
+
// .card-img hatch gradient present and aspect-ratio is 14/9
|
|
36
|
+
const cardImg = canvasElement.querySelector(".card-img") as HTMLElement;
|
|
37
|
+
const imgBg = getComputedStyle(cardImg).backgroundImage;
|
|
38
|
+
await expect(imgBg).not.toBe("none");
|
|
39
|
+
await expect(getComputedStyle(cardImg).aspectRatio).toBe("14 / 9");
|
|
40
|
+
}} />
|
|
41
|
+
|
|
42
|
+
<Story name="No Tags" args={{ href: "#private-share", slug: "private-share", title: "Private Share", description: "Private file sharing utility." }}
|
|
43
|
+
play={async ({ canvasElement }) => {
|
|
44
|
+
const canvas = within(canvasElement);
|
|
45
|
+
|
|
46
|
+
// Card renders without error
|
|
47
|
+
const root = canvasElement.firstElementChild as HTMLElement;
|
|
48
|
+
await expect(root).toBeVisible();
|
|
49
|
+
|
|
50
|
+
// title heading is visible
|
|
51
|
+
const heading = canvas.getByText("Private Share");
|
|
52
|
+
await expect(heading).toBeVisible();
|
|
53
|
+
|
|
54
|
+
// no .card-tags element rendered (or it has no children) when tags is empty
|
|
55
|
+
const cardTags = canvasElement.querySelector(".card-tags");
|
|
56
|
+
await expect(cardTags).toBeNull();
|
|
57
|
+
}} />
|
|
58
|
+
|
|
59
|
+
<Story name="As Div" args={{ as: "div", slug: "zod-fixture", title: "zod-fixture", description: "Creating fixtures based on zod schemas automatically.", tags: ["TypeScript", "Open Source"], ctaLabel: "OPEN SOURCE" }}
|
|
60
|
+
play={async ({ canvasElement }) => {
|
|
61
|
+
const root = canvasElement.firstElementChild as HTMLElement;
|
|
62
|
+
await expect(root.tagName).toBe("DIV");
|
|
63
|
+
await expect(root).toBeVisible();
|
|
64
|
+
}} />
|
|
65
|
+
|
|
66
|
+
<Story name="Many Tags" args={{ href: "#many-tags", slug: "many-tags", title: "Many Tags Project", description: "A project with many technology tags.", tags: ["TypeScript", "Open Source", "SvelteKit", "Rust", "Embedded", "Hardware"] }}
|
|
67
|
+
play={async ({ canvasElement }) => {
|
|
68
|
+
const canvas = within(canvasElement);
|
|
69
|
+
await expect(canvas.getByText("TypeScript")).toBeVisible();
|
|
70
|
+
await expect(canvas.getByText("Open Source")).toBeVisible();
|
|
71
|
+
await expect(canvas.getByText("SvelteKit")).toBeVisible();
|
|
72
|
+
await expect(canvas.getByText("Rust")).toBeVisible();
|
|
73
|
+
await expect(canvas.getByText("Embedded")).toBeVisible();
|
|
74
|
+
await expect(canvas.getByText("Hardware")).toBeVisible();
|
|
75
|
+
await expect(canvasElement.querySelector(".card-tags")).not.toBeNull();
|
|
76
|
+
await expect(canvasElement.querySelectorAll(".card-tags span").length).toBeGreaterThanOrEqual(6);
|
|
77
|
+
}} />
|
|
78
|
+
|
|
79
|
+
<Story name="No Description" args={{ href: "#no-desc", slug: "no-desc", title: "No Description Project", description: "" }}
|
|
80
|
+
play={async ({ canvasElement }) => {
|
|
81
|
+
const canvas = within(canvasElement);
|
|
82
|
+
const root = canvasElement.firstElementChild;
|
|
83
|
+
await expect(root).toBeVisible();
|
|
84
|
+
await expect(canvas.getByText("No Description Project")).toBeVisible();
|
|
85
|
+
const desc = canvasElement.querySelector(".card-desc");
|
|
86
|
+
await expect(desc).not.toBeNull();
|
|
87
|
+
await expect(desc!.textContent!.trim()).toBe("");
|
|
88
|
+
}} />
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import ProjectCard from "./ProjectCard.svelte";
|
|
2
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
3
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
4
|
+
$$bindings?: Bindings;
|
|
5
|
+
} & Exports;
|
|
6
|
+
(internal: unknown, props: {
|
|
7
|
+
$$events?: Events;
|
|
8
|
+
$$slots?: Slots;
|
|
9
|
+
}): Exports & {
|
|
10
|
+
$set?: any;
|
|
11
|
+
$on?: any;
|
|
12
|
+
};
|
|
13
|
+
z_$$bindings?: Bindings;
|
|
14
|
+
}
|
|
15
|
+
declare const ProjectCard: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
+
[evt: string]: CustomEvent<any>;
|
|
17
|
+
}, {}, {}, string>;
|
|
18
|
+
type ProjectCard = InstanceType<typeof ProjectCard>;
|
|
19
|
+
export default ProjectCard;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import Card from './Card.svelte'
|
|
4
|
+
import TagPill from '../primitives/TagPill.svelte'
|
|
5
|
+
import Stack from '../layout/Stack.svelte'
|
|
6
|
+
import Spread from '../layout/Spread.svelte'
|
|
7
|
+
import Inline from '../layout/Inline.svelte'
|
|
8
|
+
import Text from '../primitives/Text.svelte'
|
|
9
|
+
import Heading from '../primitives/Heading.svelte'
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/** HTML element to render as. @default 'a' */
|
|
13
|
+
as?: string
|
|
14
|
+
/** Project slug, shown in the card header. */
|
|
15
|
+
slug: string
|
|
16
|
+
/** Project display title. */
|
|
17
|
+
title: string
|
|
18
|
+
/** Short project description. */
|
|
19
|
+
description: string
|
|
20
|
+
/** Tag labels rendered as TagPills. @default [] */
|
|
21
|
+
tags?: string[]
|
|
22
|
+
/** CTA label in the card footer. @default 'VIEW PROJECT' */
|
|
23
|
+
ctaLabel?: string
|
|
24
|
+
children?: Snippet
|
|
25
|
+
[key: string]: unknown
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
as = 'a',
|
|
30
|
+
slug,
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
tags = [],
|
|
34
|
+
ctaLabel = 'VIEW PROJECT',
|
|
35
|
+
...rest
|
|
36
|
+
}: Props = $props()
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<Card as={as} class="project-card" {...rest}>
|
|
40
|
+
<div class="card-img">
|
|
41
|
+
<Text variant="eyebrow">{slug.toUpperCase()} · PROJECT</Text>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="card-body">
|
|
44
|
+
<Stack gap="xs">
|
|
45
|
+
{#if tags.length > 0}
|
|
46
|
+
<Inline gap="xs" class="card-tags">
|
|
47
|
+
{#each tags as tag}
|
|
48
|
+
<TagPill>{tag}</TagPill>
|
|
49
|
+
{/each}
|
|
50
|
+
</Inline>
|
|
51
|
+
{/if}
|
|
52
|
+
<Heading level={3} size="lg">{title}</Heading>
|
|
53
|
+
<p class="card-desc">{description}</p>
|
|
54
|
+
</Stack>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="card-cta">
|
|
57
|
+
<Spread>
|
|
58
|
+
<Text variant="mono">{ctaLabel}</Text>
|
|
59
|
+
<span aria-hidden="true">→</span>
|
|
60
|
+
</Spread>
|
|
61
|
+
</div>
|
|
62
|
+
</Card>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.card-body {
|
|
66
|
+
padding: 12px 14px 10px;
|
|
67
|
+
flex: 1;
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:global(.project-card) {
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
color: inherit;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.card-img {
|
|
79
|
+
aspect-ratio: 14 / 9;
|
|
80
|
+
background: repeating-linear-gradient(
|
|
81
|
+
135deg,
|
|
82
|
+
var(--bg-sunken) 0 10px,
|
|
83
|
+
var(--bg-elev) 10px 20px
|
|
84
|
+
);
|
|
85
|
+
border-bottom: 1px solid var(--rule);
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.card-desc {
|
|
92
|
+
font-family: var(--mono);
|
|
93
|
+
font-size: var(--t-mono);
|
|
94
|
+
color: var(--ink-dim);
|
|
95
|
+
line-height: 1.4;
|
|
96
|
+
margin: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.card-cta {
|
|
100
|
+
border-top: 1px solid var(--rule);
|
|
101
|
+
padding: 10px 14px;
|
|
102
|
+
transition: background var(--transition), color var(--transition);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:global(.project-card):hover .card-cta {
|
|
106
|
+
background: var(--amber);
|
|
107
|
+
color: var(--bg);
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** HTML element to render as. @default 'a' */
|
|
4
|
+
as?: string;
|
|
5
|
+
/** Project slug, shown in the card header. */
|
|
6
|
+
slug: string;
|
|
7
|
+
/** Project display title. */
|
|
8
|
+
title: string;
|
|
9
|
+
/** Short project description. */
|
|
10
|
+
description: string;
|
|
11
|
+
/** Tag labels rendered as TagPills. @default [] */
|
|
12
|
+
tags?: string[];
|
|
13
|
+
/** CTA label in the card footer. @default 'VIEW PROJECT' */
|
|
14
|
+
ctaLabel?: string;
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
declare const ProjectCard: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type ProjectCard = ReturnType<typeof ProjectCard>;
|
|
20
|
+
export default ProjectCard;
|