ab-nextjs-components 0.1.9 โ 0.1.11
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/CHANGELOG.md +36 -0
- package/ab-brand/index.tsx +30 -1
- package/ab-brand/styles.module.css +40 -1
- package/ab-button/helpers.ts +90 -0
- package/ab-button/index.tsx +164 -87
- package/ab-button/styles.module.css +79 -1
- package/ab-carousel/helpers.ts +146 -0
- package/ab-carousel/index.tsx +643 -0
- package/ab-carousel/styles.module.css +206 -0
- package/ab-chip/index.tsx +75 -0
- package/ab-chip/styles.module.css +87 -0
- package/ab-collapsible/index.tsx +30 -1
- package/ab-collapsible/styles.module.css +40 -1
- package/ab-flow-diagram/helpers.ts +77 -0
- package/ab-flow-diagram/index.tsx +152 -0
- package/ab-flow-diagram/styles.module.css +158 -0
- package/ab-icon/builtin.ts +61 -0
- package/ab-icon/helpers.ts +102 -0
- package/ab-icon/index.tsx +77 -16
- package/ab-icon/styles.module.css +53 -9
- package/ab-icon-button/index.tsx +110 -46
- package/ab-icon-button/styles.module.css +28 -10
- package/ab-install-snippet/helpers.ts +62 -0
- package/ab-install-snippet/index.tsx +170 -0
- package/ab-install-snippet/styles.module.css +104 -0
- package/ab-like-button/index.tsx +33 -1
- package/ab-like-button/styles.module.css +41 -6
- package/ab-linear-progress/index.tsx +5 -1
- package/ab-linear-progress/styles.module.css +8 -24
- package/ab-locale-switcher/index.tsx +213 -0
- package/ab-locale-switcher/styles.module.css +127 -0
- package/ab-pill/index.tsx +74 -0
- package/ab-progress-chips/index.tsx +30 -1
- package/ab-progress-chips/styles.module.css +40 -1
- package/ab-searchbar/index.tsx +33 -2
- package/ab-searchbar/styles.module.css +41 -5
- package/ab-splash/index.tsx +3 -1
- package/ab-splash/styles.module.css +2 -5
- package/ab-theme-toggle/index.tsx +188 -0
- package/ab-theme-toggle/styles.module.css +89 -0
- package/css/dialog.css +38 -3
- package/index.ts +41 -0
- package/package.json +33 -17
- package/server/ab-brand/index.tsx +29 -1
- package/server/ab-brand/styles.module.css +40 -1
- package/server/ab-button/index.tsx +96 -79
- package/server/ab-button/styles.module.css +79 -1
- package/server/ab-icon/index.tsx +68 -16
- package/server/ab-icon/styles.module.css +53 -9
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license MIT
|
|
3
|
+
* ~~~~~~~~~~~~
|
|
4
|
+
* ab-nextjs-components
|
|
5
|
+
* ~~~~~~~~~~~~
|
|
6
|
+
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the 'Software'), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*
|
|
26
|
+
* @project: ab-nextjs-components
|
|
27
|
+
* @name: AbFlowDiagram - Server (G38, Welcome illustrations)
|
|
28
|
+
* @file: ab-flow-diagram/index.tsx
|
|
29
|
+
* @type: Server component โ animated node/wire scene, zero JS
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*
|
|
32
|
+
* Example usage:
|
|
33
|
+
* 1+|> <AbFlowDiagram title="Live Playground" caption="ab-nextjs-core" alt={t('s02.illustrationAlt')} active={index === 1}
|
|
34
|
+
* -|> nodes={[{ id: 'hub', label: 'AbProvider', x: 50, y: 50, tone: 'hub' }, { id: 'cfg', label: 'config', x: 20, y: 25 }]} edges={[{ from: 'cfg', to: 'hub' }]} />
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
39
|
+
* MOTTO: We'll always do more ๐!!!
|
|
40
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// โ๏ธ types
|
|
44
|
+
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
45
|
+
// ๐งต classes
|
|
46
|
+
import clsx from 'clsx';
|
|
47
|
+
// ๐งฎ pure geometry
|
|
48
|
+
import { getAbFlowEdgePath, type AbFlowEdge, type AbFlowNode } from './helpers';
|
|
49
|
+
// ๐จ styles (all motion is CSS: draw-on, pulses, reduced motion, pause)
|
|
50
|
+
import styles from './styles.module.css';
|
|
51
|
+
// ๐ฌ wire draw-on + pulse come from ab-nextjs-animations (>= 0.2.4); this component only sets its tokens
|
|
52
|
+
import { AB_WIRE_DRAW_CLASS, AB_WIRE_PULSE_CLASS } from 'ab-nextjs-animations/wire';
|
|
53
|
+
import 'ab-nextjs-animations/wire/styles.css'; // ใฐ๏ธ .ab-wire-draw / .ab-wire-pulse
|
|
54
|
+
|
|
55
|
+
// ๐ฆ re-export helpers + types
|
|
56
|
+
export * from './helpers';
|
|
57
|
+
|
|
58
|
+
// โ๏ธ props
|
|
59
|
+
export interface AbFlowDiagramProps {
|
|
60
|
+
nodes: readonly AbFlowNode[]; // ๐ต nodes in 0โ100 coordinates
|
|
61
|
+
edges: readonly AbFlowEdge[]; // ใฐ๏ธ wires between node ids
|
|
62
|
+
alt: string; // โฟ one-sentence description (Welcome.sNN.illustrationAlt)
|
|
63
|
+
title?: string; // ๐ท๏ธ card title, e.g. "Live Playground" (visual)
|
|
64
|
+
caption?: string; // ๐ท๏ธ PKG label, e.g. "ab-nextjs-core" (visual, mono)
|
|
65
|
+
live?: boolean | string; // ๐ข sage "live" dot; a string sets its visible text
|
|
66
|
+
active?: boolean; // โถ๏ธ animate only while its slide is active (default true)
|
|
67
|
+
loops?: number | 'infinite'; // ๐ pulse loops (default infinite; mobile CSS caps at 3)
|
|
68
|
+
variant?: 'full' | 'compact'; // ๐ compact = mobile 276px scene
|
|
69
|
+
posterAt?: number; // ๐ผ๏ธ 0โ1, frozen pulse position for reduced motion (mock MID = 0.45)
|
|
70
|
+
duration?: number; // โฑ๏ธ pulse loop ms (default 2400)
|
|
71
|
+
className?: string; // ๐ท๏ธ
|
|
72
|
+
style?: CSSProperties; // ๐จ
|
|
73
|
+
children?: ReactNode; // ๐งฉ optional extra overlay (decorative)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// โ๏ธ 0โ100 (same clamp as the wires, so a node and its wire end always agree)
|
|
77
|
+
const pct = (v: number): number => Math.min(100, Math.max(0, v));
|
|
78
|
+
|
|
79
|
+
/** ๐ node position + proportional anchor (CSS keeps every node inside the stage, see styles.module.css) */
|
|
80
|
+
const nodeStyle = (n: AbFlowNode): CSSProperties =>
|
|
81
|
+
({ left: `${pct(n.x)}%`, top: `${pct(n.y)}%`, '--ab-flow-node-x': String(pct(n.x)), '--ab-flow-node-y': String(pct(n.y)) }) as CSSProperties;
|
|
82
|
+
|
|
83
|
+
/** ๐จ Live Playground scene: SVG wires + HTML nodes, CSS-only motion, one accessible image. */
|
|
84
|
+
export function AbFlowDiagram(props: AbFlowDiagramProps): ReactElement {
|
|
85
|
+
// ๐งฉ props
|
|
86
|
+
const { nodes, edges, alt, title, caption, live = false, active = true, loops = 'infinite', variant = 'full', posterAt = 0.45, duration = 2400, className, style, children } = props;
|
|
87
|
+
// ๐บ๏ธ id โ node
|
|
88
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
89
|
+
// ๐๏ธ ab-nextjs-animations wire tokens drive all timing
|
|
90
|
+
const vars = {
|
|
91
|
+
...style, // ๐จ
|
|
92
|
+
'--ab-wire-pulse-duration': `${duration}ms`, // โฑ๏ธ
|
|
93
|
+
// ๐ explicit loops win; default = infinite on laptop, 3 on phones (set in styles.module.css)
|
|
94
|
+
...(loops === 'infinite' ? {} : { '--ab-wire-pulse-iterations': String(loops) }),
|
|
95
|
+
'--ab-wire-poster': String(100 - Math.min(1, Math.max(0, posterAt)) * 100), // ๐ผ๏ธ dashoffset at the poster frame
|
|
96
|
+
} as CSSProperties;
|
|
97
|
+
|
|
98
|
+
// ๐ท๏ธ is there a header row? (drives the header-safe stage inset in CSS)
|
|
99
|
+
const hasHeader = Boolean(title || live || caption);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<figure
|
|
103
|
+
role="img" // โฟ the whole scene is ONE image
|
|
104
|
+
aria-label={alt} // ๐ฃ๏ธ
|
|
105
|
+
className={clsx('AbFlowDiagram', styles.root, className)} // ๐ท๏ธ
|
|
106
|
+
style={vars} // ๐๏ธ
|
|
107
|
+
data-ab-part="flow-diagram" // ๐๏ธ
|
|
108
|
+
data-active={String(active)} // โถ๏ธ / โธ๏ธ
|
|
109
|
+
data-ab-paused={String(!active)} // โธ๏ธ ab-nextjs-animations pauses wires on inactive slides
|
|
110
|
+
data-variant={variant} // ๐
|
|
111
|
+
data-ab-header={String(hasHeader)} // ๐ท๏ธ header-safe stage: nodes + wires start below the header
|
|
112
|
+
>
|
|
113
|
+
{/* ๐ท๏ธ card header (decorative: alt already describes it) */}
|
|
114
|
+
{hasHeader ? (
|
|
115
|
+
<div className={styles.header} aria-hidden="true">
|
|
116
|
+
{title ? <span className={styles.title}>{title}</span> : null}
|
|
117
|
+
{live ? <span className={styles.live}><span className={styles.liveDot} />{typeof live === 'string' ? live : null}</span> : null}
|
|
118
|
+
{caption ? <span className={styles.caption}>{caption}</span> : null}
|
|
119
|
+
</div>
|
|
120
|
+
) : null}
|
|
121
|
+
{/* ใฐ๏ธ wires layer */}
|
|
122
|
+
<svg className={styles.wires} viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true" focusable="false">
|
|
123
|
+
{edges.map((e, i) => {
|
|
124
|
+
const a = byId.get(e.from); // ๐ต
|
|
125
|
+
const b = byId.get(e.to); // ๐ต
|
|
126
|
+
if (!a || !b) return null; // ๐งฏ unknown id: skip, don't crash the slide
|
|
127
|
+
const d = getAbFlowEdgePath(a, b, e.curve); // ใฐ๏ธ
|
|
128
|
+
const delay = { '--ab-wire-index': String(i) } as CSSProperties; // โฑ๏ธ stagger (ab-nextjs-animations)
|
|
129
|
+
return (
|
|
130
|
+
<g key={`${e.from}-${e.to}-${i}`} style={delay}>
|
|
131
|
+
<path className={clsx(styles.wire, AB_WIRE_DRAW_CLASS)} d={d} pathLength={100} />{/* ๐๏ธ draws on */}
|
|
132
|
+
<path className={clsx(styles.pulse, AB_WIRE_PULSE_CLASS)} d={d} pathLength={100} />{/* โจ travelling pulse */}
|
|
133
|
+
</g>
|
|
134
|
+
);
|
|
135
|
+
})}
|
|
136
|
+
</svg>
|
|
137
|
+
{/* ๐ต nodes layer (HTML: crisp text, real fonts) */}
|
|
138
|
+
<div className={styles.nodes} aria-hidden="true">
|
|
139
|
+
{nodes.map((n) => (
|
|
140
|
+
<span key={n.id} className={styles.node} data-tone={n.tone ?? 'node'} style={nodeStyle(n)}>
|
|
141
|
+
{n.icon ? <span className={styles.nodeIcon}>{n.icon}</span> : null}
|
|
142
|
+
<span>{n.label}</span>
|
|
143
|
+
</span>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
{children ? <div className={styles.overlay} aria-hidden="true">{children}</div> : null}
|
|
147
|
+
</figure>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ๐ฆ default export
|
|
152
|
+
export default AbFlowDiagram;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license MIT
|
|
3
|
+
* ~~~~~~~~~~~~
|
|
4
|
+
* ab-nextjs-components
|
|
5
|
+
* ~~~~~~~~~~~~
|
|
6
|
+
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the 'Software'), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*
|
|
26
|
+
* @project: ab-nextjs-components
|
|
27
|
+
* @name: AbFlowDiagram - Styles (G38)
|
|
28
|
+
* @file: ab-flow-diagram/styles.module.css
|
|
29
|
+
* @type: CSS module โ transform/opacity/dash only (CLS 0)
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
35
|
+
* MOTTO: We'll always do more ๐!!!
|
|
36
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/* ๐ scene card (laptop 760ร600 ratio) */
|
|
40
|
+
.root {
|
|
41
|
+
position: relative; /* ๐ layers */
|
|
42
|
+
margin: 0; /* ๐งน figure default */
|
|
43
|
+
width: 100%; /* โ๏ธ */
|
|
44
|
+
aspect-ratio: 760 / 600; /* ๐ mock card */
|
|
45
|
+
border-radius: 1.25rem; /* ๐ฒ */
|
|
46
|
+
border: 1px solid var(--ab-color-outline-variant, rgb(0 0 0 / 0.12)); /* ๐๏ธ */
|
|
47
|
+
background: var(--ab-color-surface-container, transparent); /* ๐จ */
|
|
48
|
+
overflow: hidden; /* โ๏ธ */
|
|
49
|
+
color: var(--ab-color-on-surface, currentColor); /* ๐จ */
|
|
50
|
+
container-type: inline-size; /* ๐ฆ nodes scale with the card width (cqi units below) */
|
|
51
|
+
/* ๐๏ธ stage insets (tunable): where nodes + wires may sit. Top is 0 unless a header renders (below) */
|
|
52
|
+
--ab-flow-diagram-stage-top: 0px;
|
|
53
|
+
--ab-flow-diagram-stage-x: var(--ab-flow-diagram-inset-x, 0.5rem); /* โ๏ธ side margin: nodes never touch the card edge */
|
|
54
|
+
}
|
|
55
|
+
/* ๐ท๏ธ header-safe stage: with a title / live dot / caption, nodes + wires start BELOW the header band, so a
|
|
56
|
+
* node near the top (e.g. "core" at 390px) can't cover the header. Override with --ab-flow-diagram-inset-top */
|
|
57
|
+
.root[data-ab-header='true'] {
|
|
58
|
+
--ab-flow-diagram-stage-top: var(--ab-flow-diagram-inset-top, 2.75rem); /* ๐ 0.875rem header top + ~1.25rem row + gap */
|
|
59
|
+
}
|
|
60
|
+
.root[data-variant='compact'] {
|
|
61
|
+
aspect-ratio: auto; /* ๐ */
|
|
62
|
+
height: 276px; /* ๐ mobile scene */
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ๐ท๏ธ header row */
|
|
66
|
+
.header {
|
|
67
|
+
position: absolute; /* ๐ */
|
|
68
|
+
inset: 0.875rem 1rem auto; /* ๐ top */
|
|
69
|
+
display: flex; /* ๐ */
|
|
70
|
+
align-items: center; /* โ๏ธ */
|
|
71
|
+
gap: 0.5rem; /* ๐งฑ */
|
|
72
|
+
font-size: 0.8125rem; /* ๐ก */
|
|
73
|
+
z-index: 2; /* ๐งฑ */
|
|
74
|
+
}
|
|
75
|
+
.title { font-weight: 600; } /* ๐ช */
|
|
76
|
+
.caption {
|
|
77
|
+
margin-inline-start: auto; /* โก๏ธ end */
|
|
78
|
+
font-family: var(--ab-font-mono, ui-monospace, monospace); /* ๐ค PKG label */
|
|
79
|
+
color: var(--ab-color-bronze-text, #8A6440); /* โฟ small text */
|
|
80
|
+
}
|
|
81
|
+
.live { display: inline-flex; align-items: center; gap: 0.25rem; } /* ๐ข */
|
|
82
|
+
.liveDot {
|
|
83
|
+
width: 0.5rem; height: 0.5rem; border-radius: 50%; /* โช */
|
|
84
|
+
background: var(--ab-color-sage, #7C9A7E); /* ๐ฟ */
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ใฐ๏ธ wires layer */
|
|
88
|
+
.wires {
|
|
89
|
+
position: absolute; /* ๐ */
|
|
90
|
+
inset: var(--ab-flow-diagram-stage-top) var(--ab-flow-diagram-stage-x) 0; /* ๐ the stage (below the header, inside the side margins) */
|
|
91
|
+
width: calc(100% - 2 * var(--ab-flow-diagram-stage-x)); /* โ๏ธ same box as .nodes */
|
|
92
|
+
height: calc(100% - var(--ab-flow-diagram-stage-top)); /* โ๏ธ same box as .nodes, so wires meet the nodes */
|
|
93
|
+
overflow: visible; /* ๐ซ clip */
|
|
94
|
+
}
|
|
95
|
+
.wire,
|
|
96
|
+
.pulse {
|
|
97
|
+
fill: none; /* ๐ซฅ */
|
|
98
|
+
vector-effect: non-scaling-stroke; /* ๐ stroke stays crisp with preserveAspectRatio="none" */
|
|
99
|
+
stroke-linecap: round; /* โช */
|
|
100
|
+
}
|
|
101
|
+
.wire {
|
|
102
|
+
stroke: color-mix(in srgb, currentColor 28%, transparent); /* ๐ซ๏ธ */
|
|
103
|
+
stroke-width: 1.5; /* ๐ px */
|
|
104
|
+
/* ๐๏ธ draw-on = .ab-wire-draw (ab-nextjs-animations) */
|
|
105
|
+
}
|
|
106
|
+
.pulse {
|
|
107
|
+
stroke: var(--ab-color-bronze, #A67C52); /* ๐ฅ */
|
|
108
|
+
stroke-width: 3; /* ๐ */
|
|
109
|
+
/* โจ dash, reveal + travel = .ab-wire-pulse (ab-nextjs-animations) */
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ๐ต nodes layer */
|
|
113
|
+
.nodes { position: absolute; inset: var(--ab-flow-diagram-stage-top) var(--ab-flow-diagram-stage-x) 0; } /* ๐ same stage as .wires */
|
|
114
|
+
.node {
|
|
115
|
+
position: absolute; /* ๐ x/y */
|
|
116
|
+
/* ๐ฏ orbit that scales with the box: each node is anchored at the SAME fraction of its own box as its
|
|
117
|
+
* position in the stage (x = 8 โ 8% of the pill sits left of the point, x = 92 โ 92%). So the outer edge
|
|
118
|
+
* of the orbit pulls in by exactly the node's width: a side node can never cross the card edge, at any
|
|
119
|
+
* box size, and the wire end (the point) is always inside the pill. x = y = 50 is the old centring. */
|
|
120
|
+
transform: translate(calc(var(--ab-flow-node-x, 50) * -1%), calc(var(--ab-flow-node-y, 50) * -1%));
|
|
121
|
+
max-width: 100%; /* ๐ an over-long label can't be wider than the stageโฆ */
|
|
122
|
+
overflow: hidden; /* โ๏ธ โฆit ellipsises instead */
|
|
123
|
+
text-overflow: ellipsis; /* โฆ */
|
|
124
|
+
display: inline-flex; /* ๐ */
|
|
125
|
+
align-items: center; /* โ๏ธ */
|
|
126
|
+
gap: 0.375rem; /* ๐งฑ */
|
|
127
|
+
/* ๐ scales with the card (cqi = 1% of its width): 760px card โ 13px text, 390px โ 11px; clamped both ways */
|
|
128
|
+
padding: clamp(0.25rem, 0.8cqi, 0.375rem) clamp(0.4375rem, 1.4cqi, 0.625rem); /* ๐งฑ */
|
|
129
|
+
border-radius: clamp(0.5rem, 1.4cqi, 0.625rem); /* ๐ฒ */
|
|
130
|
+
border: 1px solid var(--ab-color-outline-variant, rgb(0 0 0 / 0.12)); /* ๐๏ธ */
|
|
131
|
+
background: var(--ab-color-surface, #fff); /* ๐จ */
|
|
132
|
+
font-size: clamp(0.6875rem, 1.75cqi, 0.8125rem); /* ๐ก */
|
|
133
|
+
white-space: nowrap; /* ๐ซ wrap */
|
|
134
|
+
}
|
|
135
|
+
.node[data-tone='hub'] {
|
|
136
|
+
padding: clamp(0.4375rem, 1.4cqi, 0.625rem) clamp(0.625rem, 1.9cqi, 0.875rem); /* ๐งฑ bigger (scales too) */
|
|
137
|
+
border-color: var(--ab-color-bronze, #A67C52); /* ๐ฅ */
|
|
138
|
+
font-weight: 700; /* ๐ช */
|
|
139
|
+
box-shadow: 0 0 0 clamp(3px, 0.8cqi, 6px) color-mix(in srgb, var(--ab-color-bronze, #A67C52) 12%, transparent); /* โจ glow */
|
|
140
|
+
}
|
|
141
|
+
.node[data-tone='accent'] { color: var(--ab-color-bronze-text, #8A6440); } /* โฟ */
|
|
142
|
+
.nodeIcon { display: inline-flex; width: 1.125rem; height: 1.125rem; } /* ๐ผ๏ธ */
|
|
143
|
+
.root[data-variant='compact'] .node { font-size: 0.6875rem; padding: 0.25rem 0.5rem; } /* ๐ฑ */
|
|
144
|
+
.overlay { position: absolute; inset: 0; pointer-events: none; } /* ๐งฉ */
|
|
145
|
+
|
|
146
|
+
/* ๐ฑ๏ธ hover glow on laptop only */
|
|
147
|
+
@media (hover: hover) and (min-width: 1024px) {
|
|
148
|
+
.node:hover { box-shadow: 0 0 0 4px color-mix(in srgb, var(--ab-color-bronze, #A67C52) 18%, transparent); }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* โธ๏ธ inactive slide: paused by ab-nextjs-animations via data-ab-paused */
|
|
152
|
+
|
|
153
|
+
/* ๐ฑ battery: 3 pulse loops on small screens (token only; an explicit `loops` prop wins via inline style) */
|
|
154
|
+
@media (max-width: 639px) {
|
|
155
|
+
.root { --ab-wire-pulse-iterations: 3; } /* ๐ */
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* ๐ง reduced motion: handled by ab-nextjs-animations (wires drawn, pulses frozen at --ab-wire-poster) */
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license MIT
|
|
3
|
+
* ~~~~~~~~~~~~
|
|
4
|
+
* ab-nextjs-components
|
|
5
|
+
* ~~~~~~~~~~~~
|
|
6
|
+
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the 'Software'), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*
|
|
26
|
+
* @project: ab-nextjs-components
|
|
27
|
+
* @name: AbIcon - Built-in glyphs (G2)
|
|
28
|
+
* @file: ab-icon/builtin.ts
|
|
29
|
+
* @type: TypeScript โ the only icons components draw themselves
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
35
|
+
* MOTTO: We'll always do more ๐!!!
|
|
36
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// ๐ฃ one module per icon (ab-nextjs-icons โฅ 0.2.3): bundlers keep only these 8
|
|
40
|
+
import arrowBack from 'ab-nextjs-icons/ab-icons/icons/arrow_back'; // โฎ๏ธ AbCarouselPrev
|
|
41
|
+
import arrowForward from 'ab-nextjs-icons/ab-icons/icons/arrow_forward'; // โญ๏ธ AbCarouselNext
|
|
42
|
+
import lightMode from 'ab-nextjs-icons/ab-icons/icons/light_mode'; // ๐ AbThemeToggle
|
|
43
|
+
import darkMode from 'ab-nextjs-icons/ab-icons/icons/dark_mode'; // ๐ AbThemeToggle
|
|
44
|
+
import computer from 'ab-nextjs-icons/ab-icons/icons/computer'; // ๐ฅ๏ธ AbThemeToggle (system)
|
|
45
|
+
import language from 'ab-nextjs-icons/ab-icons/icons/language'; // ๐ AbLocaleSwitcher
|
|
46
|
+
import contentCopy from 'ab-nextjs-icons/ab-icons/icons/content_copy'; // ๐ AbInstallSnippet
|
|
47
|
+
import check from 'ab-nextjs-icons/ab-icons/icons/check'; // โ
AbInstallSnippet (copied)
|
|
48
|
+
// ๐งพ shape
|
|
49
|
+
import type { AbIconSvgData } from './helpers';
|
|
50
|
+
|
|
51
|
+
/** ๐๏ธ Icons the component library renders internally (always registered by AbIcon). */
|
|
52
|
+
export const AB_ICON_BUILTINS: Record<string, AbIconSvgData> = {
|
|
53
|
+
arrow_back: arrowBack, // โฎ๏ธ
|
|
54
|
+
arrow_forward: arrowForward, // โญ๏ธ
|
|
55
|
+
light_mode: lightMode, // ๐
|
|
56
|
+
dark_mode: darkMode, // ๐
|
|
57
|
+
computer, // ๐ฅ๏ธ
|
|
58
|
+
language, // ๐
|
|
59
|
+
content_copy: contentCopy, // ๐
|
|
60
|
+
check, // โ
|
|
61
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license MIT
|
|
3
|
+
* ~~~~~~~~~~~~
|
|
4
|
+
* ab-nextjs-components
|
|
5
|
+
* ~~~~~~~~~~~~
|
|
6
|
+
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the 'Software'), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*
|
|
26
|
+
* @project: ab-nextjs-components
|
|
27
|
+
* @name: AbIcon - Shared helpers + registry (G2)
|
|
28
|
+
* @file: ab-icon/helpers.ts
|
|
29
|
+
* @type: TypeScript (pure, server-safe)
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*
|
|
32
|
+
* Example usage:
|
|
33
|
+
* 1+|> import arrowBack from 'ab-nextjs-icons/ab-icons/icons/arrow_back' // ๐ฆ icons โฅ 0.2.3, one icon per module
|
|
34
|
+
* -|> registerAbIcons({ arrow_back: arrowBack }) // ๐๏ธ at module scope (same on server + client โ no hydration mismatch)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
39
|
+
* MOTTO: We'll always do more ๐!!!
|
|
40
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// ๐งพ one icon's SVG markup (same shape as ab-nextjs-icons `AbIconSvgEntry`)
|
|
44
|
+
export interface AbIconSvgData {
|
|
45
|
+
outlined: string; // โ๏ธ outlined markup (stroke = currentColor)
|
|
46
|
+
filled?: string; // โฌ filled markup (optional)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ๐๏ธ variants + weights (mirrors ab-nextjs-icons โฅ 0.2.0)
|
|
50
|
+
export type AbIconVariant = 'outlined' | 'filled';
|
|
51
|
+
export type AbIconWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700;
|
|
52
|
+
|
|
53
|
+
// ๐ weight โ stroke width. Copied from ab-nextjs-icons `abIconWeightStroke`
|
|
54
|
+
// (importing it would pull the whole 378 KB icon map into client bundles).
|
|
55
|
+
export const AB_ICON_WEIGHT_STROKE: Record<AbIconWeight, number> = {
|
|
56
|
+
100: 1.0, 200: 1.25, 300: 1.5, 400: 1.85, 500: 2.2, 600: 2.5, 700: 2.85, // โ
test guards drift
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ๐งญ package default weight (icons 0.2.0+: 200)
|
|
60
|
+
export const AB_ICON_DEFAULT_WEIGHT: AbIconWeight = 200;
|
|
61
|
+
|
|
62
|
+
// ๐๏ธ module-level registry: name โ markup (client bundles only carry what you register)
|
|
63
|
+
const registry = new Map<string, AbIconSvgData>();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @name registerAbIcons
|
|
67
|
+
* @description ๐๏ธ Make icons available to the client `<AbIcon name>`. Call at module
|
|
68
|
+
* scope so server render + hydration see the same set. Later calls overwrite names.
|
|
69
|
+
*/
|
|
70
|
+
export function registerAbIcons(icons: Record<string, AbIconSvgData>): void {
|
|
71
|
+
for (const [name, data] of Object.entries(icons)) registry.set(name, data); // โ
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ๐ registry lookup (tests + server twin fallback)
|
|
75
|
+
export function getRegisteredAbIcon(name: string): AbIconSvgData | undefined {
|
|
76
|
+
return registry.get(name); // ๐ฏ
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @name pickAbIconMarkup
|
|
81
|
+
* @description ๐ฏ Choose markup for a variant; filled falls back to outlined (like getAbIconSvg).
|
|
82
|
+
*/
|
|
83
|
+
export function pickAbIconMarkup(data: AbIconSvgData | undefined, variant: AbIconVariant): string | undefined {
|
|
84
|
+
if (!data) return undefined; // ๐
|
|
85
|
+
return variant === 'filled' ? (data.filled ?? data.outlined) : data.outlined; // โ
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @name getAbIconStroke
|
|
90
|
+
* @description ๐ Stroke width for a weight (snaps to the nearest supported weight).
|
|
91
|
+
*/
|
|
92
|
+
export function getAbIconStroke(weight: number = AB_ICON_DEFAULT_WEIGHT): number {
|
|
93
|
+
const keys = Object.keys(AB_ICON_WEIGHT_STROKE).map(Number) as AbIconWeight[]; // ๐ข
|
|
94
|
+
const nearest = keys.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a)); // ๐ฏ
|
|
95
|
+
return AB_ICON_WEIGHT_STROKE[nearest]; // ๐
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ๐ normalise size โ CSS length
|
|
99
|
+
export function toAbIconSize(size: number | string | undefined): string {
|
|
100
|
+
if (size == null) return '24px'; // ๐งญ default 24
|
|
101
|
+
return typeof size === 'number' ? `${size}px` : size; // ๐
|
|
102
|
+
}
|
package/ab-icon/index.tsx
CHANGED
|
@@ -22,42 +22,103 @@
|
|
|
22
22
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
23
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
24
|
* SOFTWARE.
|
|
25
|
-
|
|
26
25
|
*
|
|
27
26
|
* @project: ab-nextjs-components
|
|
28
|
-
* @name: AbIcon - Client
|
|
27
|
+
* @name: AbIcon - Client (G2)
|
|
29
28
|
* @file: ab-icon/index.tsx
|
|
29
|
+
* @type: Component (client) โ AbIcons SVG, no icon font
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*
|
|
32
|
+
* Example usage:
|
|
33
|
+
* 1+|> <AbIcon name="arrow_back" /> // ๐ decorative
|
|
34
|
+
* -|> <AbIcon name="github" label="GitHub" svg={github} /> // ๐ผ๏ธ named image
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
39
|
+
* MOTTO: We'll always do more ๐!!!
|
|
40
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
30
41
|
*/
|
|
31
42
|
|
|
32
43
|
'use client';
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
// โ๏ธ types
|
|
46
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
47
|
+
// ๐งต classes
|
|
35
48
|
import clsx from 'clsx';
|
|
49
|
+
// ๐งฎ shared helpers (NO icon map import: client bundles stay small)
|
|
50
|
+
import { getAbIconStroke, getRegisteredAbIcon, pickAbIconMarkup, registerAbIcons, toAbIconSize, type AbIconSvgData, type AbIconVariant, type AbIconWeight } from './helpers';
|
|
51
|
+
// ๐๏ธ the 8 glyphs components draw themselves (per-icon modules, not the map)
|
|
52
|
+
import { AB_ICON_BUILTINS } from './builtin';
|
|
53
|
+
// ๐จ styles
|
|
36
54
|
import styles from './styles.module.css';
|
|
37
55
|
|
|
56
|
+
// ๐ฆ helpers for consumers (registerAbIcons, types)
|
|
57
|
+
export * from './helpers';
|
|
58
|
+
export { AB_ICON_BUILTINS } from './builtin';
|
|
59
|
+
|
|
60
|
+
// ๐๏ธ register built-ins at module scope (same on server render + hydration โ no mismatch)
|
|
61
|
+
registerAbIcons(AB_ICON_BUILTINS);
|
|
62
|
+
|
|
63
|
+
// โ๏ธ props
|
|
38
64
|
export interface AbIconProps {
|
|
39
|
-
name: string;
|
|
40
|
-
|
|
41
|
-
filled?: boolean;
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
name: string; // ๐๏ธ AbIcons name (snake_case); Material ligature fallback if unknown
|
|
66
|
+
variant?: AbIconVariant; // ๐๏ธ outlined (default) | filled
|
|
67
|
+
filled?: boolean; // ๐ฐ๏ธ legacy alias for variant="filled"
|
|
68
|
+
weight?: AbIconWeight | number; // ๐ 100โ700 (outlined), default 200
|
|
69
|
+
size?: number | string; // ๐ default 24px
|
|
70
|
+
label?: string; // โฟ NEW: accessible name; omitted = decorative (aria-hidden)
|
|
71
|
+
title?: string; // ๐ฌ legacy tooltip + name
|
|
72
|
+
svg?: AbIconSvgData; // ๐งพ NEW: explicit markup (tree-shaken per-icon import)
|
|
73
|
+
className?: string; // ๐ท๏ธ
|
|
74
|
+
style?: CSSProperties; // ๐จ
|
|
44
75
|
}
|
|
45
76
|
|
|
46
|
-
/**
|
|
47
|
-
const AbIcon = ({ name,
|
|
77
|
+
/** ๐ฃ AbIcons SVG icon (client): `svg` prop โ registry โ Material ligature fallback. */
|
|
78
|
+
const AbIcon = ({ name, variant, filled = false, weight, size, label, title, svg, className, style }: AbIconProps): ReactElement => {
|
|
79
|
+
// ๐ resolve markup without the full map
|
|
80
|
+
const markup = pickAbIconMarkup(svg ?? getRegisteredAbIcon(name), variant ?? (filled ? 'filled' : 'outlined'));
|
|
81
|
+
// ๐๏ธ legacy `filled` โ variant
|
|
82
|
+
const v: AbIconVariant = variant ?? (filled ? 'filled' : 'outlined');
|
|
83
|
+
// ๐ท๏ธ accessible name: `label` (new) or `title` (legacy)
|
|
84
|
+
const name_ = label ?? title;
|
|
85
|
+
// ๐จ size + weight as CSS variables (weight overrides SVG stroke-width via CSS)
|
|
86
|
+
const vars = { ...style, '--ab-icon-size': toAbIconSize(size), '--ab-icon-stroke': String(getAbIconStroke(weight)) } as CSSProperties;
|
|
87
|
+
// โฟ labelled = image, else decorative
|
|
88
|
+
const a11y = name_ ? { role: 'img' as const, 'aria-label': name_ } : { 'aria-hidden': true as const };
|
|
89
|
+
|
|
90
|
+
// โ
AbIcons SVG
|
|
91
|
+
if (markup) {
|
|
92
|
+
return (
|
|
93
|
+
<span
|
|
94
|
+
className={clsx('AbIcon', styles.abIcon, className)} // ๐ท๏ธ
|
|
95
|
+
style={vars} // ๐จ
|
|
96
|
+
data-variant={v} // ๐๏ธ
|
|
97
|
+
data-icon={name} // ๐๏ธ
|
|
98
|
+
title={title} // ๐ฌ legacy tooltip only
|
|
99
|
+
{...a11y} // โฟ
|
|
100
|
+
// ๐ trusted, package-generated static markup (never user input)
|
|
101
|
+
dangerouslySetInnerHTML={{ __html: markup }}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ๐ฐ๏ธ fallback: Material Symbols ligature (pre-G2 behaviour) for names not in AbIcons
|
|
48
107
|
return (
|
|
49
108
|
<span
|
|
50
|
-
className={clsx('material-symbols-rounded', 'icon', styles.
|
|
51
|
-
style={
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
109
|
+
className={clsx('material-symbols-rounded', 'icon', styles.abIconFont, className)} // ๐ท๏ธ
|
|
110
|
+
style={{ ...style, fontSize: toAbIconSize(size) }} // ๐
|
|
111
|
+
data-filled={v === 'filled' ? 'true' : 'false'} // ๐๏ธ
|
|
112
|
+
data-icon={name} // ๐๏ธ
|
|
113
|
+
data-ab-icon-fallback="font" // ๐ easy to audit
|
|
114
|
+
title={title} // ๐ฌ
|
|
115
|
+
{...a11y} // โฟ
|
|
56
116
|
>
|
|
57
117
|
{name}
|
|
58
118
|
</span>
|
|
59
119
|
);
|
|
60
120
|
};
|
|
61
121
|
|
|
122
|
+
// ๐ฆ exports
|
|
62
123
|
export default AbIcon;
|
|
63
124
|
export { AbIcon };
|