ab-nextjs-components 0.1.8 โ 0.1.10
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 +42 -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 +141 -0
- package/ab-flow-diagram/styles.module.css +141 -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 +129 -0
- package/ab-linear-progress/styles.module.css +105 -0
- package/ab-locale-switcher/index.tsx +213 -0
- package/ab-locale-switcher/styles.module.css +127 -0
- package/ab-logo/index.tsx +88 -30
- package/ab-logo/styles.module.css +79 -11
- 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/csp.d.ts +54 -0
- package/ab-splash/csp.mjs +82 -0
- package/ab-splash/csp.ts +60 -0
- package/ab-splash/gate.ts +216 -0
- package/ab-splash/index.tsx +140 -0
- package/ab-splash/styles.module.css +103 -0
- 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 +47 -0
- package/package.json +46 -18
- 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
- package/server/ab-logo/index.tsx +88 -29
- package/server/ab-logo/styles.module.css +79 -11
- package/types/css.d.ts +43 -0
- package/types/index.d.ts +2 -0
package/ab-icon-button/index.tsx
CHANGED
|
@@ -5,103 +5,167 @@
|
|
|
5
5
|
* ~~~~~~~~~~~~
|
|
6
6
|
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
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
|
+
*
|
|
8
26
|
* @project: ab-nextjs-components
|
|
9
27
|
* @name: AbIconButton - Client
|
|
10
28
|
* @file: ab-icon-button/index.tsx
|
|
29
|
+
* @type: Component (client) โ G6: accessible name on the interactive element
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*
|
|
32
|
+
* Example usage:
|
|
33
|
+
* 1+|> <AbIconButton icon="arrow_back" label={t('back')} onClick={prev} />
|
|
34
|
+
* -|> <AbIconButton icon="open_in_new" href="/docs" label="Open docs" /> // ๐ link variant is named too
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
39
|
+
* MOTTO: We'll always do more ๐!!!
|
|
40
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
11
41
|
*/
|
|
12
42
|
|
|
13
43
|
'use client';
|
|
14
44
|
|
|
45
|
+
// โ๏ธ React types + forwardRef
|
|
15
46
|
import type { CSSProperties, MouseEvent } from 'react';
|
|
16
47
|
import { forwardRef } from 'react';
|
|
48
|
+
// ๐งต class-name joiner
|
|
17
49
|
import clsx from 'clsx';
|
|
50
|
+
// ๐จ package-shipped CSS module
|
|
18
51
|
import styles from './styles.module.css';
|
|
52
|
+
import { AB_MOTION } from 'ab-nextjs-animations/motion'; // ๐ฌ transition class names (ab-nextjs-animations >= 0.2.4)
|
|
53
|
+
import 'ab-nextjs-animations/motion/styles.css'; // ๐จ .ab-motion-*
|
|
19
54
|
|
|
55
|
+
// โ๏ธ public props
|
|
20
56
|
export interface AbIconButtonProps {
|
|
57
|
+
// ๐ท๏ธ extra classes
|
|
21
58
|
className?: string;
|
|
59
|
+
// ๐จ inline style
|
|
22
60
|
style?: CSSProperties;
|
|
61
|
+
// ๐ hide the whole button
|
|
23
62
|
hidden?: boolean;
|
|
63
|
+
// ๐ฃ Material Symbols ligature name (AbIcons-backed AbIcon arrives with G2)
|
|
24
64
|
icon: string;
|
|
65
|
+
// ๐ซ disabled (button: native `disabled`; link: `aria-disabled` + no href)
|
|
25
66
|
disabled?: boolean;
|
|
67
|
+
// ๐ grow a little on hover
|
|
26
68
|
expands?: boolean;
|
|
69
|
+
// ๐ค shrink a little when pressed
|
|
27
70
|
shrinks?: boolean;
|
|
71
|
+
// โจ๏ธ custom tab index
|
|
28
72
|
tabIndex?: number;
|
|
73
|
+
// ๐ฑ๏ธ click handler (button variant)
|
|
29
74
|
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
75
|
+
// ๐ turns the button into a link
|
|
30
76
|
href?: string;
|
|
77
|
+
// โ
active / selected look
|
|
31
78
|
active?: boolean;
|
|
79
|
+
// ๐ซฅ no hover background
|
|
32
80
|
naked?: boolean;
|
|
81
|
+
// โณ show a spinner instead of the icon (button variant)
|
|
33
82
|
isLoading?: boolean;
|
|
83
|
+
// โฟ G6: accessible name (preferred; translate it). Falls back to `title`.
|
|
84
|
+
label?: string;
|
|
85
|
+
// ๐ฌ tooltip text; also the accessible name when `label` is missing (pre-G6 callers)
|
|
34
86
|
title?: string;
|
|
87
|
+
// ๐งพ button type
|
|
35
88
|
type?: 'button' | 'submit' | 'reset';
|
|
36
89
|
}
|
|
37
90
|
|
|
38
91
|
const AbIconButton = forwardRef<HTMLButtonElement, AbIconButtonProps>(function AbIconButton(props, ref) {
|
|
92
|
+
// ๐งฉ destructure with defaults
|
|
39
93
|
const {
|
|
40
|
-
className,
|
|
41
|
-
style,
|
|
42
|
-
hidden = false,
|
|
43
|
-
icon,
|
|
44
|
-
disabled = false,
|
|
45
|
-
expands = false,
|
|
46
|
-
shrinks = false,
|
|
47
|
-
tabIndex,
|
|
48
|
-
onClick,
|
|
49
|
-
href,
|
|
50
|
-
active = false,
|
|
51
|
-
naked = false,
|
|
52
|
-
isLoading = false,
|
|
53
|
-
|
|
54
|
-
|
|
94
|
+
className, // ๐ท๏ธ
|
|
95
|
+
style, // ๐จ
|
|
96
|
+
hidden = false, // ๐
|
|
97
|
+
icon, // ๐ฃ
|
|
98
|
+
disabled = false, // ๐ฆ
|
|
99
|
+
expands = false, // ๐
|
|
100
|
+
shrinks = false, // ๐ค
|
|
101
|
+
tabIndex, // โจ๏ธ
|
|
102
|
+
onClick, // ๐ฑ๏ธ
|
|
103
|
+
href, // ๐
|
|
104
|
+
active = false, // โ
|
|
105
|
+
naked = false, // ๐ซฅ
|
|
106
|
+
isLoading = false, // โณ
|
|
107
|
+
label, // โฟ
|
|
108
|
+
title, // ๐ฌ
|
|
109
|
+
type = 'button', // ๐งพ
|
|
55
110
|
} = props;
|
|
56
111
|
|
|
57
|
-
|
|
112
|
+
// โฟ G6: one accessible name for both variants, ON the interactive element (not the wrapper span)
|
|
113
|
+
const name = label ?? title;
|
|
114
|
+
// ๐ฌ tooltip: explicit title wins, else reuse the name so sighted mouse users see it too
|
|
115
|
+
const tooltip = title ?? label;
|
|
116
|
+
// ๐ท๏ธ classes
|
|
117
|
+
const classNames = clsx('AbIconButton', 'icon-button', styles.abIconButton, AB_MOTION.interactive, expands && AB_MOTION.lift, shrinks && AB_MOTION.press, className); // ๐ฌ hover lift / press from ab-nextjs-animations
|
|
118
|
+
// ๐ฃ the glyph is decoration: the ligature text ("arrow_back") must never be read aloud
|
|
119
|
+
const glyph = <span className={clsx('material-symbols-rounded', 'icon')} aria-hidden="true">{icon}</span>;
|
|
58
120
|
|
|
121
|
+
// ๐ link variant
|
|
59
122
|
if (href && !isLoading) {
|
|
60
123
|
return (
|
|
61
|
-
<span hidden={hidden} className={styles.wrap}
|
|
124
|
+
<span hidden={hidden} className={styles.wrap}>
|
|
62
125
|
<a
|
|
63
|
-
href={disabled ? undefined : href}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
data-
|
|
70
|
-
data-
|
|
71
|
-
|
|
72
|
-
|
|
126
|
+
href={disabled ? undefined : href} // ๐ซ a disabled link has no target
|
|
127
|
+
className={classNames} // ๐ท๏ธ
|
|
128
|
+
style={style} // ๐จ
|
|
129
|
+
aria-label={name} // โฟ G6: the href variant is named now
|
|
130
|
+
title={tooltip} // ๐ฌ on the link, not the wrapper
|
|
131
|
+
aria-disabled={disabled || undefined} // ๐ฆ
|
|
132
|
+
data-active={String(active)} // โ
|
|
133
|
+
data-naked={String(naked)} // ๐ซฅ
|
|
134
|
+
data-expands={String(expands)} // ๐
|
|
135
|
+
data-shrinks={String(shrinks)} // ๐ค
|
|
136
|
+
tabIndex={tabIndex ?? (disabled ? -1 : 0)} // โจ๏ธ a disabled link leaves the tab order
|
|
73
137
|
>
|
|
74
|
-
|
|
138
|
+
{glyph}
|
|
75
139
|
</a>
|
|
76
140
|
</span>
|
|
77
141
|
);
|
|
78
142
|
}
|
|
79
143
|
|
|
144
|
+
// ๐ button variant
|
|
80
145
|
return (
|
|
81
|
-
<span hidden={hidden} className={styles.wrap}
|
|
146
|
+
<span hidden={hidden} className={styles.wrap}>
|
|
82
147
|
<button
|
|
83
|
-
ref={ref}
|
|
84
|
-
type={type}
|
|
85
|
-
className={classNames}
|
|
86
|
-
style={style}
|
|
87
|
-
disabled={disabled || isLoading}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
data-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
ref={ref} // ๐
|
|
149
|
+
type={type} // ๐งพ
|
|
150
|
+
className={classNames} // ๐ท๏ธ
|
|
151
|
+
style={style} // ๐จ
|
|
152
|
+
disabled={disabled || isLoading} // ๐ฆ
|
|
153
|
+
aria-label={name} // โฟ accessible name
|
|
154
|
+
title={tooltip} // ๐ฌ tooltip on the button itself
|
|
155
|
+
aria-busy={isLoading || undefined} // โณ announce loading
|
|
156
|
+
data-active={String(active)} // โ
|
|
157
|
+
data-naked={String(naked)} // ๐ซฅ
|
|
158
|
+
data-expands={String(expands)} // ๐
|
|
159
|
+
data-shrinks={String(shrinks)} // ๐ค
|
|
160
|
+
onClick={onClick} // ๐ฑ๏ธ
|
|
161
|
+
tabIndex={tabIndex ?? 0} // โจ๏ธ
|
|
95
162
|
>
|
|
96
|
-
{isLoading ? (
|
|
97
|
-
<span className={clsx('spinner', 'dots-12', styles.spinner)} />
|
|
98
|
-
) : (
|
|
99
|
-
<span className={clsx('material-symbols-rounded', 'icon')}>{icon}</span>
|
|
100
|
-
)}
|
|
163
|
+
{isLoading ? <span className={clsx('spinner', 'dots-12', styles.spinner)} aria-hidden="true" /> : glyph}
|
|
101
164
|
</button>
|
|
102
165
|
</span>
|
|
103
166
|
);
|
|
104
167
|
});
|
|
105
168
|
|
|
169
|
+
// ๐ฆ exports
|
|
106
170
|
export default AbIconButton;
|
|
107
171
|
export { AbIconButton };
|
|
@@ -2,7 +2,32 @@
|
|
|
2
2
|
* @license MIT
|
|
3
3
|
* ~~~~~~~~~~~~
|
|
4
4
|
* ab-nextjs-components
|
|
5
|
-
*
|
|
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: styles.module.css
|
|
28
|
+
* @file: ab-icon-button/styles.module.css
|
|
29
|
+
* @type: CSS Module
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
6
31
|
*/
|
|
7
32
|
|
|
8
33
|
/*
|
|
@@ -11,6 +36,7 @@
|
|
|
11
36
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
12
37
|
*/
|
|
13
38
|
|
|
39
|
+
|
|
14
40
|
.wrap {
|
|
15
41
|
display: inline-block;
|
|
16
42
|
width: fit-content;
|
|
@@ -33,7 +59,7 @@
|
|
|
33
59
|
cursor: pointer;
|
|
34
60
|
background: transparent;
|
|
35
61
|
color: inherit;
|
|
36
|
-
|
|
62
|
+
/* ๐ฌ motion: .ab-motion-interactive (+ .ab-motion-lift / .ab-motion-press) from ab-nextjs-animations */
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
.abIconButton:hover {
|
|
@@ -44,14 +70,6 @@
|
|
|
44
70
|
background-color: transparent;
|
|
45
71
|
}
|
|
46
72
|
|
|
47
|
-
.abIconButton[data-expands='true']:hover {
|
|
48
|
-
transform: scale(1.06);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.abIconButton[data-shrinks='true']:active {
|
|
52
|
-
transform: scale(0.94);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
73
|
.abIconButton:disabled {
|
|
56
74
|
opacity: 0.5;
|
|
57
75
|
cursor: not-allowed;
|
|
@@ -0,0 +1,62 @@
|
|
|
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: AbInstallSnippet - Helpers (G35)
|
|
28
|
+
* @file: ab-install-snippet/helpers.ts
|
|
29
|
+
* @type: TypeScript (pure, server-safe)
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
35
|
+
* MOTTO: We'll always do more ๐!!!
|
|
36
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// ๐ฆ supported package managers
|
|
40
|
+
export type AbPackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
|
|
41
|
+
|
|
42
|
+
// ๐๏ธ default tab order (mocks: pnpm first)
|
|
43
|
+
export const AB_PACKAGE_MANAGERS: readonly AbPackageManager[] = ['pnpm', 'npm', 'yarn', 'bun'] as const;
|
|
44
|
+
|
|
45
|
+
// ๐ narrow an unknown (e.g. from localStorage) to a manager
|
|
46
|
+
export function isAbPackageManager(value: unknown): value is AbPackageManager {
|
|
47
|
+
return value === 'pnpm' || value === 'npm' || value === 'yarn' || value === 'bun'; // โ
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @name getAbInstallCommand
|
|
52
|
+
* @description ๐งพ The install command for a manager. Package names are never translated.
|
|
53
|
+
* @example getAbInstallCommand('npm', 'ab-nextjs-core') // "npm i ab-nextjs-core"
|
|
54
|
+
*/
|
|
55
|
+
export function getAbInstallCommand(pm: AbPackageManager, pkg: string, dev = false): string {
|
|
56
|
+
switch (pm) {
|
|
57
|
+
case 'npm': return `npm i ${dev ? '-D ' : ''}${pkg}`; // ๐ฆ
|
|
58
|
+
case 'yarn': return `yarn add ${dev ? '-D ' : ''}${pkg}`; // ๐งถ
|
|
59
|
+
case 'bun': return `bun add ${dev ? '-d ' : ''}${pkg}`; // ๐ฅ
|
|
60
|
+
default: return `pnpm add ${dev ? '-D ' : ''}${pkg}`; // โก pnpm
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
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: AbInstallSnippet - Client (G35)
|
|
28
|
+
* @file: ab-install-snippet/index.tsx
|
|
29
|
+
* @type: Component (client) โ package-manager tabs + command + copy
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*
|
|
32
|
+
* Example usage:
|
|
33
|
+
* 1+|> <AbInstallSnippet pkg="ab-nextjs-core" storageKey="ab-welcome-pm" labels={{ group: t('install.label', { pkg }), copy: t('install.copy'), copied: t('install.copied') }} />
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
38
|
+
* MOTTO: We'll always do more ๐!!!
|
|
39
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
'use client';
|
|
43
|
+
|
|
44
|
+
// โ๏ธ React
|
|
45
|
+
import type { CSSProperties, KeyboardEvent } from 'react';
|
|
46
|
+
import { useId, useRef, useState } from 'react';
|
|
47
|
+
// ๐งต classes
|
|
48
|
+
import clsx from 'clsx';
|
|
49
|
+
// ๐ clipboard + ๐๏ธ persisted choice (hooks โฅ 0.1.6, deep imports)
|
|
50
|
+
import { useAbClipboard } from 'ab-nextjs-hooks/useAbClipboard';
|
|
51
|
+
import { useAbLocalStorage } from 'ab-nextjs-hooks/useAbLocalStorage';
|
|
52
|
+
// ๐ copy button (G6-fixed)
|
|
53
|
+
import AbIconButton from '../ab-icon-button';
|
|
54
|
+
// ๐งฎ pure helpers
|
|
55
|
+
import { AB_PACKAGE_MANAGERS, getAbInstallCommand, isAbPackageManager, type AbPackageManager } from './helpers';
|
|
56
|
+
// ๐จ styles
|
|
57
|
+
import styles from './styles.module.css';
|
|
58
|
+
|
|
59
|
+
// ๐ฆ re-export helpers for convenience
|
|
60
|
+
export * from './helpers';
|
|
61
|
+
|
|
62
|
+
// ๐ท๏ธ i18n labels
|
|
63
|
+
export interface AbInstallSnippetLabels {
|
|
64
|
+
group: string; // ๐๏ธ "Install ab-nextjs-core" (tablist name)
|
|
65
|
+
copy: string; // ๐ "Copy command"
|
|
66
|
+
copied: string; // โ
"Copied" (announced politely)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// โ๏ธ props
|
|
70
|
+
export interface AbInstallSnippetProps {
|
|
71
|
+
pkg: string; // ๐ฆ package name (not translated)
|
|
72
|
+
labels: AbInstallSnippetLabels; // ๐ท๏ธ
|
|
73
|
+
managers?: readonly AbPackageManager[]; // ๐๏ธ default pnpm, npm, yarn, bun
|
|
74
|
+
storageKey?: string; // ๐๏ธ persist + sync the choice across snippets (e.g. 'ab-welcome-pm')
|
|
75
|
+
defaultManager?: AbPackageManager; // ๐ฑ default 'pnpm'
|
|
76
|
+
dev?: boolean; // ๐ ๏ธ dev dependency flag
|
|
77
|
+
className?: string; // ๐ท๏ธ
|
|
78
|
+
style?: CSSProperties; // ๐จ
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ๐ง state source: persisted (shared across instances) or local
|
|
82
|
+
type PmState = readonly [AbPackageManager, (pm: AbPackageManager) => void];
|
|
83
|
+
|
|
84
|
+
/** ๐๏ธ persisted variant (hooks can't be conditional, so it's its own component) */
|
|
85
|
+
function PersistedSnippet(props: AbInstallSnippetProps & { storageKey: string }) {
|
|
86
|
+
const fallback = props.defaultManager ?? 'pnpm'; // ๐ฑ
|
|
87
|
+
const [raw, setRaw] = useAbLocalStorage<string>(props.storageKey, fallback); // ๐๏ธ syncs every snippet with this key
|
|
88
|
+
const pm = isAbPackageManager(raw) ? raw : fallback; // ๐งฏ ignore junk in storage
|
|
89
|
+
return <SnippetView {...props} state={[pm, setRaw]} />;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** ๐ง local variant */
|
|
93
|
+
function LocalSnippet(props: AbInstallSnippetProps) {
|
|
94
|
+
const [pm, setPm] = useState<AbPackageManager>(props.defaultManager ?? 'pnpm'); // ๐ง
|
|
95
|
+
return <SnippetView {...props} state={[pm, setPm]} />;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** ๐ผ๏ธ the actual UI */
|
|
99
|
+
function SnippetView({ pkg, labels, managers = AB_PACKAGE_MANAGERS, dev = false, className, style, state }: AbInstallSnippetProps & { state: PmState }) {
|
|
100
|
+
const [pm, setPm] = state; // ๐๏ธ
|
|
101
|
+
const current = managers.includes(pm) ? pm : managers[0]; // ๐งฏ stored pm not offered here
|
|
102
|
+
const command = getAbInstallCommand(current, pkg, dev); // ๐งพ
|
|
103
|
+
const { copy, copied } = useAbClipboard(); // ๐
|
|
104
|
+
const base = useId(); // ๐ tab/panel ids
|
|
105
|
+
const tabs = useRef<Array<HTMLButtonElement | null>>([]); // ๐ roving focus
|
|
106
|
+
|
|
107
|
+
// โจ๏ธ APG tabs: arrows move + select (automatic activation), Home/End
|
|
108
|
+
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
109
|
+
const i = managers.indexOf(current); // ๐
|
|
110
|
+
const rtl = event.currentTarget.closest('[dir]')?.getAttribute('dir') === 'rtl'; // ๐
|
|
111
|
+
const nextKey = rtl ? 'ArrowLeft' : 'ArrowRight'; // โก๏ธ
|
|
112
|
+
const prevKey = rtl ? 'ArrowRight' : 'ArrowLeft'; // โฌ
๏ธ
|
|
113
|
+
let j: number | null = null; // ๐ฏ
|
|
114
|
+
if (event.key === nextKey) j = (i + 1) % managers.length; // โญ๏ธ
|
|
115
|
+
else if (event.key === prevKey) j = (i - 1 + managers.length) % managers.length; // โฎ๏ธ
|
|
116
|
+
else if (event.key === 'Home') j = 0; // โฎ๏ธโฎ๏ธ
|
|
117
|
+
else if (event.key === 'End') j = managers.length - 1; // โญ๏ธโญ๏ธ
|
|
118
|
+
if (j === null) return; // ๐
|
|
119
|
+
event.preventDefault(); // ๐
|
|
120
|
+
event.stopPropagation(); // ๐ the carousel must not also move
|
|
121
|
+
setPm(managers[j]); // โ
|
|
122
|
+
tabs.current[j]?.focus(); // ๐ฏ
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className={clsx('AbInstallSnippet', styles.root, className)} style={style} data-ab-part="install-snippet" data-ab-swipe-ignore="">
|
|
127
|
+
{/* ๐๏ธ manager tabs */}
|
|
128
|
+
<div role="tablist" aria-label={labels.group} className={styles.tabs} onKeyDown={onKeyDown}>
|
|
129
|
+
{managers.map((m, i) => (
|
|
130
|
+
<button
|
|
131
|
+
key={m} // ๐
|
|
132
|
+
ref={(el) => { tabs.current[i] = el; }} // ๐
|
|
133
|
+
id={`${base}-tab-${m}`} // ๐
|
|
134
|
+
type="button" // ๐งพ
|
|
135
|
+
role="tab" // โฟ
|
|
136
|
+
aria-selected={m === current} // โ
|
|
137
|
+
aria-controls={`${base}-panel`} // ๐
|
|
138
|
+
tabIndex={m === current ? 0 : -1} // โจ๏ธ roving
|
|
139
|
+
className={styles.tab} // ๐จ
|
|
140
|
+
onClick={() => setPm(m)} // ๐ฑ๏ธ
|
|
141
|
+
>
|
|
142
|
+
{m}
|
|
143
|
+
</button>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
{/* ๐งพ command panel */}
|
|
147
|
+
<div id={`${base}-panel`} role="tabpanel" aria-labelledby={`${base}-tab-${current}`} className={styles.panel}>
|
|
148
|
+
<span className={styles.prompt} aria-hidden="true">$</span>{/* ๐ฒ decorative prompt */}
|
|
149
|
+
<code className={styles.code}>{command}</code>
|
|
150
|
+
<AbIconButton
|
|
151
|
+
icon={copied ? 'check' : 'content_copy'} // ๐ / โ
|
|
152
|
+
label={labels.copy} // โฟ name stays "Copy command"
|
|
153
|
+
naked // ๐ซฅ
|
|
154
|
+
className={styles.copy} // ๐จ
|
|
155
|
+
onClick={() => { void copy(command); }} // ๐
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
{/* ๐ฃ own polite live region */}
|
|
159
|
+
<span className={styles.srOnly} aria-live="polite" role="status">{copied ? labels.copied : ''}</span>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** ๐ฆ Install box: pnpm / npm / yarn / bun tabs + command + copy + "Copied" announcement. */
|
|
165
|
+
export function AbInstallSnippet(props: AbInstallSnippetProps) {
|
|
166
|
+
return props.storageKey ? <PersistedSnippet {...props} storageKey={props.storageKey} /> : <LocalSnippet {...props} />;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ๐ฆ default export
|
|
170
|
+
export default AbInstallSnippet;
|
|
@@ -0,0 +1,104 @@
|
|
|
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: AbInstallSnippet - Styles
|
|
28
|
+
* @file: ab-install-snippet/styles.module.css
|
|
29
|
+
* @type: CSS module
|
|
30
|
+
* @authors: Abraham Ukachi <abrahamukachi@gmail.com>
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
35
|
+
* MOTTO: We'll always do more ๐!!!
|
|
36
|
+
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/* ๐ฆ box */
|
|
40
|
+
.root {
|
|
41
|
+
display: grid; /* ๐ */
|
|
42
|
+
gap: 0.5rem; /* ๐งฑ */
|
|
43
|
+
padding: 0.75rem; /* ๐งฑ */
|
|
44
|
+
border-radius: 0.875rem; /* ๐ฒ */
|
|
45
|
+
border: 1px solid var(--ab-color-outline-variant, rgb(0 0 0 / 0.12)); /* ๐๏ธ */
|
|
46
|
+
background: var(--ab-color-surface-container, transparent); /* ๐จ */
|
|
47
|
+
max-width: 100%; /* ๐ */
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ๐๏ธ tabs row */
|
|
51
|
+
.tabs {
|
|
52
|
+
display: flex; /* ๐ */
|
|
53
|
+
gap: 0.25rem; /* ๐งฑ */
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ๐ tab */
|
|
57
|
+
.tab {
|
|
58
|
+
min-height: 28px; /* ๐ โฅ 24px target */
|
|
59
|
+
padding: 0.25rem 0.625rem; /* ๐งฑ */
|
|
60
|
+
border: 0; /* ๐งน */
|
|
61
|
+
border-radius: 9999px; /* ๐ */
|
|
62
|
+
background: transparent; /* ๐ซฅ */
|
|
63
|
+
color: var(--ab-color-on-surface-variant, currentColor); /* ๐จ */
|
|
64
|
+
font: inherit; /* ๐ค */
|
|
65
|
+
font-size: 0.8125rem; /* ๐ก */
|
|
66
|
+
cursor: pointer; /* ๐ */
|
|
67
|
+
}
|
|
68
|
+
.tab[aria-selected='true'] {
|
|
69
|
+
color: var(--ab-color-bronze-text, #8A6440); /* โฟ AA small text */
|
|
70
|
+
background: color-mix(in srgb, var(--ab-color-bronze, #A67C52) 12%, transparent); /* ๐จ */
|
|
71
|
+
font-weight: 600; /* ๐ช */
|
|
72
|
+
}
|
|
73
|
+
.tab:focus-visible {
|
|
74
|
+
outline: 2px solid var(--ab-focus-ring, var(--ab-color-primary, #A67C52)); /* ๐ */
|
|
75
|
+
outline-offset: 2px; /* โ๏ธ */
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ๐งพ command row */
|
|
79
|
+
.panel {
|
|
80
|
+
display: flex; /* ๐ */
|
|
81
|
+
align-items: center; /* โ๏ธ */
|
|
82
|
+
gap: 0.5rem; /* ๐งฑ */
|
|
83
|
+
min-width: 0; /* ๐ซ overflow */
|
|
84
|
+
}
|
|
85
|
+
.prompt {
|
|
86
|
+
opacity: 0.5; /* ๐ซ๏ธ */
|
|
87
|
+
font-family: var(--ab-font-mono, ui-monospace, monospace); /* ๐ค */
|
|
88
|
+
}
|
|
89
|
+
.code {
|
|
90
|
+
flex: 1; /* โ๏ธ */
|
|
91
|
+
overflow-x: auto; /* ๐ long commands scroll */
|
|
92
|
+
white-space: nowrap; /* ๐ซ wrap */
|
|
93
|
+
font-family: var(--ab-font-mono, ui-monospace, monospace); /* ๐ค */
|
|
94
|
+
font-size: 0.875rem; /* ๐ก */
|
|
95
|
+
}
|
|
96
|
+
.copy {
|
|
97
|
+
flex: none; /* ๐ซ squash */
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ๐ visually hidden */
|
|
101
|
+
.srOnly {
|
|
102
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; /* ๐ */
|
|
103
|
+
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; /* โ๏ธ */
|
|
104
|
+
}
|