@hubpav/hwio-ui 0.7.7 → 0.7.9
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/package.json +6 -5
- package/src/component-sources.mjs +125 -0
- package/src/components/HWioChoiceCards.astro +1 -1
- package/src/components/HWioContactSection.astro +1 -1
- package/src/components/HWioFeatureGrid.astro +1 -1
- package/src/components/HWioFooter.astro +2 -2
- package/src/components/HWioHeroSplit.astro +1 -1
- package/src/components/HWioHubSpotForm.astro +1 -1
- package/src/components/HWioPricingTiers.astro +1 -1
- package/src/components/HWioProcessSteps.astro +1 -1
- package/src/components/HWioSectionHeader.astro +1 -1
- package/src/components/HWioStatsBand.astro +1 -1
- package/src/integration.d.mts +6 -0
- package/src/integration.mjs +9 -5
- package/src/styles/styles.css +33 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubpav/hwio-ui",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.9",
|
|
4
4
|
"description": "HARDWARIO web design system for Astro: DaisyUI 5 themes generated from the brand tokens, the Tailwind entry stylesheet and the HWio* components.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
"./html": "./src/html.ts"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
|
-
"build": "node scripts/build-themes.mjs"
|
|
31
|
+
"build": "node scripts/build-themes.mjs",
|
|
32
|
+
"test": "node --test \"test/**/*.test.mjs\""
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@hubpav/hwio-brand": "0.7.
|
|
35
|
-
"@hubpav/hwio-web-runtime": "0.7.
|
|
36
|
-
"@hubpav/hwio-web-worker": "0.7.
|
|
35
|
+
"@hubpav/hwio-brand": "0.7.9",
|
|
36
|
+
"@hubpav/hwio-web-runtime": "0.7.9",
|
|
37
|
+
"@hubpav/hwio-web-worker": "0.7.9",
|
|
37
38
|
"daisyui": "5.7.47"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Tailwind scans the package's components through `@source "../components"` in styles.css, so a
|
|
2
|
+
// page used to carry DaisyUI and utility rules for every HWio component, rendered or not. The
|
|
3
|
+
// Vite plugin below appends `@source not` lines for the components a site never imports to the
|
|
4
|
+
// site's entry stylesheet (the one that imports `@hubpav/hwio-ui/styles.css`). It keeps every
|
|
5
|
+
// component when it cannot be sure: no import found, or an import it cannot read literally.
|
|
6
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
7
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
export const HWIO_COMPONENTS_DIR = fileURLToPath(new URL('./components/', import.meta.url));
|
|
11
|
+
|
|
12
|
+
const ENTRY_IMPORT = /@import\s+(?:url\()?["']@hubpav\/hwio-ui\/styles\.css["']/;
|
|
13
|
+
const LITERAL_IMPORT = /@hubpav\/hwio-ui\/components\/(HWio[A-Za-z0-9]+)\.astro/g;
|
|
14
|
+
const ANY_IMPORT = /@hubpav\/hwio-ui\/components\//g;
|
|
15
|
+
const LOCAL_IMPORT = /from\s+["']\.\/(HWio[A-Za-z0-9]+)\.astro["']/g;
|
|
16
|
+
// Plain Markdown cannot import a component; MDX can.
|
|
17
|
+
const SCAN_EXT = /\.(astro|[cm]?[jt]sx?|mdx|svelte|vue)$/;
|
|
18
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', '.astro', '.git', '.wrangler', 'public']);
|
|
19
|
+
|
|
20
|
+
/** Component name -> the components it imports, read from the package's own sources. */
|
|
21
|
+
export function hwioComponentGraph(dir = HWIO_COMPONENTS_DIR) {
|
|
22
|
+
const graph = new Map();
|
|
23
|
+
for (const file of readdirSync(dir)) {
|
|
24
|
+
if (!file.endsWith('.astro')) continue;
|
|
25
|
+
const source = readFileSync(join(dir, file), 'utf8');
|
|
26
|
+
graph.set(file.slice(0, -'.astro'.length), new Set(Array.from(source.matchAll(LOCAL_IMPORT), (m) => m[1])));
|
|
27
|
+
}
|
|
28
|
+
return graph;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The components to leave out of the scan, given the text of every file that may import one.
|
|
33
|
+
* Returns null (scan everything) when nothing imports a component or an import is not a literal
|
|
34
|
+
* `@hubpav/hwio-ui/components/HWioName.astro` path (a computed path could name any of them).
|
|
35
|
+
*/
|
|
36
|
+
export function hwioUnusedComponents(texts, graph = hwioComponentGraph()) {
|
|
37
|
+
const used = new Set();
|
|
38
|
+
for (const text of texts) {
|
|
39
|
+
const literal = Array.from(text.matchAll(LITERAL_IMPORT), (m) => m[1]);
|
|
40
|
+
if (Array.from(text.matchAll(ANY_IMPORT)).length !== literal.length) return null;
|
|
41
|
+
literal.forEach((name) => used.add(name));
|
|
42
|
+
}
|
|
43
|
+
if (!used.size || [...used].some((name) => !graph.has(name))) return null;
|
|
44
|
+
const stack = [...used];
|
|
45
|
+
while (stack.length) {
|
|
46
|
+
for (const dep of graph.get(stack.pop()) ?? []) {
|
|
47
|
+
if (!used.has(dep)) { used.add(dep); stack.push(dep); }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...graph.keys()].filter((name) => !used.has(name)).sort();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function walk(dir, out) {
|
|
54
|
+
let entries;
|
|
55
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return out; }
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
58
|
+
const path = join(dir, entry.name);
|
|
59
|
+
if (entry.isDirectory()) walk(path, out);
|
|
60
|
+
else if (entry.isFile() && SCAN_EXT.test(entry.name)) out.push(path);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Real directory of an installed dependency, looked up the way Node does (walking up). */
|
|
66
|
+
function packageDir(name, from) {
|
|
67
|
+
for (let dir = from; ; dir = dirname(dir)) {
|
|
68
|
+
const candidate = join(dir, 'node_modules', name);
|
|
69
|
+
if (existsSync(candidate)) return realpathSync(candidate);
|
|
70
|
+
if (dirname(dir) === dir) return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Where a site's component imports can live: the project, every local (workspace or `file:`)
|
|
76
|
+
* dependency, and every directory the entry stylesheet itself registers with `@source`.
|
|
77
|
+
*/
|
|
78
|
+
export function hwioScanRoots(root, cssFile, css) {
|
|
79
|
+
const roots = new Set([root]);
|
|
80
|
+
try {
|
|
81
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
82
|
+
for (const name of Object.keys({ ...pkg.dependencies, ...pkg.devDependencies })) {
|
|
83
|
+
if (name.startsWith('@hubpav/hwio-')) continue; // the design system itself (linked in its own workspace)
|
|
84
|
+
const dir = packageDir(name, root);
|
|
85
|
+
if (dir && !dir.split(sep).includes('node_modules')) roots.add(dir);
|
|
86
|
+
}
|
|
87
|
+
} catch { /* no package.json: the project alone */ }
|
|
88
|
+
for (const m of css.matchAll(/@source\s+(?!not\b)(?!inline\()["']([^"']+)["']/g)) {
|
|
89
|
+
const dir = resolve(dirname(cssFile), m[1]);
|
|
90
|
+
if (!dir.split(sep).includes('node_modules') && existsSync(dir)) roots.add(statSync(dir).isDirectory() ? dir : dirname(dir));
|
|
91
|
+
}
|
|
92
|
+
return [...roots];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Vite plugin: a `load` hook, so it works whatever order the Tailwind plugin runs in (Tailwind
|
|
97
|
+
* transforms the entry stylesheet after it is loaded and reads its @imports from disk itself).
|
|
98
|
+
* @param {{ root: string, logger?: { info: (msg: string) => void } }} options
|
|
99
|
+
*/
|
|
100
|
+
export function hwioComponentSourcesPlugin({ root, logger }) {
|
|
101
|
+
const graph = hwioComponentGraph();
|
|
102
|
+
const reported = new Set();
|
|
103
|
+
return {
|
|
104
|
+
name: '@hubpav/hwio-ui:component-sources',
|
|
105
|
+
enforce: 'pre',
|
|
106
|
+
load(id) {
|
|
107
|
+
const file = id.split('?')[0];
|
|
108
|
+
if (!file.endsWith('.css') || file.split(sep).includes('node_modules') || !existsSync(file)) return null;
|
|
109
|
+
const css = readFileSync(file, 'utf8');
|
|
110
|
+
if (!ENTRY_IMPORT.test(css)) return null;
|
|
111
|
+
const files = [...new Set(hwioScanRoots(root, file, css).flatMap((dir) => walk(dir, [])))];
|
|
112
|
+
for (const f of files) this.addWatchFile?.(f);
|
|
113
|
+
const unused = hwioUnusedComponents(files.map((f) => readFileSync(f, 'utf8')), graph);
|
|
114
|
+
if (!reported.has(file)) {
|
|
115
|
+
reported.add(file);
|
|
116
|
+
logger?.info(unused
|
|
117
|
+
? `Tailwind scans ${graph.size - unused.length} of ${graph.size} HWio components (${relative(root, file)})`
|
|
118
|
+
: `Tailwind scans all ${graph.size} HWio components (${relative(root, file)})`);
|
|
119
|
+
}
|
|
120
|
+
if (!unused?.length) return null;
|
|
121
|
+
const lines = unused.map((name) => `@source not "${relative(dirname(file), join(HWIO_COMPONENTS_DIR, `${name}.astro`)).split(sep).join('/')}";`);
|
|
122
|
+
return `${css}\n/* @hubpav/hwio-ui: components this site never imports */\n${lines.join('\n')}\n`;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -6,7 +6,7 @@ import HWioIcon from './HWioIcon.astro';
|
|
|
6
6
|
interface Choice { label: string; title: string; text: string; points?: string[]; cta: HWioCta }
|
|
7
7
|
interface Props { items: Choice[]; lead?: number; class?: string }
|
|
8
8
|
const { items, lead = 0, class: className = '' } = Astro.props;
|
|
9
|
-
const COLS = { 1: 'grid gap-6', 2: 'grid gap-6 md:grid-cols-2', 3: 'grid gap-6 md:grid-cols-3' } as const;
|
|
9
|
+
const COLS = { 1: 'grid grid-cols-1 gap-6', 2: 'grid grid-cols-1 gap-6 md:grid-cols-2', 3: 'grid grid-cols-1 gap-6 md:grid-cols-3' } as const;
|
|
10
10
|
const cols = COLS[(Math.min(Math.max(items.length, 1), 3) as 1 | 2 | 3)];
|
|
11
11
|
---
|
|
12
12
|
<div class:list={[cols, 'mx-auto max-w-[65rem]', className]}>
|
|
@@ -16,7 +16,7 @@ interface Props {
|
|
|
16
16
|
const { id = 'contact', eyebrow, title, lead, person, tone = 'alt', class: className = '' } = Astro.props;
|
|
17
17
|
---
|
|
18
18
|
<HWioSection id={id} tone={tone} class={className}>
|
|
19
|
-
<div class="grid gap-12 lg:grid-cols-[
|
|
19
|
+
<div class="grid grid-cols-1 gap-12 lg:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]">
|
|
20
20
|
<div>
|
|
21
21
|
<HWioSectionHeader eyebrow={eyebrow} title={title} lead={lead} align="left" />
|
|
22
22
|
{person && (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import HWioFeatureCard from './HWioFeatureCard.astro';
|
|
3
3
|
interface Props { items: { title: string; text: string; icon?: string; href?: string }[]; columns?: 2 | 3 | 4; class?: string }
|
|
4
|
-
const COLS = { 2: 'grid gap-6 md:grid-cols-2', 3: 'grid gap-6 md:grid-cols-2 lg:grid-cols-3', 4: 'grid gap-6 md:grid-cols-2 lg:grid-cols-4' } as const;
|
|
4
|
+
const COLS = { 2: 'grid grid-cols-1 gap-6 md:grid-cols-2', 3: 'grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3', 4: 'grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4' } as const;
|
|
5
5
|
const { items, columns = 3, class: className = '' } = Astro.props;
|
|
6
6
|
---
|
|
7
7
|
<div class:list={[COLS[columns], className]}>
|
|
@@ -37,8 +37,8 @@ const linkClass = 'inline-flex items-center gap-1 text-neutral-content/80 transi
|
|
|
37
37
|
|
|
38
38
|
<div class="container py-12 lg:py-16">
|
|
39
39
|
{/* Long compound words (DE "Datenschutzerklärung", "Rechtsdokumente") are wider than a column on
|
|
40
|
-
phones and at 1024 px: hyphenate them
|
|
41
|
-
<div class="grid grid-cols-2 gap-8
|
|
40
|
+
phones and at 1024 px: hyphenate them; words under 10 letters (brand names) stay whole. */}
|
|
41
|
+
<div class="grid grid-cols-2 gap-8 hwio-hyphenate md:grid-cols-3 lg:grid-cols-6">
|
|
42
42
|
<div class="hidden md:col-span-3 md:flex lg:col-span-1 lg:block">
|
|
43
43
|
<slot name="logo" />
|
|
44
44
|
</div>
|
|
@@ -8,7 +8,7 @@ const { id, eyebrow, title, accent, lead, actions = [], checklist = [], reverse
|
|
|
8
8
|
const VARIANT = { primary: 'primary', secondary: 'secondary', ghost: 'ghost' } as const;
|
|
9
9
|
---
|
|
10
10
|
<section id={id} class:list={['bg-base-100 text-base-content', className]}>
|
|
11
|
-
<div class:list={['container grid items-center gap-12 py-16 lg:grid-cols-[
|
|
11
|
+
<div class:list={['container grid grid-cols-1 items-center gap-12 py-16 lg:grid-cols-[minmax(0,7fr)_minmax(0,5fr)] lg:gap-16 lg:py-24', reverse ? 'lg:[&>*:first-child]:order-2' : '']}>
|
|
12
12
|
<div>
|
|
13
13
|
{eyebrow && <p class="hwio-kicker mb-5">{eyebrow}</p>}
|
|
14
14
|
<h1><slot name="title"><span class="block">{title}</span>{accent && <span class="block text-accent">{accent}</span>}</slot></h1>
|
|
@@ -17,7 +17,7 @@ interface Props {
|
|
|
17
17
|
class?: string;
|
|
18
18
|
}
|
|
19
19
|
const { form, fields, labels, locale, turnstileSiteKey, layout = 'grid', class: className = '' } = Astro.props;
|
|
20
|
-
const LAYOUT = { grid: 'grid gap-4 md:grid-cols-2', inline: 'flex flex-col gap-3 sm:flex-row sm:items-end' } as const;
|
|
20
|
+
const LAYOUT = { grid: 'grid grid-cols-1 gap-4 md:grid-cols-2', inline: 'flex flex-col gap-3 sm:flex-row sm:items-end' } as const;
|
|
21
21
|
---
|
|
22
22
|
<form
|
|
23
23
|
class:list={['hwio-form', className]}
|
|
@@ -5,7 +5,7 @@ import HWioIcon from './HWioIcon.astro';
|
|
|
5
5
|
interface Tier { eyebrow?: string; name: string; price?: string; pricePrefix?: string; period?: string; description?: string; features: string[]; cta?: HWioCta; featured?: boolean; badge?: string }
|
|
6
6
|
interface Props { tiers: Tier[]; class?: string }
|
|
7
7
|
const { tiers, class: className = '' } = Astro.props;
|
|
8
|
-
const COLS = { 1: 'grid gap-6', 2: 'grid gap-6 md:grid-cols-2', 3: 'grid gap-6 md:grid-cols-3', 4: 'grid gap-6 md:grid-cols-2 lg:grid-cols-4' } as const;
|
|
8
|
+
const COLS = { 1: 'grid grid-cols-1 gap-6', 2: 'grid grid-cols-1 gap-6 md:grid-cols-2', 3: 'grid grid-cols-1 gap-6 md:grid-cols-3', 4: 'grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4' } as const;
|
|
9
9
|
const cols = (Math.min(Math.max(tiers.length, 1), 4) as 1 | 2 | 3 | 4);
|
|
10
10
|
---
|
|
11
11
|
<div class:list={[COLS[cols], className]}>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/** `number` overrides the rendered ordinal (sites whose copy numbers steps "01", "02"…). */
|
|
4
4
|
interface Props { items: { title: string; text: string; number?: string }[]; orientation?: 'horizontal' | 'vertical'; class?: string }
|
|
5
5
|
const { items, orientation = 'horizontal', class: className = '' } = Astro.props;
|
|
6
|
-
const LAYOUT = { horizontal: 'grid gap-8 md:grid-cols-2 lg:grid-cols-4', vertical: 'grid gap-8' } as const;
|
|
6
|
+
const LAYOUT = { horizontal: 'grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4', vertical: 'grid grid-cols-1 gap-8' } as const;
|
|
7
7
|
---
|
|
8
8
|
<ol class:list={[LAYOUT[orientation], className]} data-hwio-steps>
|
|
9
9
|
{items.map((step, i) => (
|
|
@@ -6,7 +6,7 @@ const { eyebrow, title, lead, align = 'center', layout = 'stack', level = 2, cla
|
|
|
6
6
|
const Heading = `h${level}` as 'h1' | 'h2' | 'h3';
|
|
7
7
|
---
|
|
8
8
|
{layout === 'split' ? (
|
|
9
|
-
<div class:list={['grid gap-8 lg:grid-cols-[
|
|
9
|
+
<div class:list={['grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:items-end', className]}>
|
|
10
10
|
<div>
|
|
11
11
|
{eyebrow && <p class="hwio-kicker mb-4">{eyebrow}</p>}
|
|
12
12
|
<Heading><slot name="title">{title}</slot></Heading>
|
|
@@ -6,7 +6,7 @@ const COLS = { 2: 'sm:grid-cols-2', 3: 'sm:grid-cols-3', 4: 'sm:grid-cols-2 lg:g
|
|
|
6
6
|
const cols = COLS[(Math.min(Math.max(items.length, 2), 5) as 2 | 3 | 4 | 5)];
|
|
7
7
|
---
|
|
8
8
|
<section id={id} class:list={['border-y border-base-300 bg-base-100 text-base-content', className]}>
|
|
9
|
-
<div class:list={['container grid py-9', cols]}>
|
|
9
|
+
<div class:list={['container grid grid-cols-1 py-9', cols]}>
|
|
10
10
|
{items.map((s, i) => {
|
|
11
11
|
const numeric = typeof s.value === 'number';
|
|
12
12
|
return (
|
package/src/integration.d.mts
CHANGED
|
@@ -7,5 +7,11 @@ export interface HWioUiOptions {
|
|
|
7
7
|
assets?: boolean;
|
|
8
8
|
/** Font families copied into /fonts (file-name prefixes in @hubpav/hwio-brand/dist/fonts). Default ['inter']. */
|
|
9
9
|
fonts?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* 'used' (default): Tailwind scans only the HWio components the site imports (found in the project,
|
|
12
|
+
* its local workspace dependencies and the entry stylesheet's own `@source` directories), so pages
|
|
13
|
+
* carry no rules for components they never render. 'all': scan every component.
|
|
14
|
+
*/
|
|
15
|
+
componentCss?: 'used' | 'all';
|
|
10
16
|
}
|
|
11
17
|
export function hwioUi(options?: HWioUiOptions): AstroIntegration;
|
package/src/integration.mjs
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
// integrations: [hwioUi({ headers: { gtm: true, turnstile: true, submitEndpoint: 'https://submit.hardwario.com', earlyHints: ['/fonts/inter-latin.woff2', '/fonts/inter-latin-ext.woff2'] } })]
|
|
4
4
|
// It (1) forces component scripts to stay external (script-src 'self' covers them), (2) serves the
|
|
5
5
|
// brand fonts and logos in dev and copies them into the build output, (3) writes `_headers`
|
|
6
|
-
// from one options object so security headers have a single source of truth
|
|
6
|
+
// from one options object so security headers have a single source of truth, (4) limits
|
|
7
|
+
// Tailwind's scan of the package components to the ones the site imports (component-sources.mjs).
|
|
7
8
|
import { copyFileSync, mkdirSync, readdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
8
9
|
import { join, dirname, extname } from 'node:path';
|
|
9
10
|
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { createRequire } from 'node:module';
|
|
11
12
|
import { hwioRenderHeadersFile } from '@hubpav/hwio-web-worker/headers';
|
|
13
|
+
import { hwioComponentSourcesPlugin } from './component-sources.mjs';
|
|
12
14
|
|
|
13
15
|
const require = createRequire(import.meta.url);
|
|
14
16
|
|
|
@@ -19,20 +21,22 @@ function brandDir() {
|
|
|
19
21
|
const MIME = { '.woff2': 'font/woff2', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon' };
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
* @param {{ headers?: import('@hubpav/hwio-web-worker/headers').HWioHeadersOptions | false, assets?: boolean, fonts?: string[] }} [options]
|
|
24
|
+
* @param {{ headers?: import('@hubpav/hwio-web-worker/headers').HWioHeadersOptions | false, assets?: boolean, fonts?: string[], componentCss?: 'used' | 'all' }} [options]
|
|
23
25
|
* fonts: font families (file-name prefixes in @hubpav/hwio-brand/dist/fonts) copied to /fonts; default ['inter'].
|
|
26
|
+
* componentCss: 'used' (default) emits CSS for the components the site imports, 'all' for every component.
|
|
24
27
|
* @returns {import('astro').AstroIntegration}
|
|
25
28
|
*/
|
|
26
29
|
export function hwioUi(options = {}) {
|
|
27
|
-
const { headers = false, assets = true, fonts = ['inter'] } = options;
|
|
30
|
+
const { headers = false, assets = true, fonts = ['inter'], componentCss = 'used' } = options;
|
|
28
31
|
const brand = brandDir();
|
|
29
32
|
const fontsDir = join(brand, 'dist/fonts');
|
|
30
33
|
const logosDir = join(brand, 'logos');
|
|
31
34
|
return {
|
|
32
35
|
name: '@hubpav/hwio-ui',
|
|
33
36
|
hooks: {
|
|
34
|
-
'astro:config:setup': ({ updateConfig }) => {
|
|
35
|
-
|
|
37
|
+
'astro:config:setup': ({ updateConfig, config, logger }) => {
|
|
38
|
+
const plugins = componentCss === 'used' ? [hwioComponentSourcesPlugin({ root: fileURLToPath(config.root), logger })] : [];
|
|
39
|
+
updateConfig({ vite: { build: { assetsInlineLimit: 0 }, plugins } });
|
|
36
40
|
},
|
|
37
41
|
'astro:server:setup': ({ server }) => {
|
|
38
42
|
if (!assets) return;
|
package/src/styles/styles.css
CHANGED
|
@@ -20,6 +20,19 @@
|
|
|
20
20
|
--font-sans: var(--hwio-font-sans);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/* Long words (CS/DE/PL compounds) hyphenate instead of overflowing a narrow column. At least five
|
|
24
|
+
letters on each side of the hyphen, so no word under 10 letters ever splits; this keeps the brand
|
|
25
|
+
names whole (HARDWARIO, ProXimos, CHESTER) in Safari too, which ignores hyphenate-limit-chars and
|
|
26
|
+
reads only the -webkit- limits. A word no browser can hyphenate still breaks rather than widening
|
|
27
|
+
its grid or flex parent (`anywhere` lowers min-content). */
|
|
28
|
+
@utility hwio-hyphenate {
|
|
29
|
+
overflow-wrap: anywhere;
|
|
30
|
+
hyphens: auto;
|
|
31
|
+
hyphenate-limit-chars: 10 5 5;
|
|
32
|
+
-webkit-hyphenate-limit-before: 5;
|
|
33
|
+
-webkit-hyphenate-limit-after: 5;
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/* One container for the fleet; width comes from the theme (hub) or the site (landing).
|
|
24
37
|
Unlayered on purpose: Tailwind's built-in `container` utility caps max-width at each
|
|
25
38
|
breakpoint (40/48/64/80/96rem) and would otherwise win over the theme's width. */
|
|
@@ -68,6 +81,16 @@
|
|
|
68
81
|
@media (width >= 64rem) {
|
|
69
82
|
h1 { font-size: 3.5rem; }
|
|
70
83
|
}
|
|
84
|
+
/* Phones: a heading word wider than its column (a 40 px H1 gets 218-288 px) hyphenates instead
|
|
85
|
+
of running past the screen, and a token longer than the line (a URL, an ID) breaks. */
|
|
86
|
+
@media (width < 40rem) {
|
|
87
|
+
h1, h2, h3 {
|
|
88
|
+
@apply hwio-hyphenate;
|
|
89
|
+
}
|
|
90
|
+
main {
|
|
91
|
+
overflow-wrap: break-word;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
71
94
|
:focus-visible {
|
|
72
95
|
outline: 2px solid var(--color-secondary);
|
|
73
96
|
outline-offset: 2px;
|
|
@@ -83,6 +106,16 @@
|
|
|
83
106
|
}
|
|
84
107
|
|
|
85
108
|
@layer components {
|
|
109
|
+
/* Phones: a button label wider than its column wraps onto a second line instead of pushing the
|
|
110
|
+
page sideways; one-line buttons keep their height. */
|
|
111
|
+
@media (width < 40rem) {
|
|
112
|
+
.btn {
|
|
113
|
+
max-width: 100%;
|
|
114
|
+
height: auto;
|
|
115
|
+
min-height: var(--size);
|
|
116
|
+
padding-block: 0.375rem;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
86
119
|
/* Rhythm (direction A): 80 / 112 px sections, 40 / 48 px compact bands. */
|
|
87
120
|
.hwio-section { padding-block: calc(var(--spacing) * 20); }
|
|
88
121
|
.hwio-section-compact { padding-block: calc(var(--spacing) * 10); }
|