@behivetech/organisms.hero-banner 0.0.0-stage → 0.7.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/README.md +17 -2
- package/dist/index.cjs +318 -0
- package/dist/index.css +118 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +298 -0
- package/docs.json +78 -0
- package/hero-banner.example.tsx +20 -0
- package/hero-banner.module.scss +163 -0
- package/hero-banner.tsx +97 -0
- package/index.ts +13 -0
- package/package.json +60 -3
package/README.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HeroBanner
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TODO: Add component description.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
import { HeroBanner } from "@behivetech/organisms.hero-banner";
|
|
9
|
+
|
|
10
|
+
<HeroBanner>Content</HeroBanner>;
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Props
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
| ----------- | ----------- | ------- | ---------------------- |
|
|
17
|
+
| `children` | `ReactNode` | — | Component content |
|
|
18
|
+
| `className` | `string` | — | Additional class names |
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
|
|
34
|
+
var require_classnames = __commonJS({
|
|
35
|
+
"../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports2, module2) {
|
|
36
|
+
"use strict";
|
|
37
|
+
(function() {
|
|
38
|
+
"use strict";
|
|
39
|
+
var hasOwn = {}.hasOwnProperty;
|
|
40
|
+
function classNames() {
|
|
41
|
+
var classes = "";
|
|
42
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
43
|
+
var arg = arguments[i];
|
|
44
|
+
if (arg) {
|
|
45
|
+
classes = appendClass(classes, parseValue(arg));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return classes;
|
|
49
|
+
}
|
|
50
|
+
function parseValue(arg) {
|
|
51
|
+
if (typeof arg === "string" || typeof arg === "number") {
|
|
52
|
+
return arg;
|
|
53
|
+
}
|
|
54
|
+
if (typeof arg !== "object") {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(arg)) {
|
|
58
|
+
return classNames.apply(null, arg);
|
|
59
|
+
}
|
|
60
|
+
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
|
|
61
|
+
return arg.toString();
|
|
62
|
+
}
|
|
63
|
+
var classes = "";
|
|
64
|
+
for (var key in arg) {
|
|
65
|
+
if (hasOwn.call(arg, key) && arg[key]) {
|
|
66
|
+
classes = appendClass(classes, key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return classes;
|
|
70
|
+
}
|
|
71
|
+
function appendClass(value, newClass) {
|
|
72
|
+
if (!newClass) {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
if (value) {
|
|
76
|
+
return value + " " + newClass;
|
|
77
|
+
}
|
|
78
|
+
return value + newClass;
|
|
79
|
+
}
|
|
80
|
+
if (typeof module2 !== "undefined" && module2.exports) {
|
|
81
|
+
classNames.default = classNames;
|
|
82
|
+
module2.exports = classNames;
|
|
83
|
+
} else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
|
|
84
|
+
define("classnames", [], function() {
|
|
85
|
+
return classNames;
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
window.classNames = classNames;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// index.ts
|
|
95
|
+
var index_exports = {};
|
|
96
|
+
__export(index_exports, {
|
|
97
|
+
HeroBanner: () => HeroBanner,
|
|
98
|
+
heroBannerMeta: () => heroBannerMeta,
|
|
99
|
+
heroBannerMigrations: () => heroBannerMigrations,
|
|
100
|
+
heroBannerSchema: () => heroBannerSchema,
|
|
101
|
+
meta: () => heroBannerMeta,
|
|
102
|
+
migrations: () => heroBannerMigrations,
|
|
103
|
+
schema: () => heroBannerSchema
|
|
104
|
+
});
|
|
105
|
+
module.exports = __toCommonJS(index_exports);
|
|
106
|
+
|
|
107
|
+
// ../../../../packages/registry/class-names/dist/index.js
|
|
108
|
+
var import_classnames = __toESM(require_classnames(), 1);
|
|
109
|
+
var getClassName = ({
|
|
110
|
+
className,
|
|
111
|
+
modifiers = {},
|
|
112
|
+
rootClass,
|
|
113
|
+
styles = {}
|
|
114
|
+
}) => {
|
|
115
|
+
function getModifiers() {
|
|
116
|
+
const updatedModifiers = {};
|
|
117
|
+
const modifierKeys = Object.keys(modifiers);
|
|
118
|
+
if (modifierKeys.length) {
|
|
119
|
+
modifierKeys.forEach((modKey) => {
|
|
120
|
+
const classKey = `${rootClass}--${modKey}`;
|
|
121
|
+
const newClassName = styles[classKey] || classKey;
|
|
122
|
+
updatedModifiers[newClassName] = modifiers[modKey] ?? false;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return updatedModifiers;
|
|
126
|
+
}
|
|
127
|
+
function getChildClass(childClassName) {
|
|
128
|
+
const classKey = `${rootClass}__${childClassName}`;
|
|
129
|
+
return styles[classKey] || classKey;
|
|
130
|
+
}
|
|
131
|
+
function getRootStyles() {
|
|
132
|
+
return (0, import_classnames.default)(
|
|
133
|
+
className,
|
|
134
|
+
{
|
|
135
|
+
...getModifiers()
|
|
136
|
+
},
|
|
137
|
+
styles[rootClass] || rootClass
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return [getRootStyles(), getChildClass];
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// hero-banner.module.scss
|
|
144
|
+
var hero_banner_module_default = {
|
|
145
|
+
"BHT__hero-banner": "BHT__hero-banner",
|
|
146
|
+
"BHT__hero-banner__content": "BHT__hero-banner__content",
|
|
147
|
+
"BHT__hero-banner__headline": "BHT__hero-banner__headline",
|
|
148
|
+
"BHT__hero-banner__subtext": "BHT__hero-banner__subtext",
|
|
149
|
+
"BHT__hero-banner__ctas": "BHT__hero-banner__ctas",
|
|
150
|
+
"BHT__hero-banner__cta": "BHT__hero-banner__cta",
|
|
151
|
+
"BHT__hero-banner__cta-secondary": "BHT__hero-banner__cta-secondary",
|
|
152
|
+
"BHT__hero-banner--align-left": "BHT__hero-banner--align-left",
|
|
153
|
+
"BHT__hero-banner--align-right": "BHT__hero-banner--align-right"
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// hero-banner.tsx
|
|
157
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
158
|
+
var HeroBanner = ({
|
|
159
|
+
headline,
|
|
160
|
+
subtext,
|
|
161
|
+
imageUrl,
|
|
162
|
+
ctaLabel,
|
|
163
|
+
ctaHref,
|
|
164
|
+
ctaButtons,
|
|
165
|
+
align = "center",
|
|
166
|
+
className,
|
|
167
|
+
children
|
|
168
|
+
}) => {
|
|
169
|
+
const [rootClass, getChildClass] = getClassName({
|
|
170
|
+
className,
|
|
171
|
+
rootClass: "BHT__hero-banner",
|
|
172
|
+
modifiers: { [`align-${align}`]: true },
|
|
173
|
+
styles: hero_banner_module_default
|
|
174
|
+
});
|
|
175
|
+
const ctas = (Array.isArray(ctaButtons) && ctaButtons.length > 0 ? ctaButtons : [{ label: ctaLabel, href: ctaHref, variant: "primary" }]).filter(
|
|
176
|
+
(cta) => Boolean(cta) && Boolean(cta.label) && Boolean(cta.href)
|
|
177
|
+
);
|
|
178
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
179
|
+
"div",
|
|
180
|
+
{
|
|
181
|
+
className: rootClass,
|
|
182
|
+
style: imageUrl ? { backgroundImage: `url(${imageUrl})` } : void 0,
|
|
183
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: getChildClass("content"), children: [
|
|
184
|
+
headline && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: getChildClass("headline"), children: headline }),
|
|
185
|
+
subtext && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: getChildClass("subtext"), children: subtext }),
|
|
186
|
+
ctas.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: getChildClass("ctas"), children: ctas.map((cta, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
187
|
+
"a",
|
|
188
|
+
{
|
|
189
|
+
className: `${getChildClass("cta")} ${cta.variant === "secondary" ? getChildClass("cta-secondary") : ""}`.trim(),
|
|
190
|
+
href: cta.href,
|
|
191
|
+
children: cta.label
|
|
192
|
+
},
|
|
193
|
+
`${cta.href}-${index}`
|
|
194
|
+
)) }),
|
|
195
|
+
children
|
|
196
|
+
] })
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// hero-banner.schema.ts
|
|
202
|
+
var heroBannerSchema = {
|
|
203
|
+
fields: [
|
|
204
|
+
{
|
|
205
|
+
name: "headline",
|
|
206
|
+
type: "string",
|
|
207
|
+
label: "Headline",
|
|
208
|
+
placeholder: "Enter headline\u2026",
|
|
209
|
+
required: true,
|
|
210
|
+
defaultValue: "Welcome"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "subtext",
|
|
214
|
+
type: "richtext",
|
|
215
|
+
label: "Subtext",
|
|
216
|
+
placeholder: "Supporting text\u2026",
|
|
217
|
+
defaultValue: ""
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "imageUrl",
|
|
221
|
+
type: "image",
|
|
222
|
+
label: "Background Image",
|
|
223
|
+
defaultValue: ""
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "ctaButtons",
|
|
227
|
+
type: "array",
|
|
228
|
+
label: "Buttons",
|
|
229
|
+
hint: "The first button is the main action. Leave empty for a banner with no call to action.",
|
|
230
|
+
defaultValue: [],
|
|
231
|
+
itemSchema: [
|
|
232
|
+
{
|
|
233
|
+
name: "label",
|
|
234
|
+
type: "string",
|
|
235
|
+
label: "Label",
|
|
236
|
+
placeholder: "Get started",
|
|
237
|
+
required: true
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "href",
|
|
241
|
+
type: "url",
|
|
242
|
+
label: "Link",
|
|
243
|
+
placeholder: "https://\u2026",
|
|
244
|
+
required: true
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "variant",
|
|
248
|
+
type: "select",
|
|
249
|
+
label: "Style",
|
|
250
|
+
defaultValue: "primary",
|
|
251
|
+
options: [
|
|
252
|
+
{ label: "Primary", value: "primary" },
|
|
253
|
+
{ label: "Secondary", value: "secondary" }
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "align",
|
|
260
|
+
type: "select",
|
|
261
|
+
label: "Text Alignment",
|
|
262
|
+
defaultValue: "center",
|
|
263
|
+
options: [
|
|
264
|
+
{ label: "Left", value: "left" },
|
|
265
|
+
{ label: "Center", value: "center" },
|
|
266
|
+
{ label: "Right", value: "right" }
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// hero-banner.meta.ts
|
|
273
|
+
var heroBannerMeta = {
|
|
274
|
+
name: "HeroBanner",
|
|
275
|
+
displayName: "Hero Banner",
|
|
276
|
+
description: "Full-width hero section with headline, subtext, and call-to-action",
|
|
277
|
+
icon: "\u{1F3AF}",
|
|
278
|
+
tags: ["marketing", "layout"],
|
|
279
|
+
acceptsChildren: false
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// hero-banner.migrations.ts
|
|
283
|
+
var heroBannerMigrations = [
|
|
284
|
+
{
|
|
285
|
+
fromVersion: "0.3.1",
|
|
286
|
+
toVersion: "0.4.0",
|
|
287
|
+
migrate(props) {
|
|
288
|
+
const { ctaLabel, ctaHref, ...rest } = props;
|
|
289
|
+
const hasCta = typeof ctaLabel === "string" && ctaLabel !== "" && typeof ctaHref === "string" && ctaHref !== "";
|
|
290
|
+
if (!hasCta) return rest;
|
|
291
|
+
const existing = rest.ctaButtons;
|
|
292
|
+
if (Array.isArray(existing) && existing.length > 0) return rest;
|
|
293
|
+
return {
|
|
294
|
+
...rest,
|
|
295
|
+
ctaButtons: [{ label: ctaLabel, href: ctaHref, variant: "primary" }]
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
];
|
|
300
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
301
|
+
0 && (module.exports = {
|
|
302
|
+
HeroBanner,
|
|
303
|
+
heroBannerMeta,
|
|
304
|
+
heroBannerMigrations,
|
|
305
|
+
heroBannerSchema,
|
|
306
|
+
meta,
|
|
307
|
+
migrations,
|
|
308
|
+
schema
|
|
309
|
+
});
|
|
310
|
+
/*! Bundled license information:
|
|
311
|
+
|
|
312
|
+
classnames/index.js:
|
|
313
|
+
(*!
|
|
314
|
+
Copyright (c) 2018 Jed Watson.
|
|
315
|
+
Licensed under the MIT License (MIT), see
|
|
316
|
+
http://jedwatson.github.io/classnames
|
|
317
|
+
*)
|
|
318
|
+
*/
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/* esbuild-sass-plugin:css-chunkhero-banner.module.scss */
|
|
2
|
+
.BHT__hero-banner {
|
|
3
|
+
--hero-banner-on-image: #ffffff;
|
|
4
|
+
position: relative;
|
|
5
|
+
min-height: 30rem;
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
background-size: cover;
|
|
9
|
+
background-position: center;
|
|
10
|
+
background-color: var(--md-sys-color-surface, var(--color-surface));
|
|
11
|
+
padding: var(--spacing-16, 4rem) var(--spacing-8, 2rem);
|
|
12
|
+
}
|
|
13
|
+
.BHT__hero-banner::before {
|
|
14
|
+
content: "";
|
|
15
|
+
position: absolute;
|
|
16
|
+
inset: 0;
|
|
17
|
+
background: var(--md-sys-color-scrim, var(--color-overlay));
|
|
18
|
+
opacity: 0.4;
|
|
19
|
+
}
|
|
20
|
+
.BHT__hero-banner__content {
|
|
21
|
+
position: relative;
|
|
22
|
+
z-index: 1;
|
|
23
|
+
max-width: 45rem;
|
|
24
|
+
width: 100%;
|
|
25
|
+
margin: 0 auto;
|
|
26
|
+
text-align: center;
|
|
27
|
+
}
|
|
28
|
+
.BHT__hero-banner__headline {
|
|
29
|
+
font-family: var(--md-sys-typescale-display-medium-font, var(--font-heading));
|
|
30
|
+
font-size: var(--md-sys-typescale-display-medium-size, 2.8125rem);
|
|
31
|
+
font-weight: var(--md-sys-typescale-display-medium-weight, 400);
|
|
32
|
+
line-height: var(--md-sys-typescale-display-medium-line-height, 3.25rem);
|
|
33
|
+
letter-spacing: var(--md-sys-typescale-display-medium-tracking, 0em);
|
|
34
|
+
color: var(--hero-banner-on-image);
|
|
35
|
+
margin: 0 0 var(--spacing-4, 1rem);
|
|
36
|
+
}
|
|
37
|
+
.BHT__hero-banner__subtext {
|
|
38
|
+
font-family: var(--md-sys-typescale-title-large-font, var(--font-body));
|
|
39
|
+
font-size: var(--md-sys-typescale-title-large-size, 1.375rem);
|
|
40
|
+
font-weight: var(--md-sys-typescale-title-large-weight, 400);
|
|
41
|
+
line-height: var(--md-sys-typescale-title-large-line-height, 1.75rem);
|
|
42
|
+
letter-spacing: var(--md-sys-typescale-title-large-tracking, 0em);
|
|
43
|
+
color: color-mix(in srgb, var(--hero-banner-on-image) 85%, transparent);
|
|
44
|
+
margin: 0 0 var(--spacing-6, 1.5rem);
|
|
45
|
+
}
|
|
46
|
+
.BHT__hero-banner__ctas {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-wrap: wrap;
|
|
49
|
+
gap: var(--spacing-3, 0.75rem);
|
|
50
|
+
justify-content: center;
|
|
51
|
+
}
|
|
52
|
+
.BHT__hero-banner__cta {
|
|
53
|
+
position: relative;
|
|
54
|
+
isolation: isolate;
|
|
55
|
+
display: inline-block;
|
|
56
|
+
padding: var(--spacing-3, 0.75rem) var(--spacing-8, 2rem);
|
|
57
|
+
background: var(--md-sys-color-primary, var(--color-primary));
|
|
58
|
+
color: var(--md-sys-color-on-primary, var(--color-on-primary));
|
|
59
|
+
font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
|
|
60
|
+
font-size: var(--md-sys-typescale-title-medium-size, 1rem);
|
|
61
|
+
font-weight: var(--md-sys-typescale-label-large-weight, 500);
|
|
62
|
+
line-height: var(--md-sys-typescale-title-medium-line-height, 1.5rem);
|
|
63
|
+
letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
|
|
64
|
+
border-radius: var(--md-sys-shape-corner-full, 9999px);
|
|
65
|
+
text-decoration: none;
|
|
66
|
+
}
|
|
67
|
+
.BHT__hero-banner__cta::after {
|
|
68
|
+
content: "";
|
|
69
|
+
position: absolute;
|
|
70
|
+
inset: 0;
|
|
71
|
+
border-radius: inherit;
|
|
72
|
+
background: currentColor;
|
|
73
|
+
opacity: 0;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
transition: opacity var(--md-sys-motion-duration-short-2, 100ms) var(--md-sys-motion-easing-standard, ease);
|
|
76
|
+
}
|
|
77
|
+
.BHT__hero-banner__cta:hover::after {
|
|
78
|
+
opacity: var(--md-sys-state-hover-opacity, 0.08);
|
|
79
|
+
}
|
|
80
|
+
.BHT__hero-banner__cta:focus-visible::after {
|
|
81
|
+
opacity: var(--md-sys-state-focus-opacity, 0.1);
|
|
82
|
+
}
|
|
83
|
+
.BHT__hero-banner__cta:active::after {
|
|
84
|
+
opacity: var(--md-sys-state-pressed-opacity, 0.1);
|
|
85
|
+
}
|
|
86
|
+
.BHT__hero-banner__cta::before {
|
|
87
|
+
content: "";
|
|
88
|
+
position: absolute;
|
|
89
|
+
left: 0;
|
|
90
|
+
right: 0;
|
|
91
|
+
top: 50%;
|
|
92
|
+
translate: 0 -50%;
|
|
93
|
+
height: 100%;
|
|
94
|
+
min-height: 48px;
|
|
95
|
+
}
|
|
96
|
+
.BHT__hero-banner__cta:focus-visible {
|
|
97
|
+
outline: 3px solid var(--md-sys-color-secondary, var(--color-border-focus));
|
|
98
|
+
outline-offset: 2px;
|
|
99
|
+
}
|
|
100
|
+
.BHT__hero-banner__cta-secondary {
|
|
101
|
+
background: transparent;
|
|
102
|
+
color: var(--hero-banner-on-image);
|
|
103
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hero-banner-on-image) 70%, transparent);
|
|
104
|
+
}
|
|
105
|
+
.BHT__hero-banner--align-left .BHT__hero-banner__content {
|
|
106
|
+
text-align: left;
|
|
107
|
+
margin-left: 0;
|
|
108
|
+
}
|
|
109
|
+
.BHT__hero-banner--align-left .BHT__hero-banner__ctas {
|
|
110
|
+
justify-content: flex-start;
|
|
111
|
+
}
|
|
112
|
+
.BHT__hero-banner--align-right .BHT__hero-banner__content {
|
|
113
|
+
text-align: right;
|
|
114
|
+
margin-right: 0;
|
|
115
|
+
}
|
|
116
|
+
.BHT__hero-banner--align-right .BHT__hero-banner__ctas {
|
|
117
|
+
justify-content: flex-end;
|
|
118
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { ComponentSchema, ComponentMeta, Migration } from '@behivetech/cms.types';
|
|
4
|
+
|
|
5
|
+
/** One call-to-action link in `ctaButtons`. */
|
|
6
|
+
interface HeroBannerCta {
|
|
7
|
+
/** Link text; a button with no label is skipped */
|
|
8
|
+
label?: string;
|
|
9
|
+
/** Destination URL; a button with no href is skipped */
|
|
10
|
+
href?: string;
|
|
11
|
+
/** Visual weight — the first button usually leads, the rest support */
|
|
12
|
+
variant?: "primary" | "secondary";
|
|
13
|
+
}
|
|
14
|
+
interface HeroBannerProps {
|
|
15
|
+
/** Main heading text */
|
|
16
|
+
headline?: string;
|
|
17
|
+
/** Supporting text shown below the headline */
|
|
18
|
+
subtext?: string;
|
|
19
|
+
/** Background image URL rendered behind the content */
|
|
20
|
+
imageUrl?: string;
|
|
21
|
+
/** Label for the call-to-action link; requires `ctaHref` to render. Superseded by `ctaButtons`, kept so existing content keeps rendering */
|
|
22
|
+
ctaLabel?: string;
|
|
23
|
+
/** Destination URL for the call-to-action link; requires `ctaLabel` to render. Superseded by `ctaButtons` */
|
|
24
|
+
ctaHref?: string;
|
|
25
|
+
/** Call-to-action links. When present these render instead of `ctaLabel`/`ctaHref` */
|
|
26
|
+
ctaButtons?: HeroBannerCta[];
|
|
27
|
+
/** Horizontal alignment of the content; defaults to center */
|
|
28
|
+
align?: "left" | "center" | "right";
|
|
29
|
+
/** Additional class names to merge with the component root element */
|
|
30
|
+
className?: string;
|
|
31
|
+
/** Additional content rendered after the CTA */
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
declare const HeroBanner: ({ headline, subtext, imageUrl, ctaLabel, ctaHref, ctaButtons, align, className, children, }: HeroBannerProps) => react.JSX.Element;
|
|
35
|
+
|
|
36
|
+
declare const heroBannerSchema: ComponentSchema;
|
|
37
|
+
|
|
38
|
+
declare const heroBannerMeta: ComponentMeta;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prop migrations for stored hero-banner blocks.
|
|
42
|
+
*
|
|
43
|
+
* This is a real migration, not a demonstration. 0.4.0 replaced the single
|
|
44
|
+
* `ctaLabel`/`ctaHref` pair with `ctaButtons`, an array. The component still
|
|
45
|
+
* reads the old pair so nothing broke on release, but that fallback is a
|
|
46
|
+
* permanent tax on the component: two code paths for one feature, forever.
|
|
47
|
+
* Migrating the stored content is what eventually lets it go.
|
|
48
|
+
*
|
|
49
|
+
* Chain order is expressed by `fromVersion`/`toVersion`, not array position —
|
|
50
|
+
* the runner follows the links.
|
|
51
|
+
*/
|
|
52
|
+
declare const heroBannerMigrations: Migration[];
|
|
53
|
+
|
|
54
|
+
export { HeroBanner, type HeroBannerCta, type HeroBannerProps, heroBannerMeta, heroBannerMigrations, heroBannerSchema, heroBannerMeta as meta, heroBannerMigrations as migrations, heroBannerSchema as schema };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
+
mod
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
// ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
|
|
28
|
+
var require_classnames = __commonJS({
|
|
29
|
+
"../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports, module) {
|
|
30
|
+
"use strict";
|
|
31
|
+
(function() {
|
|
32
|
+
"use strict";
|
|
33
|
+
var hasOwn = {}.hasOwnProperty;
|
|
34
|
+
function classNames() {
|
|
35
|
+
var classes = "";
|
|
36
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
37
|
+
var arg = arguments[i];
|
|
38
|
+
if (arg) {
|
|
39
|
+
classes = appendClass(classes, parseValue(arg));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return classes;
|
|
43
|
+
}
|
|
44
|
+
function parseValue(arg) {
|
|
45
|
+
if (typeof arg === "string" || typeof arg === "number") {
|
|
46
|
+
return arg;
|
|
47
|
+
}
|
|
48
|
+
if (typeof arg !== "object") {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(arg)) {
|
|
52
|
+
return classNames.apply(null, arg);
|
|
53
|
+
}
|
|
54
|
+
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
|
|
55
|
+
return arg.toString();
|
|
56
|
+
}
|
|
57
|
+
var classes = "";
|
|
58
|
+
for (var key in arg) {
|
|
59
|
+
if (hasOwn.call(arg, key) && arg[key]) {
|
|
60
|
+
classes = appendClass(classes, key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return classes;
|
|
64
|
+
}
|
|
65
|
+
function appendClass(value, newClass) {
|
|
66
|
+
if (!newClass) {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
if (value) {
|
|
70
|
+
return value + " " + newClass;
|
|
71
|
+
}
|
|
72
|
+
return value + newClass;
|
|
73
|
+
}
|
|
74
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
75
|
+
classNames.default = classNames;
|
|
76
|
+
module.exports = classNames;
|
|
77
|
+
} else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
|
|
78
|
+
define("classnames", [], function() {
|
|
79
|
+
return classNames;
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
window.classNames = classNames;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ../../../../packages/registry/class-names/dist/index.js
|
|
89
|
+
var import_classnames = __toESM(require_classnames(), 1);
|
|
90
|
+
var getClassName = ({
|
|
91
|
+
className,
|
|
92
|
+
modifiers = {},
|
|
93
|
+
rootClass,
|
|
94
|
+
styles = {}
|
|
95
|
+
}) => {
|
|
96
|
+
function getModifiers() {
|
|
97
|
+
const updatedModifiers = {};
|
|
98
|
+
const modifierKeys = Object.keys(modifiers);
|
|
99
|
+
if (modifierKeys.length) {
|
|
100
|
+
modifierKeys.forEach((modKey) => {
|
|
101
|
+
const classKey = `${rootClass}--${modKey}`;
|
|
102
|
+
const newClassName = styles[classKey] || classKey;
|
|
103
|
+
updatedModifiers[newClassName] = modifiers[modKey] ?? false;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return updatedModifiers;
|
|
107
|
+
}
|
|
108
|
+
function getChildClass(childClassName) {
|
|
109
|
+
const classKey = `${rootClass}__${childClassName}`;
|
|
110
|
+
return styles[classKey] || classKey;
|
|
111
|
+
}
|
|
112
|
+
function getRootStyles() {
|
|
113
|
+
return (0, import_classnames.default)(
|
|
114
|
+
className,
|
|
115
|
+
{
|
|
116
|
+
...getModifiers()
|
|
117
|
+
},
|
|
118
|
+
styles[rootClass] || rootClass
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return [getRootStyles(), getChildClass];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// hero-banner.module.scss
|
|
125
|
+
var hero_banner_module_default = {
|
|
126
|
+
"BHT__hero-banner": "BHT__hero-banner",
|
|
127
|
+
"BHT__hero-banner__content": "BHT__hero-banner__content",
|
|
128
|
+
"BHT__hero-banner__headline": "BHT__hero-banner__headline",
|
|
129
|
+
"BHT__hero-banner__subtext": "BHT__hero-banner__subtext",
|
|
130
|
+
"BHT__hero-banner__ctas": "BHT__hero-banner__ctas",
|
|
131
|
+
"BHT__hero-banner__cta": "BHT__hero-banner__cta",
|
|
132
|
+
"BHT__hero-banner__cta-secondary": "BHT__hero-banner__cta-secondary",
|
|
133
|
+
"BHT__hero-banner--align-left": "BHT__hero-banner--align-left",
|
|
134
|
+
"BHT__hero-banner--align-right": "BHT__hero-banner--align-right"
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// hero-banner.tsx
|
|
138
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
139
|
+
var HeroBanner = ({
|
|
140
|
+
headline,
|
|
141
|
+
subtext,
|
|
142
|
+
imageUrl,
|
|
143
|
+
ctaLabel,
|
|
144
|
+
ctaHref,
|
|
145
|
+
ctaButtons,
|
|
146
|
+
align = "center",
|
|
147
|
+
className,
|
|
148
|
+
children
|
|
149
|
+
}) => {
|
|
150
|
+
const [rootClass, getChildClass] = getClassName({
|
|
151
|
+
className,
|
|
152
|
+
rootClass: "BHT__hero-banner",
|
|
153
|
+
modifiers: { [`align-${align}`]: true },
|
|
154
|
+
styles: hero_banner_module_default
|
|
155
|
+
});
|
|
156
|
+
const ctas = (Array.isArray(ctaButtons) && ctaButtons.length > 0 ? ctaButtons : [{ label: ctaLabel, href: ctaHref, variant: "primary" }]).filter(
|
|
157
|
+
(cta) => Boolean(cta) && Boolean(cta.label) && Boolean(cta.href)
|
|
158
|
+
);
|
|
159
|
+
return /* @__PURE__ */ jsx(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
className: rootClass,
|
|
163
|
+
style: imageUrl ? { backgroundImage: `url(${imageUrl})` } : void 0,
|
|
164
|
+
children: /* @__PURE__ */ jsxs("div", { className: getChildClass("content"), children: [
|
|
165
|
+
headline && /* @__PURE__ */ jsx("h1", { className: getChildClass("headline"), children: headline }),
|
|
166
|
+
subtext && /* @__PURE__ */ jsx("p", { className: getChildClass("subtext"), children: subtext }),
|
|
167
|
+
ctas.length > 0 && /* @__PURE__ */ jsx("div", { className: getChildClass("ctas"), children: ctas.map((cta, index) => /* @__PURE__ */ jsx(
|
|
168
|
+
"a",
|
|
169
|
+
{
|
|
170
|
+
className: `${getChildClass("cta")} ${cta.variant === "secondary" ? getChildClass("cta-secondary") : ""}`.trim(),
|
|
171
|
+
href: cta.href,
|
|
172
|
+
children: cta.label
|
|
173
|
+
},
|
|
174
|
+
`${cta.href}-${index}`
|
|
175
|
+
)) }),
|
|
176
|
+
children
|
|
177
|
+
] })
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// hero-banner.schema.ts
|
|
183
|
+
var heroBannerSchema = {
|
|
184
|
+
fields: [
|
|
185
|
+
{
|
|
186
|
+
name: "headline",
|
|
187
|
+
type: "string",
|
|
188
|
+
label: "Headline",
|
|
189
|
+
placeholder: "Enter headline\u2026",
|
|
190
|
+
required: true,
|
|
191
|
+
defaultValue: "Welcome"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "subtext",
|
|
195
|
+
type: "richtext",
|
|
196
|
+
label: "Subtext",
|
|
197
|
+
placeholder: "Supporting text\u2026",
|
|
198
|
+
defaultValue: ""
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "imageUrl",
|
|
202
|
+
type: "image",
|
|
203
|
+
label: "Background Image",
|
|
204
|
+
defaultValue: ""
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "ctaButtons",
|
|
208
|
+
type: "array",
|
|
209
|
+
label: "Buttons",
|
|
210
|
+
hint: "The first button is the main action. Leave empty for a banner with no call to action.",
|
|
211
|
+
defaultValue: [],
|
|
212
|
+
itemSchema: [
|
|
213
|
+
{
|
|
214
|
+
name: "label",
|
|
215
|
+
type: "string",
|
|
216
|
+
label: "Label",
|
|
217
|
+
placeholder: "Get started",
|
|
218
|
+
required: true
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "href",
|
|
222
|
+
type: "url",
|
|
223
|
+
label: "Link",
|
|
224
|
+
placeholder: "https://\u2026",
|
|
225
|
+
required: true
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: "variant",
|
|
229
|
+
type: "select",
|
|
230
|
+
label: "Style",
|
|
231
|
+
defaultValue: "primary",
|
|
232
|
+
options: [
|
|
233
|
+
{ label: "Primary", value: "primary" },
|
|
234
|
+
{ label: "Secondary", value: "secondary" }
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "align",
|
|
241
|
+
type: "select",
|
|
242
|
+
label: "Text Alignment",
|
|
243
|
+
defaultValue: "center",
|
|
244
|
+
options: [
|
|
245
|
+
{ label: "Left", value: "left" },
|
|
246
|
+
{ label: "Center", value: "center" },
|
|
247
|
+
{ label: "Right", value: "right" }
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// hero-banner.meta.ts
|
|
254
|
+
var heroBannerMeta = {
|
|
255
|
+
name: "HeroBanner",
|
|
256
|
+
displayName: "Hero Banner",
|
|
257
|
+
description: "Full-width hero section with headline, subtext, and call-to-action",
|
|
258
|
+
icon: "\u{1F3AF}",
|
|
259
|
+
tags: ["marketing", "layout"],
|
|
260
|
+
acceptsChildren: false
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// hero-banner.migrations.ts
|
|
264
|
+
var heroBannerMigrations = [
|
|
265
|
+
{
|
|
266
|
+
fromVersion: "0.3.1",
|
|
267
|
+
toVersion: "0.4.0",
|
|
268
|
+
migrate(props) {
|
|
269
|
+
const { ctaLabel, ctaHref, ...rest } = props;
|
|
270
|
+
const hasCta = typeof ctaLabel === "string" && ctaLabel !== "" && typeof ctaHref === "string" && ctaHref !== "";
|
|
271
|
+
if (!hasCta) return rest;
|
|
272
|
+
const existing = rest.ctaButtons;
|
|
273
|
+
if (Array.isArray(existing) && existing.length > 0) return rest;
|
|
274
|
+
return {
|
|
275
|
+
...rest,
|
|
276
|
+
ctaButtons: [{ label: ctaLabel, href: ctaHref, variant: "primary" }]
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
];
|
|
281
|
+
export {
|
|
282
|
+
HeroBanner,
|
|
283
|
+
heroBannerMeta,
|
|
284
|
+
heroBannerMigrations,
|
|
285
|
+
heroBannerSchema,
|
|
286
|
+
heroBannerMeta as meta,
|
|
287
|
+
heroBannerMigrations as migrations,
|
|
288
|
+
heroBannerSchema as schema
|
|
289
|
+
};
|
|
290
|
+
/*! Bundled license information:
|
|
291
|
+
|
|
292
|
+
classnames/index.js:
|
|
293
|
+
(*!
|
|
294
|
+
Copyright (c) 2018 Jed Watson.
|
|
295
|
+
Licensed under the MIT License (MIT), see
|
|
296
|
+
http://jedwatson.github.io/classnames
|
|
297
|
+
*)
|
|
298
|
+
*/
|
package/docs.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://github.com/behivetech/bht-component-registry/docs.schema.json",
|
|
3
|
+
"version": 2,
|
|
4
|
+
"generatedAt": "2026-10-23T16:58:54.227Z",
|
|
5
|
+
"docs": [
|
|
6
|
+
{
|
|
7
|
+
"displayName": "HeroBanner",
|
|
8
|
+
"description": "",
|
|
9
|
+
"extendsNative": false,
|
|
10
|
+
"props": [
|
|
11
|
+
{
|
|
12
|
+
"name": "align",
|
|
13
|
+
"type": "\"left\" | \"center\" | \"right\"",
|
|
14
|
+
"required": false,
|
|
15
|
+
"defaultValue": "center",
|
|
16
|
+
"description": "Horizontal alignment of the content; defaults to center"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "children",
|
|
20
|
+
"type": "ReactNode",
|
|
21
|
+
"required": false,
|
|
22
|
+
"defaultValue": null,
|
|
23
|
+
"description": "Additional content rendered after the CTA"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "className",
|
|
27
|
+
"type": "string",
|
|
28
|
+
"required": false,
|
|
29
|
+
"defaultValue": null,
|
|
30
|
+
"description": "Additional class names to merge with the component root element"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "ctaButtons",
|
|
34
|
+
"type": "HeroBannerCta[]",
|
|
35
|
+
"required": false,
|
|
36
|
+
"defaultValue": null,
|
|
37
|
+
"description": "Call-to-action links. When present these render instead of `ctaLabel`/`ctaHref`"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "ctaHref",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"required": false,
|
|
43
|
+
"defaultValue": null,
|
|
44
|
+
"description": "Destination URL for the call-to-action link; requires `ctaLabel` to render. Superseded by `ctaButtons`"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "ctaLabel",
|
|
48
|
+
"type": "string",
|
|
49
|
+
"required": false,
|
|
50
|
+
"defaultValue": null,
|
|
51
|
+
"description": "Label for the call-to-action link; requires `ctaHref` to render. Superseded by `ctaButtons`, kept so existing content keeps rendering"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "headline",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"required": false,
|
|
57
|
+
"defaultValue": null,
|
|
58
|
+
"description": "Main heading text"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "imageUrl",
|
|
62
|
+
"type": "string",
|
|
63
|
+
"required": false,
|
|
64
|
+
"defaultValue": null,
|
|
65
|
+
"description": "Background image URL rendered behind the content"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "subtext",
|
|
69
|
+
"type": "string",
|
|
70
|
+
"required": false,
|
|
71
|
+
"defaultValue": null,
|
|
72
|
+
"description": "Supporting text shown below the headline"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"functions": []
|
|
78
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { HeroBanner } from "./hero-banner.js";
|
|
2
|
+
|
|
3
|
+
export const BasicHeroBanner = () => (
|
|
4
|
+
<HeroBanner>HeroBanner content</HeroBanner>
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
export const HeroBannerWithButtons = () => (
|
|
8
|
+
<HeroBanner
|
|
9
|
+
headline="Build it once"
|
|
10
|
+
subtext="Ship the same components to every site you run."
|
|
11
|
+
ctaButtons={[
|
|
12
|
+
{ label: "Get started", href: "https://example.com" },
|
|
13
|
+
{
|
|
14
|
+
label: "Read the docs",
|
|
15
|
+
href: "https://docs.example.com",
|
|
16
|
+
variant: "secondary",
|
|
17
|
+
},
|
|
18
|
+
]}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Hero banner: full-bleed image under a scrim, with content on top.
|
|
2
|
+
//
|
|
3
|
+
// The text sits on a darkened photograph, not on a theme surface, so it must
|
|
4
|
+
// stay light in both color schemes. MD3 has no "on-scrim" role, and every
|
|
5
|
+
// `on-*` role flips with the theme, so the one light literal lives in
|
|
6
|
+
// `--hero-banner-on-image` below rather than being repeated per rule. A site
|
|
7
|
+
// can override it on the root if its imagery calls for something else.
|
|
8
|
+
|
|
9
|
+
.BHT__hero-banner {
|
|
10
|
+
--hero-banner-on-image: #ffffff;
|
|
11
|
+
|
|
12
|
+
position: relative;
|
|
13
|
+
min-height: 30rem;
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
background-size: cover;
|
|
17
|
+
background-position: center;
|
|
18
|
+
background-color: var(--md-sys-color-surface, var(--color-surface));
|
|
19
|
+
padding: var(--spacing-16, 4rem) var(--spacing-8, 2rem);
|
|
20
|
+
|
|
21
|
+
// Scrim over the image. Opacity rather than a translucent color so it stays
|
|
22
|
+
// on the `scrim` role and matches the previous 40% darkening exactly.
|
|
23
|
+
&::before {
|
|
24
|
+
content: "";
|
|
25
|
+
position: absolute;
|
|
26
|
+
inset: 0;
|
|
27
|
+
background: var(--md-sys-color-scrim, var(--color-overlay));
|
|
28
|
+
opacity: 0.4;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&__content {
|
|
32
|
+
position: relative;
|
|
33
|
+
z-index: 1;
|
|
34
|
+
max-width: 45rem;
|
|
35
|
+
width: 100%;
|
|
36
|
+
margin: 0 auto;
|
|
37
|
+
text-align: center;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&__headline {
|
|
41
|
+
font-family: var(
|
|
42
|
+
--md-sys-typescale-display-medium-font,
|
|
43
|
+
var(--font-heading)
|
|
44
|
+
);
|
|
45
|
+
font-size: var(--md-sys-typescale-display-medium-size, 2.8125rem);
|
|
46
|
+
font-weight: var(--md-sys-typescale-display-medium-weight, 400);
|
|
47
|
+
line-height: var(--md-sys-typescale-display-medium-line-height, 3.25rem);
|
|
48
|
+
letter-spacing: var(--md-sys-typescale-display-medium-tracking, 0em);
|
|
49
|
+
color: var(--hero-banner-on-image);
|
|
50
|
+
margin: 0 0 var(--spacing-4, 1rem);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&__subtext {
|
|
54
|
+
font-family: var(--md-sys-typescale-title-large-font, var(--font-body));
|
|
55
|
+
font-size: var(--md-sys-typescale-title-large-size, 1.375rem);
|
|
56
|
+
font-weight: var(--md-sys-typescale-title-large-weight, 400);
|
|
57
|
+
line-height: var(--md-sys-typescale-title-large-line-height, 1.75rem);
|
|
58
|
+
letter-spacing: var(--md-sys-typescale-title-large-tracking, 0em);
|
|
59
|
+
color: color-mix(in srgb, var(--hero-banner-on-image) 85%, transparent);
|
|
60
|
+
margin: 0 0 var(--spacing-6, 1.5rem);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&__ctas {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-wrap: wrap;
|
|
66
|
+
gap: var(--spacing-3, 0.75rem);
|
|
67
|
+
justify-content: center;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// CTAs follow Button's large filled / outlined treatment: pill shape,
|
|
71
|
+
// label-large type at title-medium size, and a currentColor state layer.
|
|
72
|
+
&__cta {
|
|
73
|
+
position: relative;
|
|
74
|
+
isolation: isolate;
|
|
75
|
+
display: inline-block;
|
|
76
|
+
padding: var(--spacing-3, 0.75rem) var(--spacing-8, 2rem);
|
|
77
|
+
background: var(--md-sys-color-primary, var(--color-primary));
|
|
78
|
+
color: var(--md-sys-color-on-primary, var(--color-on-primary));
|
|
79
|
+
font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
|
|
80
|
+
font-size: var(--md-sys-typescale-title-medium-size, 1rem);
|
|
81
|
+
font-weight: var(--md-sys-typescale-label-large-weight, 500);
|
|
82
|
+
line-height: var(--md-sys-typescale-title-medium-line-height, 1.5rem);
|
|
83
|
+
letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
|
|
84
|
+
border-radius: var(--md-sys-shape-corner-full, 9999px);
|
|
85
|
+
text-decoration: none;
|
|
86
|
+
|
|
87
|
+
&::after {
|
|
88
|
+
content: "";
|
|
89
|
+
position: absolute;
|
|
90
|
+
inset: 0;
|
|
91
|
+
border-radius: inherit;
|
|
92
|
+
background: currentColor;
|
|
93
|
+
opacity: 0;
|
|
94
|
+
pointer-events: none;
|
|
95
|
+
transition: opacity var(--md-sys-motion-duration-short-2, 100ms)
|
|
96
|
+
var(--md-sys-motion-easing-standard, ease);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&:hover::after {
|
|
100
|
+
opacity: var(--md-sys-state-hover-opacity, 0.08);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&:focus-visible::after {
|
|
104
|
+
opacity: var(--md-sys-state-focus-opacity, 0.1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&:active::after {
|
|
108
|
+
opacity: var(--md-sys-state-pressed-opacity, 0.1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Touch target, as in Button: extends the hit area to 48px without
|
|
112
|
+
// changing the drawn size. px because it is sized to the finger.
|
|
113
|
+
&::before {
|
|
114
|
+
content: "";
|
|
115
|
+
position: absolute;
|
|
116
|
+
left: 0;
|
|
117
|
+
right: 0;
|
|
118
|
+
top: 50%;
|
|
119
|
+
translate: 0 -50%;
|
|
120
|
+
height: 100%;
|
|
121
|
+
min-height: 48px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&:focus-visible {
|
|
125
|
+
outline: 3px solid
|
|
126
|
+
var(--md-sys-color-secondary, var(--color-border-focus));
|
|
127
|
+
outline-offset: 2px;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&__cta-secondary {
|
|
132
|
+
background: transparent;
|
|
133
|
+
color: var(--hero-banner-on-image);
|
|
134
|
+
box-shadow: inset 0 0 0 1px
|
|
135
|
+
color-mix(in srgb, var(--hero-banner-on-image) 70%, transparent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&--align-left {
|
|
139
|
+
.BHT__hero-banner {
|
|
140
|
+
&__content {
|
|
141
|
+
text-align: left;
|
|
142
|
+
margin-left: 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
&__ctas {
|
|
146
|
+
justify-content: flex-start;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&--align-right {
|
|
152
|
+
.BHT__hero-banner {
|
|
153
|
+
&__content {
|
|
154
|
+
text-align: right;
|
|
155
|
+
margin-right: 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&__ctas {
|
|
159
|
+
justify-content: flex-end;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
package/hero-banner.tsx
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { getClassName } from "@bht-component-registry/class-names";
|
|
3
|
+
import styles from "./hero-banner.module.scss";
|
|
4
|
+
|
|
5
|
+
/** One call-to-action link in `ctaButtons`. */
|
|
6
|
+
export interface HeroBannerCta {
|
|
7
|
+
/** Link text; a button with no label is skipped */
|
|
8
|
+
label?: string;
|
|
9
|
+
/** Destination URL; a button with no href is skipped */
|
|
10
|
+
href?: string;
|
|
11
|
+
/** Visual weight — the first button usually leads, the rest support */
|
|
12
|
+
variant?: "primary" | "secondary";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface HeroBannerProps {
|
|
16
|
+
/** Main heading text */
|
|
17
|
+
headline?: string;
|
|
18
|
+
/** Supporting text shown below the headline */
|
|
19
|
+
subtext?: string;
|
|
20
|
+
/** Background image URL rendered behind the content */
|
|
21
|
+
imageUrl?: string;
|
|
22
|
+
/** Label for the call-to-action link; requires `ctaHref` to render. Superseded by `ctaButtons`, kept so existing content keeps rendering */
|
|
23
|
+
ctaLabel?: string;
|
|
24
|
+
/** Destination URL for the call-to-action link; requires `ctaLabel` to render. Superseded by `ctaButtons` */
|
|
25
|
+
ctaHref?: string;
|
|
26
|
+
/** Call-to-action links. When present these render instead of `ctaLabel`/`ctaHref` */
|
|
27
|
+
ctaButtons?: HeroBannerCta[];
|
|
28
|
+
/** Horizontal alignment of the content; defaults to center */
|
|
29
|
+
align?: "left" | "center" | "right";
|
|
30
|
+
/** Additional class names to merge with the component root element */
|
|
31
|
+
className?: string;
|
|
32
|
+
/** Additional content rendered after the CTA */
|
|
33
|
+
children?: ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const HeroBanner = ({
|
|
37
|
+
headline,
|
|
38
|
+
subtext,
|
|
39
|
+
imageUrl,
|
|
40
|
+
ctaLabel,
|
|
41
|
+
ctaHref,
|
|
42
|
+
ctaButtons,
|
|
43
|
+
align = "center",
|
|
44
|
+
className,
|
|
45
|
+
children,
|
|
46
|
+
}: HeroBannerProps) => {
|
|
47
|
+
const [rootClass, getChildClass] = getClassName({
|
|
48
|
+
className,
|
|
49
|
+
rootClass: "BHT__hero-banner",
|
|
50
|
+
modifiers: { [`align-${align}`]: true },
|
|
51
|
+
styles,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// `ctaButtons` arrives from a jsonb column an author edits through the CMS, so
|
|
55
|
+
// it can legitimately hold half-filled rows — someone adds a button, types the
|
|
56
|
+
// label, and saves before pasting the URL. Those are skipped rather than
|
|
57
|
+
// rendered as a dead link. Falling back to ctaLabel/ctaHref keeps pages
|
|
58
|
+
// authored before this prop existed rendering unchanged.
|
|
59
|
+
const ctas: HeroBannerCta[] = (
|
|
60
|
+
Array.isArray(ctaButtons) && ctaButtons.length > 0
|
|
61
|
+
? ctaButtons
|
|
62
|
+
: [{ label: ctaLabel, href: ctaHref, variant: "primary" as const }]
|
|
63
|
+
).filter(
|
|
64
|
+
(cta): cta is HeroBannerCta =>
|
|
65
|
+
Boolean(cta) && Boolean(cta.label) && Boolean(cta.href),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className={rootClass}
|
|
71
|
+
style={imageUrl ? { backgroundImage: `url(${imageUrl})` } : undefined}
|
|
72
|
+
>
|
|
73
|
+
<div className={getChildClass("content")}>
|
|
74
|
+
{headline && <h1 className={getChildClass("headline")}>{headline}</h1>}
|
|
75
|
+
{subtext && <p className={getChildClass("subtext")}>{subtext}</p>}
|
|
76
|
+
{ctas.length > 0 && (
|
|
77
|
+
<div className={getChildClass("ctas")}>
|
|
78
|
+
{ctas.map((cta, index) => (
|
|
79
|
+
<a
|
|
80
|
+
key={`${cta.href}-${index}`}
|
|
81
|
+
className={`${getChildClass("cta")} ${
|
|
82
|
+
cta.variant === "secondary"
|
|
83
|
+
? getChildClass("cta-secondary")
|
|
84
|
+
: ""
|
|
85
|
+
}`.trim()}
|
|
86
|
+
href={cta.href}
|
|
87
|
+
>
|
|
88
|
+
{cta.label}
|
|
89
|
+
</a>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
{children}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
package/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { HeroBanner } from "./hero-banner.js";
|
|
2
|
+
export type { HeroBannerProps, HeroBannerCta } from "./hero-banner.js";
|
|
3
|
+
// `schema` and `meta` aliases are the stable contract the CMS catalog seed
|
|
4
|
+
// reads by; the prefixed names stay for direct consumers.
|
|
5
|
+
export {
|
|
6
|
+
heroBannerSchema,
|
|
7
|
+
heroBannerSchema as schema,
|
|
8
|
+
} from "./hero-banner.schema.js";
|
|
9
|
+
export { heroBannerMeta, heroBannerMeta as meta } from "./hero-banner.meta.js";
|
|
10
|
+
export {
|
|
11
|
+
heroBannerMigrations,
|
|
12
|
+
heroBannerMigrations as migrations,
|
|
13
|
+
} from "./hero-banner.migrations.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@behivetech/organisms.hero-banner",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.7.3",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
},
|
|
13
|
+
"./styles.css": "./dist/index.css",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"index.ts",
|
|
20
|
+
"hero-banner.tsx",
|
|
21
|
+
"hero-banner.module.scss",
|
|
22
|
+
"*.example.tsx",
|
|
23
|
+
"docs.json"
|
|
24
|
+
],
|
|
25
|
+
"registry": {
|
|
26
|
+
"status": "stable",
|
|
27
|
+
"tags": []
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@behivetech/cms.types": "0.5.1"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": "^19.2.7",
|
|
34
|
+
"react-dom": "^19.2.7"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/react": "19.2.17",
|
|
38
|
+
"@types/react-dom": "19.2.3",
|
|
39
|
+
"eslint": "^9.39.5",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "^6.0.3",
|
|
42
|
+
"vitest": "^4.1.10",
|
|
43
|
+
"@bht-component-registry/class-names": "0.0.6",
|
|
44
|
+
"@bht-component-registry/env": "0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/behivetech/bht-galaxy.git",
|
|
49
|
+
"directory": "registry/behivetech/organisms/hero-banner"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"registry": "https://registry.npmjs.org",
|
|
53
|
+
"access": "public",
|
|
54
|
+
"provenance": true
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsup && bht-registry-docgen",
|
|
58
|
+
"dev": "tsup --watch",
|
|
59
|
+
"lint": "eslint . --max-warnings 0",
|
|
60
|
+
"check-types": "tsc --noEmit",
|
|
61
|
+
"test": "vitest run"
|
|
62
|
+
}
|
|
6
63
|
}
|