ab-nextjs-components 0.1.2 → 0.1.3
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 +40 -0
- package/README.md +60 -14
- package/ab-avatar/index.tsx +90 -0
- package/ab-avatar/styles.module.css +62 -0
- package/ab-balloon/index.tsx +55 -0
- package/ab-balloon/styles.module.css +12 -0
- package/ab-brand/index.tsx +88 -0
- package/ab-brand/styles.module.css +54 -0
- package/ab-button/index.tsx +164 -0
- package/ab-button/styles.module.css +63 -0
- package/ab-collapsible/index.tsx +188 -0
- package/ab-collapsible/styles.module.css +98 -0
- package/ab-color/index.tsx +84 -0
- package/ab-color/styles.module.css +51 -0
- package/ab-demo-box/index.tsx +38 -0
- package/ab-demo-box/styles.module.css +13 -0
- package/ab-demo-code/index.tsx +62 -0
- package/ab-demo-code/styles.module.css +41 -0
- package/ab-icon/index.tsx +63 -0
- package/ab-icon/styles.module.css +27 -0
- package/ab-icon-button/index.tsx +107 -0
- package/ab-icon-button/styles.module.css +64 -0
- package/ab-image/index.tsx +83 -0
- package/ab-image/styles.module.css +27 -0
- package/ab-info/index.tsx +89 -0
- package/ab-info/styles.module.css +87 -0
- package/ab-input/index.tsx +277 -0
- package/ab-input/styles.module.css +101 -0
- package/ab-key-feature/index.tsx +60 -0
- package/ab-key-feature/styles.module.css +47 -0
- package/ab-like-button/index.tsx +116 -0
- package/ab-like-button/styles.module.css +60 -0
- package/ab-logo/index.tsx +94 -0
- package/ab-logo/styles.module.css +30 -0
- package/ab-main-headline/index.tsx +57 -0
- package/ab-main-headline/styles.module.css +24 -0
- package/ab-menu/index.tsx +119 -0
- package/ab-menu/styles.module.css +99 -0
- package/ab-name/index.tsx +49 -0
- package/ab-name/styles.module.css +14 -0
- package/ab-navbar/index.tsx +68 -0
- package/ab-navbar/styles.module.css +35 -0
- package/ab-page-paginator/index.tsx +130 -0
- package/ab-page-paginator/styles.module.css +47 -0
- package/ab-page-switcher/index.tsx +121 -0
- package/ab-page-switcher/styles.module.css +37 -0
- package/ab-preview/index.tsx +78 -0
- package/ab-preview/styles.module.css +26 -0
- package/ab-price/index.tsx +109 -0
- package/ab-price/styles.module.css +36 -0
- package/ab-price-tag/index.tsx +70 -0
- package/ab-price-tag/styles.module.css +81 -0
- package/ab-product/index.tsx +128 -0
- package/ab-product/styles.module.css +44 -0
- package/ab-product-item/index.tsx +78 -0
- package/ab-product-item/styles.module.css +21 -0
- package/ab-progress-chips/index.tsx +105 -0
- package/ab-progress-chips/styles.module.css +43 -0
- package/ab-search-result/index.tsx +79 -0
- package/ab-search-result/styles.module.css +48 -0
- package/ab-searchbar/index.tsx +150 -0
- package/ab-searchbar/styles.module.css +77 -0
- package/ab-sidebar/index.tsx +72 -0
- package/ab-sidebar/styles.module.css +41 -0
- package/ab-switch-back/index.tsx +72 -0
- package/ab-switch-back/styles.module.css +45 -0
- package/ab-tab/index.tsx +101 -0
- package/ab-tab/styles.module.css +65 -0
- package/ab-tabs/index.tsx +45 -0
- package/ab-tabs/styles.module.css +17 -0
- package/ab-test-hello/index.tsx +37 -0
- package/ab-test-hello/styles.module.css +25 -0
- package/ab-text/index.tsx +36 -0
- package/ab-text/styles.module.css +3 -0
- package/index.ts +117 -0
- package/package.json +152 -7
- package/server/ab-avatar/index.tsx +85 -0
- package/server/ab-avatar/styles.module.css +62 -0
- package/server/ab-badge/index.tsx +66 -0
- package/server/ab-badge/styles.module.css +39 -0
- package/server/ab-brand/index.tsx +87 -0
- package/server/ab-brand/styles.module.css +54 -0
- package/server/ab-button/index.tsx +144 -0
- package/server/ab-button/styles.module.css +63 -0
- package/server/ab-icon/index.tsx +61 -0
- package/server/ab-icon/styles.module.css +27 -0
- package/server/ab-key-feature/index.tsx +59 -0
- package/server/ab-key-feature/styles.module.css +47 -0
- package/server/ab-logo/index.tsx +93 -0
- package/server/ab-logo/styles.module.css +30 -0
- package/server/ab-name/index.tsx +48 -0
- package/server/ab-name/styles.module.css +14 -0
- package/server/ab-nav-link/index.tsx +60 -0
- package/server/ab-nav-link/styles.module.css +32 -0
- package/server/ab-navbar/index.tsx +64 -0
- package/server/ab-navbar/styles.module.css +35 -0
- package/server/ab-polygon/index.tsx +45 -0
- package/server/ab-polygon/styles.module.css +11 -0
- package/server/ab-sidebar/index.tsx +68 -0
- package/server/ab-sidebar/styles.module.css +41 -0
- package/server/ab-switch-back/index.tsx +71 -0
- package/server/ab-switch-back/styles.module.css +45 -0
- package/server/ab-test-hello/index.tsx +35 -0
- package/server/ab-test-hello/styles.module.css +22 -0
- package/server/ab-text/index.tsx +33 -0
- package/server/ab-text/styles.module.css +3 -0
- package/server/index.ts +27 -0
- package/types/index.d.ts +54 -0
- package/.github/ab-logo-dark.svg +0 -12
- package/.github/ab-logo-light.svg +0 -9
- package/.github/nextjs-logoname-dark.svg +0 -28
- package/.github/nextjs-logoname-light.svg +0 -25
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
### Unreleased (chore/upgrade-next-16 — Skip-override / Phase 5 components)
|
|
2
|
+
|
|
3
|
+
* **Port (client):** AbBrand, AbName, AbNavbar, AbSidebar, AbText, AbTestHello, AbKeyFeature, AbMainHeadline, AbPreview, AbProduct, AbProductItem, AbSwitchBack, AbDemoBox, AbDemoCode
|
|
4
|
+
* **Port (server):** AbBrand, AbName, AbNavbar, AbSidebar, AbNavLink, AbText, AbTestHello, AbKeyFeature, AbSwitchBack
|
|
5
|
+
* Select/MenuItem skipped — empty on Projects (GitHub-only historically)
|
|
6
|
+
* Product/Preview adapted (Dexie/providers/i18n stripped); usable props API kept
|
|
7
|
+
* Catalog **51 Done** (36 client / 15 server). Local only — no push.
|
|
8
|
+
|
|
9
|
+
# Changelog
|
|
10
|
+
|
|
11
|
+
### Unreleased (chore/upgrade-next-16 — Phase 4 Port)
|
|
12
|
+
|
|
13
|
+
* **Port:** AbSearchbar, AbSearchResult, AbPagePaginator, AbPageSwitcher, AbProgressChips, AbPrice, AbPriceTag
|
|
14
|
+
* **Adapt:** AbImage (Dexie / offline / background-removal stripped; next/image + optional fallback)
|
|
15
|
+
* **Port (server):** AbPolygon
|
|
16
|
+
* Catalog 28 Done (22 client / 6 server). Plain CSS modules. Local only — no push.
|
|
17
|
+
|
|
18
|
+
### Unreleased (chore/upgrade-next-16 — Phase 2 Adapt + Phase 3 Port)
|
|
19
|
+
|
|
20
|
+
* **Adapt:** **AbBalloon** (`title=` locked) — Input now uses real AbBalloon
|
|
21
|
+
* **Port:** AbAvatar (client + server), AbBadge (server), AbTab, AbTabs, AbMenu, AbCollapsible, AbColor, AbInfo, AbLikeButton
|
|
22
|
+
* Plain CSS modules; LYD brand/nav/i18n stripped; Select/MenuItem skipped (not on Projects)
|
|
23
|
+
|
|
24
|
+
### Unreleased (chore/upgrade-next-16 — Phase 2)
|
|
25
|
+
|
|
26
|
+
* Ported **AbIcon** (client + server) — Material Symbols ligatures
|
|
27
|
+
* Ported **AbLogo** (client + server) — contained | outlined | hollow | naked; defaults from `ab-nextjs-icons` logos (`#a67c52`), optional `src` / `mask`
|
|
28
|
+
* Ported **AbButton** client from lyd-button; invented server (href → `<a>`, else submit/button; no onClick)
|
|
29
|
+
* Added minimal **AbIconButton** (Input dependency)
|
|
30
|
+
* Ported **AbInput** client — `useAbToggle` from `ab-nextjs-hooks/helpers/useAbToggle`; Balloon stub via `title=`; no console.logs
|
|
31
|
+
* Hard peers: next 16.3.4, react^19, clsx; soft optional peers: theme / icons / hooks / animations
|
|
32
|
+
* Plain CSS modules (no `@apply`); theme `data-*` attrs preserved on buttons
|
|
33
|
+
|
|
34
|
+
### Unreleased (Phase 1)
|
|
35
|
+
|
|
36
|
+
* Next.js **16.3.4** / React 19 tooling scaffold + Pending catalog
|
|
37
|
+
|
|
38
|
+
### 0.1.2
|
|
39
|
+
|
|
40
|
+
* Stub package version bump
|
package/README.md
CHANGED
|
@@ -35,6 +35,15 @@
|
|
|
35
35
|
🚀 This is a lightweight collection of server & client React components created by [Abraham Ukachi](https://github.com/abraham-ukachi), and optimized for [Next.js](https://nextjs.org/docs) applications 😎.
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Tooling (Phase 1)
|
|
41
|
+
|
|
42
|
+
- Next.js **16.3.4** / React **19** hard peers
|
|
43
|
+
- ESLint 9 flat + Vitest
|
|
44
|
+
- Package ships as TypeScript (`type: module`) with a `supportedComponents` catalog
|
|
45
|
+
- Phase 2 Adapt complete; Phase 4 + Skip-override (Phase 5 components) complete (local)
|
|
46
|
+
|
|
38
47
|
## Getting Started
|
|
39
48
|
|
|
40
49
|
### Installation
|
|
@@ -69,14 +78,42 @@ A list of all the **client components** and their current status:
|
|
|
69
78
|
|
|
70
79
|
| No. | Name | File | Status |
|
|
71
80
|
|:----|:-----|:-----|:-------|
|
|
72
|
-
| 1 | *`
|
|
73
|
-
| 2 | *`
|
|
74
|
-
| 3 | *`AbButton`* | **ab-button/index.tsx** |
|
|
75
|
-
| 4 | *`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
| 1 | *`AbIcon`* | **ab-icon/index.tsx** | Done |
|
|
82
|
+
| 2 | *`AbLogo`* | **ab-logo/index.tsx** | Done |
|
|
83
|
+
| 3 | *`AbButton`* | **ab-button/index.tsx** | Done |
|
|
84
|
+
| 4 | *`AbIconButton`* | **ab-icon-button/index.tsx** | Done |
|
|
85
|
+
| 5 | *`AbInput`* | **ab-input/index.tsx** | Done |
|
|
86
|
+
| 6 | *`AbBalloon`* | **ab-balloon/index.tsx** | Done |
|
|
87
|
+
| 7 | *`AbAvatar`* | **ab-avatar/index.tsx** | Done |
|
|
88
|
+
| 8 | *`AbTab`* | **ab-tab/index.tsx** | Done |
|
|
89
|
+
| 9 | *`AbTabs`* | **ab-tabs/index.tsx** | Done |
|
|
90
|
+
| 10 | *`AbMenu`* | **ab-menu/index.tsx** | Done |
|
|
91
|
+
| 11 | *`AbCollapsible`* | **ab-collapsible/index.tsx** | Done |
|
|
92
|
+
| 12 | *`AbColor`* | **ab-color/index.tsx** | Done |
|
|
93
|
+
| 13 | *`AbInfo`* | **ab-info/index.tsx** | Done |
|
|
94
|
+
| 14 | *`AbLikeButton`* | **ab-like-button/index.tsx** | Done |
|
|
95
|
+
| 15 | *`AbSearchbar`* | **ab-searchbar/index.tsx** | Done |
|
|
96
|
+
| 16 | *`AbSearchResult`* | **ab-search-result/index.tsx** | Done |
|
|
97
|
+
| 17 | *`AbPagePaginator`* | **ab-page-paginator/index.tsx** | Done |
|
|
98
|
+
| 18 | *`AbPageSwitcher`* | **ab-page-switcher/index.tsx** | Done |
|
|
99
|
+
| 19 | *`AbProgressChips`* | **ab-progress-chips/index.tsx** | Done |
|
|
100
|
+
| 20 | *`AbPrice`* | **ab-price/index.tsx** | Done |
|
|
101
|
+
| 21 | *`AbPriceTag`* | **ab-price-tag/index.tsx** | Done |
|
|
102
|
+
| 22 | *`AbImage`* | **ab-image/index.tsx** | Done |
|
|
103
|
+
| 23 | *`AbBrand`* | **ab-brand/index.tsx** | Done |
|
|
104
|
+
| 24 | *`AbName`* | **ab-name/index.tsx** | Done |
|
|
105
|
+
| 25 | *`AbNavbar`* | **ab-navbar/index.tsx** | Done |
|
|
106
|
+
| 26 | *`AbSidebar`* | **ab-sidebar/index.tsx** | Done |
|
|
107
|
+
| 27 | *`AbText`* | **ab-text/index.tsx** | Done |
|
|
108
|
+
| 28 | *`AbTestHello`* | **ab-test-hello/index.tsx** | Done |
|
|
109
|
+
| 29 | *`AbKeyFeature`* | **ab-key-feature/index.tsx** | Done |
|
|
110
|
+
| 30 | *`AbMainHeadline`* | **ab-main-headline/index.tsx** | Done |
|
|
111
|
+
| 31 | *`AbPreview`* | **ab-preview/index.tsx** | Done |
|
|
112
|
+
| 32 | *`AbProduct`* | **ab-product/index.tsx** | Done |
|
|
113
|
+
| 33 | *`AbProductItem`* | **ab-product-item/index.tsx** | Done |
|
|
114
|
+
| 34 | *`AbSwitchBack`* | **ab-switch-back/index.tsx** | Done |
|
|
115
|
+
| 35 | *`AbDemoBox`* | **ab-demo-box/index.tsx** | Done |
|
|
116
|
+
| 36 | *`AbDemoCode`* | **ab-demo-code/index.tsx** | Done |
|
|
80
117
|
|
|
81
118
|
## Server Components
|
|
82
119
|
|
|
@@ -84,12 +121,21 @@ A list of all the **server components** and their current status:
|
|
|
84
121
|
|
|
85
122
|
| No. | Name | File | Status |
|
|
86
123
|
|:----|:-----|:-----|:-------|
|
|
87
|
-
| 1 | *`
|
|
88
|
-
| 2 | *`
|
|
89
|
-
| 3 | *`AbButton`* | **server/ab-button/index.tsx** |
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
| 1 | *`AbIcon`* | **server/ab-icon/index.tsx** | Done |
|
|
125
|
+
| 2 | *`AbLogo`* | **server/ab-logo/index.tsx** | Done |
|
|
126
|
+
| 3 | *`AbButton`* | **server/ab-button/index.tsx** | Done |
|
|
127
|
+
| 4 | *`AbAvatar`* | **server/ab-avatar/index.tsx** | Done |
|
|
128
|
+
| 5 | *`AbBadge`* | **server/ab-badge/index.tsx** | Done |
|
|
129
|
+
| 6 | *`AbPolygon`* | **server/ab-polygon/index.tsx** | Done |
|
|
130
|
+
| 7 | *`AbBrand`* | **server/ab-brand/index.tsx** | Done |
|
|
131
|
+
| 8 | *`AbName`* | **server/ab-name/index.tsx** | Done |
|
|
132
|
+
| 9 | *`AbNavbar`* | **server/ab-navbar/index.tsx** | Done |
|
|
133
|
+
| 10 | *`AbSidebar`* | **server/ab-sidebar/index.tsx** | Done |
|
|
134
|
+
| 11 | *`AbNavLink`* | **server/ab-nav-link/index.tsx** | Done |
|
|
135
|
+
| 12 | *`AbText`* | **server/ab-text/index.tsx** | Done |
|
|
136
|
+
| 13 | *`AbTestHello`* | **server/ab-test-hello/index.tsx** | Done |
|
|
137
|
+
| 14 | *`AbKeyFeature`* | **server/ab-key-feature/index.tsx** | Done |
|
|
138
|
+
| 15 | *`AbSwitchBack`* | **server/ab-switch-back/index.tsx** | Done |
|
|
93
139
|
|
|
94
140
|
|
|
95
141
|
## Learn More abElements
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license MIT
|
|
3
|
+
* ~~~~~~~~~~~~
|
|
4
|
+
* ab-nextjs-components
|
|
5
|
+
* ~~~~~~~~~~~~
|
|
6
|
+
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
|
+
*
|
|
8
|
+
* @project: ab-nextjs-components
|
|
9
|
+
* @name: AbAvatar - Client (port of lyd-avatar)
|
|
10
|
+
* @file: ab-avatar/index.tsx
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use client';
|
|
14
|
+
|
|
15
|
+
import type { ReactElement } from 'react';
|
|
16
|
+
import Image from 'next/image';
|
|
17
|
+
import Link from 'next/link';
|
|
18
|
+
import clsx from 'clsx';
|
|
19
|
+
import styles from './styles.module.css';
|
|
20
|
+
|
|
21
|
+
export interface AbAvatarProps {
|
|
22
|
+
isConnected: boolean;
|
|
23
|
+
isOnline: boolean;
|
|
24
|
+
icon?: 'person' | 'group';
|
|
25
|
+
version?: number;
|
|
26
|
+
image?: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
className?: string;
|
|
29
|
+
width?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
/** Connected href (default `/`). Offline href (default `/`). No app-brand routes. */
|
|
32
|
+
href?: string;
|
|
33
|
+
offlineHref?: string;
|
|
34
|
+
isActive?: boolean;
|
|
35
|
+
noIndicator?: boolean;
|
|
36
|
+
onClick?: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const AbAvatar = ({
|
|
40
|
+
isConnected,
|
|
41
|
+
isOnline,
|
|
42
|
+
icon = 'person',
|
|
43
|
+
version = 1,
|
|
44
|
+
image,
|
|
45
|
+
label = 'Avatar',
|
|
46
|
+
className,
|
|
47
|
+
width = 48,
|
|
48
|
+
height = 48,
|
|
49
|
+
href = '/',
|
|
50
|
+
offlineHref = '/',
|
|
51
|
+
isActive = false,
|
|
52
|
+
noIndicator = false,
|
|
53
|
+
onClick,
|
|
54
|
+
}: AbAvatarProps): ReactElement => {
|
|
55
|
+
const src = image ?? `/assets/images/avatars/avatar_${version}.png`;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className={clsx('AbAvatar', styles.abAvatar, className)} onClick={onClick}>
|
|
59
|
+
{isConnected ? (
|
|
60
|
+
<Link href={href} className={clsx(styles.abAvatarLink, isActive && styles.isActive)}>
|
|
61
|
+
<span className={styles.abAvatarImageWrap}>
|
|
62
|
+
<Image
|
|
63
|
+
src={src}
|
|
64
|
+
className={styles.abAvatarImage}
|
|
65
|
+
width={width}
|
|
66
|
+
height={height}
|
|
67
|
+
alt={label}
|
|
68
|
+
/>
|
|
69
|
+
{!noIndicator ? (
|
|
70
|
+
<span
|
|
71
|
+
className={clsx(styles.abAvatarIndicator, !isOnline && styles.isOffline)}
|
|
72
|
+
aria-hidden
|
|
73
|
+
/>
|
|
74
|
+
) : null}
|
|
75
|
+
</span>
|
|
76
|
+
</Link>
|
|
77
|
+
) : (
|
|
78
|
+
<Link href={offlineHref} className={styles.abAvatarOffline}>
|
|
79
|
+
<span className={clsx('material-symbols-rounded', styles.abAvatarIcon)} aria-hidden>
|
|
80
|
+
{icon}
|
|
81
|
+
</span>
|
|
82
|
+
{label ? <span className={styles.abAvatarLabel}>{label}</span> : null}
|
|
83
|
+
</Link>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default AbAvatar;
|
|
90
|
+
export { AbAvatar };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* AbAvatar — plain CSS (no Tailwind apply) */
|
|
2
|
+
.abAvatar {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
width: auto;
|
|
6
|
+
height: auto;
|
|
7
|
+
pointer-events: auto;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.abAvatarLink {
|
|
11
|
+
position: relative;
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
text-decoration: none;
|
|
14
|
+
opacity: 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.isActive {
|
|
18
|
+
opacity: 0.9;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.abAvatarImageWrap {
|
|
22
|
+
position: relative;
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.abAvatarImage {
|
|
27
|
+
border-radius: 9999px;
|
|
28
|
+
object-fit: cover;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.abAvatarIndicator {
|
|
32
|
+
position: absolute;
|
|
33
|
+
right: 0;
|
|
34
|
+
bottom: 0;
|
|
35
|
+
width: 1rem;
|
|
36
|
+
height: 1rem;
|
|
37
|
+
border-radius: 9999px;
|
|
38
|
+
border: 2px solid var(--ab-surface, #fff);
|
|
39
|
+
background: #22c55e;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.abAvatarIndicator.isOffline {
|
|
43
|
+
background: #ef4444;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.abAvatarOffline {
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 0.25rem;
|
|
51
|
+
text-decoration: none;
|
|
52
|
+
color: inherit;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.abAvatarIcon {
|
|
56
|
+
font-size: 1.75rem;
|
|
57
|
+
line-height: 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.abAvatarLabel {
|
|
61
|
+
font-size: 0.75rem;
|
|
62
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license MIT
|
|
3
|
+
* ~~~~~~~~~~~~
|
|
4
|
+
* ab-nextjs-components
|
|
5
|
+
* ~~~~~~~~~~~~
|
|
6
|
+
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
|
+
*
|
|
8
|
+
* @project: ab-nextjs-components
|
|
9
|
+
* @name: AbBalloon - Client (Adapt — title= locked)
|
|
10
|
+
* @file: ab-balloon/index.tsx
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use client';
|
|
14
|
+
|
|
15
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
16
|
+
import clsx from 'clsx';
|
|
17
|
+
import styles from './styles.module.css';
|
|
18
|
+
|
|
19
|
+
export interface AbBalloonProps {
|
|
20
|
+
/** Native tooltip text — locked API (`title=`). */
|
|
21
|
+
title?: string;
|
|
22
|
+
className?: string;
|
|
23
|
+
hidden?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* AbBalloon — Phase 2 Adapt.
|
|
30
|
+
* Uses the native `title=` tooltip (locked). Not a port of the LYD hover balloon.
|
|
31
|
+
*/
|
|
32
|
+
const AbBalloon = ({
|
|
33
|
+
title,
|
|
34
|
+
className,
|
|
35
|
+
hidden = false,
|
|
36
|
+
disabled = false,
|
|
37
|
+
children,
|
|
38
|
+
}: AbBalloonProps): ReactElement => {
|
|
39
|
+
if (hidden) {
|
|
40
|
+
return <>{children}</>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<span
|
|
45
|
+
className={clsx('AbBalloon', styles.abBalloon, className)}
|
|
46
|
+
title={disabled ? undefined : title}
|
|
47
|
+
data-disabled={disabled ? 'true' : 'false'}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
</span>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default AbBalloon;
|
|
55
|
+
export { AbBalloon };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* AbBalloon — title= Adapt (no hover chrome; native tooltip only) */
|
|
2
|
+
.abBalloon {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
max-width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.abBalloon[data-disabled='true'] {
|
|
11
|
+
pointer-events: none;
|
|
12
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license MIT
|
|
3
|
+
* ~~~~~~~~~~~~
|
|
4
|
+
* ab-nextjs-components
|
|
5
|
+
* ~~~~~~~~~~~~
|
|
6
|
+
* Copyright (c) 2026 Abraham Ukachi. The abElements Project.
|
|
7
|
+
*
|
|
8
|
+
* @project: ab-nextjs-components
|
|
9
|
+
* @name: AbBrand - Client (port of lyd-brand)
|
|
10
|
+
* @file: ab-brand/index.tsx
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use client';
|
|
14
|
+
|
|
15
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
16
|
+
import Link from 'next/link';
|
|
17
|
+
import Image from 'next/image';
|
|
18
|
+
import clsx from 'clsx';
|
|
19
|
+
import styles from './styles.module.css';
|
|
20
|
+
|
|
21
|
+
export interface AbBrandProps {
|
|
22
|
+
className?: string;
|
|
23
|
+
type?: 'default' | 'blurred';
|
|
24
|
+
effect?: 'normal' | 'zoom';
|
|
25
|
+
hidden?: boolean;
|
|
26
|
+
image: string;
|
|
27
|
+
imageAlt?: string;
|
|
28
|
+
logo: string;
|
|
29
|
+
logoAlt?: string;
|
|
30
|
+
name: string;
|
|
31
|
+
shortName: string;
|
|
32
|
+
description: string;
|
|
33
|
+
autohideDescription?: boolean;
|
|
34
|
+
color?: string;
|
|
35
|
+
accentColor?: string;
|
|
36
|
+
link: string;
|
|
37
|
+
ctaLabel: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const AbBrand = ({
|
|
41
|
+
className,
|
|
42
|
+
type = 'default',
|
|
43
|
+
effect = 'normal',
|
|
44
|
+
hidden = false,
|
|
45
|
+
image,
|
|
46
|
+
imageAlt,
|
|
47
|
+
logo,
|
|
48
|
+
logoAlt,
|
|
49
|
+
name,
|
|
50
|
+
shortName,
|
|
51
|
+
description,
|
|
52
|
+
autohideDescription = false,
|
|
53
|
+
color,
|
|
54
|
+
accentColor,
|
|
55
|
+
link,
|
|
56
|
+
ctaLabel,
|
|
57
|
+
}: AbBrandProps): ReactElement => {
|
|
58
|
+
const style = {
|
|
59
|
+
'--ab-brand-color': color,
|
|
60
|
+
'--ab-brand-accent': accentColor,
|
|
61
|
+
} as CSSProperties;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Link
|
|
65
|
+
href={link}
|
|
66
|
+
hidden={hidden}
|
|
67
|
+
data-type={type}
|
|
68
|
+
data-effect={effect}
|
|
69
|
+
data-autohide-desc={autohideDescription ? 'true' : 'false'}
|
|
70
|
+
className={clsx('AbBrand', styles.abBrand, className)}
|
|
71
|
+
style={style}
|
|
72
|
+
>
|
|
73
|
+
<div className={styles.abBrandImageWrap}>
|
|
74
|
+
<Image className={styles.abBrandImage} src={image} alt={imageAlt ?? name} width={640} height={360} unoptimized />
|
|
75
|
+
</div>
|
|
76
|
+
<div className={styles.abBrandMeta}>
|
|
77
|
+
<Image className={styles.abBrandLogo} src={logo} alt={logoAlt ?? shortName} width={40} height={40} unoptimized />
|
|
78
|
+
<span className={styles.abBrandShort}>{shortName}</span>
|
|
79
|
+
<h3 className={styles.abBrandName}>{name}</h3>
|
|
80
|
+
<p className={styles.abBrandDesc}>{description}</p>
|
|
81
|
+
<span className={styles.abBrandCta}>{ctaLabel}</span>
|
|
82
|
+
</div>
|
|
83
|
+
</Link>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default AbBrand;
|
|
88
|
+
export { AbBrand };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
.abBrand {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 0.75rem;
|
|
6
|
+
padding: 1rem;
|
|
7
|
+
border-radius: 1rem;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
text-decoration: none;
|
|
10
|
+
color: inherit;
|
|
11
|
+
background: color-mix(in srgb, var(--ab-surface-container, #f3edf7) 90%, transparent);
|
|
12
|
+
}
|
|
13
|
+
.abBrand[data-type='blurred'] {
|
|
14
|
+
backdrop-filter: blur(12px);
|
|
15
|
+
}
|
|
16
|
+
.abBrand[data-effect='zoom']:hover .abBrandImage {
|
|
17
|
+
transform: scale(1.05);
|
|
18
|
+
}
|
|
19
|
+
.abBrandImageWrap {
|
|
20
|
+
position: relative;
|
|
21
|
+
width: 100%;
|
|
22
|
+
aspect-ratio: 16 / 9;
|
|
23
|
+
border-radius: 0.75rem;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
background: #ddd;
|
|
26
|
+
}
|
|
27
|
+
.abBrandImage {
|
|
28
|
+
object-fit: cover;
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
transition: transform 0.35s ease;
|
|
32
|
+
}
|
|
33
|
+
.abBrandLogo {
|
|
34
|
+
width: 2.5rem;
|
|
35
|
+
height: 2.5rem;
|
|
36
|
+
object-fit: contain;
|
|
37
|
+
}
|
|
38
|
+
.abBrandMeta { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
39
|
+
.abBrandName { margin: 0; font-size: 1.125rem; font-weight: 700; }
|
|
40
|
+
.abBrandShort { font-size: 0.75rem; font-weight: 600; opacity: 0.7; text-transform: uppercase; }
|
|
41
|
+
.abBrandDesc { margin: 0; font-size: 0.875rem; opacity: 0.8; }
|
|
42
|
+
.abBrand[data-autohide-desc='true'] .abBrandDesc { display: none; }
|
|
43
|
+
.abBrandCta {
|
|
44
|
+
align-self: flex-start;
|
|
45
|
+
border: none;
|
|
46
|
+
border-radius: 9999px;
|
|
47
|
+
padding: 0.4rem 0.9rem;
|
|
48
|
+
background: var(--ab-brand-accent, var(--ab-primary, #6750a4));
|
|
49
|
+
color: #fff;
|
|
50
|
+
font: inherit;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
text-decoration: none;
|
|
54
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
*
|
|
27
|
+
* @project: ab-nextjs-components
|
|
28
|
+
* @name: AbButton - Client (port of lyd-button)
|
|
29
|
+
* @file: ab-button/index.tsx
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
'use client';
|
|
33
|
+
|
|
34
|
+
import type { CSSProperties, MouseEvent, ReactNode } from 'react';
|
|
35
|
+
import { forwardRef } from 'react';
|
|
36
|
+
import clsx from 'clsx';
|
|
37
|
+
import styles from './styles.module.css';
|
|
38
|
+
|
|
39
|
+
export type AbButtonType = 'contained' | 'outlined' | 'text';
|
|
40
|
+
export type AbButtonColor = 'primary' | 'secondary' | 'tertiary' | 'white' | 'black';
|
|
41
|
+
|
|
42
|
+
export interface AbButtonProps {
|
|
43
|
+
id?: string;
|
|
44
|
+
type?: AbButtonType;
|
|
45
|
+
htmlType?: 'button' | 'submit' | 'reset';
|
|
46
|
+
className?: string;
|
|
47
|
+
spinnerClassName?: string;
|
|
48
|
+
hidden?: boolean;
|
|
49
|
+
style?: CSSProperties;
|
|
50
|
+
iconStyle?: CSSProperties;
|
|
51
|
+
rounded?: boolean;
|
|
52
|
+
raised?: boolean;
|
|
53
|
+
shrinks?: boolean;
|
|
54
|
+
expands?: boolean;
|
|
55
|
+
withIcon?: boolean;
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
isLoading?: boolean;
|
|
58
|
+
iconPosition?: 'left' | 'right';
|
|
59
|
+
label: string;
|
|
60
|
+
icon?: string;
|
|
61
|
+
color?: AbButtonColor;
|
|
62
|
+
href?: string;
|
|
63
|
+
children?: ReactNode;
|
|
64
|
+
onClick?: (event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const AbButton = forwardRef<HTMLButtonElement | HTMLAnchorElement, AbButtonProps>(function AbButton(props, ref) {
|
|
68
|
+
const {
|
|
69
|
+
id,
|
|
70
|
+
type = 'contained',
|
|
71
|
+
htmlType = 'button',
|
|
72
|
+
className,
|
|
73
|
+
spinnerClassName,
|
|
74
|
+
hidden = false,
|
|
75
|
+
style,
|
|
76
|
+
iconStyle,
|
|
77
|
+
rounded = true,
|
|
78
|
+
raised = false,
|
|
79
|
+
shrinks = false,
|
|
80
|
+
expands = false,
|
|
81
|
+
withIcon = false,
|
|
82
|
+
disabled = false,
|
|
83
|
+
isLoading = false,
|
|
84
|
+
iconPosition = 'left',
|
|
85
|
+
label,
|
|
86
|
+
icon,
|
|
87
|
+
color = 'primary',
|
|
88
|
+
href,
|
|
89
|
+
children,
|
|
90
|
+
onClick,
|
|
91
|
+
} = props;
|
|
92
|
+
|
|
93
|
+
const classNames = clsx(
|
|
94
|
+
'AbButton',
|
|
95
|
+
styles.abButton,
|
|
96
|
+
className,
|
|
97
|
+
rounded && styles.abButtonRounded,
|
|
98
|
+
type === 'contained' && styles.isContained,
|
|
99
|
+
(disabled || isLoading) && styles.isDisabled,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const dataAttrs = {
|
|
103
|
+
'data-type': type,
|
|
104
|
+
'data-raised': raised ? 'true' : 'false',
|
|
105
|
+
'data-shrinks': shrinks ? 'true' : 'false',
|
|
106
|
+
'data-expands': expands ? 'true' : 'false',
|
|
107
|
+
'data-rounded': rounded ? 'true' : 'false',
|
|
108
|
+
'data-iconpos': iconPosition,
|
|
109
|
+
'data-color': color,
|
|
110
|
+
} as const;
|
|
111
|
+
|
|
112
|
+
const content = (
|
|
113
|
+
<>
|
|
114
|
+
{withIcon && iconPosition === 'left' && icon ? (
|
|
115
|
+
<span className={clsx('material-symbols-rounded', 'icon', styles.abButtonIcon)} style={iconStyle}>{icon}</span>
|
|
116
|
+
) : null}
|
|
117
|
+
<span className={clsx(styles.abButtonSpinnerSlot, !isLoading && styles.hidden)}>
|
|
118
|
+
<span className={clsx('spinner', 'dot-3', styles.abButtonSpinner, spinnerClassName)} />
|
|
119
|
+
</span>
|
|
120
|
+
<span className={clsx(isLoading && styles.invisible)}>{children ?? label}</span>
|
|
121
|
+
{withIcon && iconPosition === 'right' && icon ? (
|
|
122
|
+
<span className={clsx('material-symbols-rounded', 'icon', styles.abButtonIcon)} style={iconStyle}>{icon}</span>
|
|
123
|
+
) : null}
|
|
124
|
+
</>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (href) {
|
|
128
|
+
return (
|
|
129
|
+
<a
|
|
130
|
+
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
131
|
+
id={id}
|
|
132
|
+
href={disabled ? undefined : href}
|
|
133
|
+
role="button"
|
|
134
|
+
className={classNames}
|
|
135
|
+
style={style}
|
|
136
|
+
hidden={hidden}
|
|
137
|
+
aria-disabled={disabled || isLoading}
|
|
138
|
+
onClick={onClick}
|
|
139
|
+
{...dataAttrs}
|
|
140
|
+
>
|
|
141
|
+
{content}
|
|
142
|
+
</a>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
ref={ref as React.Ref<HTMLButtonElement>}
|
|
149
|
+
id={id}
|
|
150
|
+
type={htmlType}
|
|
151
|
+
className={classNames}
|
|
152
|
+
style={style}
|
|
153
|
+
hidden={hidden}
|
|
154
|
+
disabled={disabled || isLoading}
|
|
155
|
+
onClick={onClick}
|
|
156
|
+
{...dataAttrs}
|
|
157
|
+
>
|
|
158
|
+
{content}
|
|
159
|
+
</button>
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export default AbButton;
|
|
164
|
+
export { AbButton };
|