brand-shell 0.3.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Venkatesh Bommadevara
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,316 @@
1
+ ## Brand Shell
2
+
3
+ Reusable header and footer components that ship with a premium default theme, typed props, and Storybook-based docs. Drop the package into React, Next.js, TanStack Router, Vue, or Svelte apps to keep your brand chrome consistent across projects.
4
+
5
+ ### Installation
6
+
7
+ ```bash
8
+ bun add brand-shell
9
+ ```
10
+
11
+ ### Basic usage
12
+
13
+ ```tsx
14
+ import { Header, Footer, type BrandDetails, type BrandTheme } from "brand-shell";
15
+ import "brand-shell/default.css";
16
+
17
+ const details: BrandDetails = {
18
+ name: "Mounika Thota",
19
+ homeHref: "/",
20
+ navLinks: [
21
+ { label: "Blog", href: "/blog" },
22
+ { label: "Docs", href: "/docs" },
23
+ ],
24
+ primaryAction: { label: "Hire Me", href: "mailto:hello@example.com" },
25
+ secondaryAction: { label: "Resume", href: "/resume.pdf", target: "_blank" },
26
+ linkedin: "https://linkedin.com/in/example",
27
+ github: "https://github.com/example",
28
+ twitter: "https://twitter.com/example",
29
+ gmail: "hello@example.com",
30
+ tagline: "Design-system quality shell for every project.",
31
+ };
32
+
33
+ const theme: BrandTheme = {
34
+ primaryColor: "#0ea5e9",
35
+ backgroundColor: "#0f172a",
36
+ textColor: "#f8fafc",
37
+ fontFamily: '"Inter", system-ui, sans-serif',
38
+ socialIconSize: "2.25rem",
39
+ };
40
+
41
+ export function Layout({ children }: { children: React.ReactNode }) {
42
+ return (
43
+ <>
44
+ <Header details={details} theme={theme} />
45
+ {children}
46
+ <Footer details={details} theme={theme} />
47
+ </>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ### Framework-agnostic core helpers
53
+
54
+ The package now exports pure TypeScript helpers you can reuse in other framework adapters:
55
+
56
+ ```ts
57
+ import {
58
+ buildShellViewModel,
59
+ validateBrandDetails,
60
+ validateBrandTheme,
61
+ } from "brand-shell";
62
+ ```
63
+
64
+ `buildShellViewModel` normalizes nav links and CTA behavior (target/rel/variants) so framework adapters share the same rules.
65
+
66
+ Validation + normalization are also exported for external integrations. The adapters run these checks in development mode.
67
+
68
+ ```ts
69
+ const detailsCheck = validateBrandDetails(input.details);
70
+ const themeCheck = validateBrandTheme(input.theme);
71
+
72
+ if (!detailsCheck.valid || !themeCheck.valid) {
73
+ throw new Error([...detailsCheck.errors, ...themeCheck.errors].join("\n"));
74
+ }
75
+
76
+ const normalizedDetails = detailsCheck.normalized;
77
+ const normalizedTheme = themeCheck.normalized;
78
+ ```
79
+
80
+ ### JSON schema
81
+
82
+ You can validate payloads in any runtime using the published schema:
83
+
84
+ ```ts
85
+ import schema from "brand-shell/schema";
86
+ // also available as: brand-shell/schema.json
87
+ ```
88
+
89
+ This repo validates the schema with Ajv in unit tests (`src/core/schema.test.ts`) to catch contract regressions before release.
90
+
91
+ Package publish surface is also guarded by `bun run pack:check` (allowed files + unpacked size budget) so docs/dev-webapp files cannot bloat the npm package.
92
+
93
+ ### Web Components adapter
94
+
95
+ You can also use framework-agnostic custom elements:
96
+
97
+ ```ts
98
+ import { applyBrandShellProps, registerBrandShellElements } from "brand-shell/web";
99
+ import "brand-shell/default.css";
100
+
101
+ registerBrandShellElements();
102
+ ```
103
+
104
+ ```html
105
+ <brand-header id="app-header"></brand-header>
106
+ <brand-footer id="app-footer"></brand-footer>
107
+ ```
108
+
109
+ ```ts
110
+ const details = {
111
+ name: "Brand Shell",
112
+ homeHref: "/",
113
+ navLinks: [{ label: "Docs", href: "/docs" }],
114
+ primaryAction: { label: "Contact", href: "mailto:hello@example.com" },
115
+ linkedin: "https://linkedin.com/in/example",
116
+ };
117
+
118
+ const theme = {
119
+ primaryColor: "#0ea5e9",
120
+ backgroundColor: "#0f172a",
121
+ textColor: "#f8fafc",
122
+ };
123
+
124
+ const header = document.getElementById("app-header");
125
+ const footer = document.getElementById("app-footer");
126
+
127
+ if (header && footer) {
128
+ applyBrandShellProps(header, { details, theme });
129
+ applyBrandShellProps(footer, { details, theme });
130
+ }
131
+ ```
132
+
133
+ ### Vue adapter
134
+
135
+ Use framework-native Vue components:
136
+
137
+ ```vue
138
+ <script setup>
139
+ import { BrandFooter, BrandHeader } from "brand-shell/vue";
140
+ import "brand-shell/default.css";
141
+
142
+ const details = { name: "Brand Shell" };
143
+ const theme = { primaryColor: "#0ea5e9" };
144
+ </script>
145
+
146
+ <template>
147
+ <BrandHeader :details="details" :theme="theme" />
148
+ <BrandFooter :details="details" :theme="theme" />
149
+ </template>
150
+ ```
151
+
152
+ ### Svelte adapter
153
+
154
+ Use a Svelte action on the custom element tags:
155
+
156
+ ```svelte
157
+ <script>
158
+ import { brandShell } from "brand-shell/svelte";
159
+ import "brand-shell/default.css";
160
+
161
+ const shellProps = {
162
+ details: { name: "Brand Shell" },
163
+ theme: { primaryColor: "#0ea5e9" },
164
+ };
165
+ </script>
166
+
167
+ <brand-header use:brandShell={shellProps}></brand-header>
168
+ <brand-footer use:brandShell={shellProps}></brand-footer>
169
+ ```
170
+
171
+ ### Props reference
172
+
173
+ #### `BrandDetails`
174
+
175
+ | Field | Type | Description |
176
+ | --- | --- | --- |
177
+ | `name` | `string` | Required brand name shown in header/footer |
178
+ | `homeHref` | `string` | Optional link applied to the header name/logo |
179
+ | `navLinks` | `BrandNavLink[]` | Optional text links (e.g. Blog, Docs, About) |
180
+ | `primaryAction` | `BrandAction` | Highlighted CTA button (e.g. Hire me) |
181
+ | `secondaryAction` | `BrandAction` | Optional secondary CTA button |
182
+ | `linkedin`, `github`, `twitter`, `discord`, `website` | `string` | Social/profile URLs rendered as icon buttons |
183
+ | `gmail` | `string` | Email address or `mailto:` link |
184
+ | `tagline` | `string` | Short line shown in the footer |
185
+
186
+ #### `BrandNavLink`
187
+
188
+ | Field | Type | Description |
189
+ | --- | --- | --- |
190
+ | `label` | `string` | Visible text |
191
+ | `href` | `string` | Destination |
192
+ | `ariaLabel` | `string` | Optional accessible label override |
193
+ | `target` | `_blank \| _self \| _parent \| _top` | Optional target |
194
+ | `rel` | `string` | Optional rel attribute |
195
+
196
+ #### `BrandAction`
197
+
198
+ | Field | Type | Description |
199
+ | --- | --- | --- |
200
+ | `label` | `string` | CTA text |
201
+ | `href` | `string` | Destination URL |
202
+ | `ariaLabel` | `string` | Optional accessible label |
203
+ | `target` | `_blank \| _self \| _parent \| _top` | Optional target |
204
+ | `rel` | `string` | Optional rel |
205
+ | `variant` | `"primary" \| "secondary" \| "ghost"` | Optional style hint (defaults to primary for the last CTA) |
206
+
207
+ #### `BrandTheme`
208
+
209
+ | Field | Type | Description |
210
+ | --- | --- | --- |
211
+ | `primaryColor` | `string` | Accent color for links/buttons |
212
+ | `backgroundColor` | `string` | Header/footer background |
213
+ | `textColor` | `string` | Primary text color |
214
+ | `fontFamily` | `string` | Font stack |
215
+ | `linkColor` | `string` | Optional base link color |
216
+ | `socialIconSize` | `string` | Size of the circular social buttons (defaults to `2.5rem`) |
217
+ | `buttonTextColor` | `string` | Optional primary CTA text color override |
218
+ | `ctaLayout` | `"inline" \| "stacked"` | Mobile CTA layout: side-by-side (`inline`) or one-per-row (`stacked`) |
219
+
220
+ ### Theming via CSS variables
221
+
222
+ `styles/default.css` exposes CSS custom properties you can override globally:
223
+
224
+ | Variable | Defaults to | Purpose |
225
+ | --- | --- | --- |
226
+ | `--brand-primary` | `#2563eb` | Accent + CTA background |
227
+ | `--brand-bg` | `#0f172a` | Header/footer background |
228
+ | `--brand-text` | `#f1f5f9` | Base text color |
229
+ | `--brand-font` | `"Inter", system-ui` | Font stack |
230
+ | `--brand-link` | `#94a3b8` | Link color |
231
+ | `--brand-social-size` | `2.5rem` | Icon button size |
232
+ | `--brand-button-text` | `#f8fafc` | Primary button text color |
233
+
234
+ Set them once in your consuming app:
235
+
236
+ ```css
237
+ :root {
238
+ --brand-primary: #ec4899;
239
+ --brand-font: "Space Grotesk", system-ui, sans-serif;
240
+ --brand-social-size: 2rem;
241
+ }
242
+ ```
243
+
244
+ ### Storybook
245
+
246
+ Run Storybook locally to explore examples and tweak controls:
247
+
248
+ ```bash
249
+ bun run storybook
250
+ ```
251
+
252
+ The stories showcase default, themed, and minimal configurations and expose controls for nav links, CTA buttons, and theme tokens.
253
+
254
+ ### Framework demo consumers
255
+
256
+ Minimal demo apps for React (Vite), Next.js, TanStack Router (Vite), Vue, and Svelte are available under `examples/` and all share
257
+ `examples/shared/brand-contract.json`.
258
+
259
+ - `examples/react-vite`
260
+ - `examples/next-app`
261
+ - `examples/tanstack-vite`
262
+ - `examples/vue-vite`
263
+ - `examples/svelte-vite`
264
+
265
+ Quick commands from repo root:
266
+
267
+ ```bash
268
+ bun run demo:setup
269
+ bun run demo:dev:react
270
+ bun run demo:dev:vue
271
+ bun run demo:dev:svelte
272
+ bun run demo:build:all
273
+ bun run test:smoke
274
+ ```
275
+
276
+ See `examples/README.md` for details.
277
+
278
+ ### Dev Webapp Docs
279
+
280
+ An interactive docs/playground app lives in `apps/docs` and is intentionally excluded from npm package publish size.
281
+
282
+ ```bash
283
+ bun run docs:setup
284
+ bun run docs:dev
285
+ bun run docs:build
286
+ ```
287
+
288
+ ### Versioning and release
289
+
290
+ This repo uses Changesets for controlled SemVer bumps.
291
+
292
+ - every PR must include a changeset (minimum `patch`)
293
+
294
+ ```bash
295
+ bun run changeset
296
+ ```
297
+
298
+ Bump policy:
299
+
300
+ - `patch`: bug fixes and non-breaking internal changes
301
+ - `minor`: backward-compatible features (new optional fields/exports/adapters)
302
+ - `major`: breaking API/schema/peer range changes
303
+
304
+ Commit enforcement:
305
+
306
+ - PR commits must follow Conventional Commits (checked by `.github/workflows/ci.yml`)
307
+ - every PR must include a `.changeset/*.md` bump entry for `brand-shell` (`patch`/`minor`/`major`)
308
+ - bump intent is auto-checked from commits:
309
+ - `feat` => at least `minor`
310
+ - `fix` / `perf` / `refactor` => at least `patch`
311
+ - `!` or `BREAKING CHANGE:` => `major`
312
+
313
+ Release automation runs in `.github/workflows/release.yml`, which calls the same reusable verification workflow as CI before running Changesets publish steps:
314
+
315
+ - if pending changesets exist, it opens/updates a release PR
316
+ - once that release PR is merged, it publishes to npm with provenance
@@ -0,0 +1,89 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://brand-shell.dev/schema/brand-shell.schema.json",
4
+ "title": "Brand Shell Contract",
5
+ "description": "JSON Schema for Brand Shell details and theme payloads.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["details"],
9
+ "properties": {
10
+ "details": {
11
+ "$ref": "#/$defs/BrandDetails"
12
+ },
13
+ "theme": {
14
+ "$ref": "#/$defs/BrandTheme"
15
+ }
16
+ },
17
+ "$defs": {
18
+ "Target": {
19
+ "type": "string",
20
+ "enum": ["_blank", "_self", "_parent", "_top"]
21
+ },
22
+ "Variant": {
23
+ "type": "string",
24
+ "enum": ["primary", "secondary", "ghost"]
25
+ },
26
+ "BrandNavLink": {
27
+ "type": "object",
28
+ "additionalProperties": false,
29
+ "required": ["label", "href"],
30
+ "properties": {
31
+ "label": { "type": "string", "minLength": 1 },
32
+ "href": { "type": "string", "minLength": 1 },
33
+ "ariaLabel": { "type": "string", "minLength": 1 },
34
+ "target": { "$ref": "#/$defs/Target" },
35
+ "rel": { "type": "string", "minLength": 1 }
36
+ }
37
+ },
38
+ "BrandAction": {
39
+ "type": "object",
40
+ "additionalProperties": false,
41
+ "required": ["label", "href"],
42
+ "properties": {
43
+ "label": { "type": "string", "minLength": 1 },
44
+ "href": { "type": "string", "minLength": 1 },
45
+ "ariaLabel": { "type": "string", "minLength": 1 },
46
+ "target": { "$ref": "#/$defs/Target" },
47
+ "rel": { "type": "string", "minLength": 1 },
48
+ "variant": { "$ref": "#/$defs/Variant" }
49
+ }
50
+ },
51
+ "BrandDetails": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["name"],
55
+ "properties": {
56
+ "name": { "type": "string", "minLength": 1 },
57
+ "homeHref": { "type": "string", "minLength": 1 },
58
+ "navLinks": {
59
+ "type": "array",
60
+ "default": [],
61
+ "items": { "$ref": "#/$defs/BrandNavLink" }
62
+ },
63
+ "primaryAction": { "$ref": "#/$defs/BrandAction" },
64
+ "secondaryAction": { "$ref": "#/$defs/BrandAction" },
65
+ "linkedin": { "type": "string", "minLength": 1 },
66
+ "gmail": { "type": "string", "minLength": 1 },
67
+ "github": { "type": "string", "minLength": 1 },
68
+ "twitter": { "type": "string", "minLength": 1 },
69
+ "discord": { "type": "string", "minLength": 1 },
70
+ "website": { "type": "string", "minLength": 1 },
71
+ "tagline": { "type": "string", "minLength": 1 }
72
+ }
73
+ },
74
+ "BrandTheme": {
75
+ "type": "object",
76
+ "additionalProperties": false,
77
+ "properties": {
78
+ "primaryColor": { "type": "string", "minLength": 1 },
79
+ "backgroundColor": { "type": "string", "minLength": 1 },
80
+ "textColor": { "type": "string", "minLength": 1 },
81
+ "fontFamily": { "type": "string", "minLength": 1 },
82
+ "linkColor": { "type": "string", "minLength": 1 },
83
+ "socialIconSize": { "type": "string", "minLength": 1 },
84
+ "buttonTextColor": { "type": "string", "minLength": 1 },
85
+ "ctaLayout": { "type": "string", "enum": ["inline", "stacked"] }
86
+ }
87
+ }
88
+ }
89
+ }