@dbx-tools/ui-branding 0.1.23

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.
@@ -0,0 +1,44 @@
1
+ {
2
+ "dependencies": [
3
+ {
4
+ "name": "@types/node",
5
+ "version": "^24.6.0",
6
+ "type": "build"
7
+ },
8
+ {
9
+ "name": "@types/react-dom",
10
+ "version": "catalog:",
11
+ "type": "build"
12
+ },
13
+ {
14
+ "name": "@types/react",
15
+ "version": "catalog:",
16
+ "type": "build"
17
+ },
18
+ {
19
+ "name": "tsx",
20
+ "version": "^4.23.0",
21
+ "type": "build"
22
+ },
23
+ {
24
+ "name": "typescript",
25
+ "type": "build"
26
+ },
27
+ {
28
+ "name": "@dbx-tools/shared-core",
29
+ "version": "workspace:*",
30
+ "type": "runtime"
31
+ },
32
+ {
33
+ "name": "react-dom",
34
+ "version": "catalog:",
35
+ "type": "runtime"
36
+ },
37
+ {
38
+ "name": "react",
39
+ "version": "catalog:",
40
+ "type": "runtime"
41
+ }
42
+ ],
43
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\"."
44
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "files": [
3
+ ".projen/deps.json",
4
+ ".projen/files.json",
5
+ ".projen/tasks.json",
6
+ "package.json",
7
+ "test/tsconfig.json",
8
+ "tsconfig.json"
9
+ ],
10
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\"."
11
+ }
@@ -0,0 +1,126 @@
1
+ {
2
+ "manifestVersion": 3,
3
+ "env": {
4
+ "PATH": "$(pnpm -c exec \"node --print process.env.PATH\")"
5
+ },
6
+ "tasks": {
7
+ "build": {
8
+ "name": "build",
9
+ "description": "Full release build",
10
+ "steps": [
11
+ {
12
+ "spawn": "pre-compile"
13
+ },
14
+ {
15
+ "spawn": "compile"
16
+ },
17
+ {
18
+ "spawn": "post-compile"
19
+ },
20
+ {
21
+ "spawn": "test"
22
+ },
23
+ {
24
+ "spawn": "package"
25
+ }
26
+ ]
27
+ },
28
+ "compile": {
29
+ "name": "compile",
30
+ "description": "Only compile",
31
+ "steps": [
32
+ {
33
+ "execArgs": [
34
+ "tsc",
35
+ "--build"
36
+ ]
37
+ }
38
+ ]
39
+ },
40
+ "default": {
41
+ "name": "default",
42
+ "description": "Synthesize project files",
43
+ "steps": [
44
+ {
45
+ "exec": "pnpm exec projen default",
46
+ "cwd": "../../.."
47
+ }
48
+ ]
49
+ },
50
+ "install": {
51
+ "name": "install",
52
+ "description": "Install project dependencies and update lockfile (non-frozen)",
53
+ "steps": [
54
+ {
55
+ "exec": "pnpm i --no-frozen-lockfile"
56
+ }
57
+ ]
58
+ },
59
+ "install:ci": {
60
+ "name": "install:ci",
61
+ "description": "Install project dependencies using frozen lockfile",
62
+ "steps": [
63
+ {
64
+ "exec": "pnpm i --frozen-lockfile"
65
+ }
66
+ ]
67
+ },
68
+ "package": {
69
+ "name": "package",
70
+ "description": "Creates the distribution package",
71
+ "steps": [
72
+ {
73
+ "execArgs": [
74
+ "mkdir",
75
+ "-p",
76
+ "dist/js"
77
+ ]
78
+ },
79
+ {
80
+ "execArgs": [
81
+ "pnpm",
82
+ "pack",
83
+ "--pack-destination",
84
+ "dist/js"
85
+ ]
86
+ }
87
+ ]
88
+ },
89
+ "post-compile": {
90
+ "name": "post-compile",
91
+ "description": "Runs after successful compilation"
92
+ },
93
+ "pre-compile": {
94
+ "name": "pre-compile",
95
+ "description": "Prepare the project for compilation",
96
+ "steps": [
97
+ {
98
+ "exec": "node ../../../branding/generate-package-assets.mjs"
99
+ }
100
+ ]
101
+ },
102
+ "test": {
103
+ "name": "test",
104
+ "description": "Run tests",
105
+ "steps": [
106
+ {
107
+ "exec": "tsx --test 'test/**/*.test.ts'"
108
+ }
109
+ ]
110
+ },
111
+ "watch": {
112
+ "name": "watch",
113
+ "description": "Watch & compile in the background",
114
+ "steps": [
115
+ {
116
+ "execArgs": [
117
+ "tsc",
118
+ "--build",
119
+ "-w"
120
+ ]
121
+ }
122
+ ]
123
+ }
124
+ },
125
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\"."
126
+ }
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @dbx-tools/ui-branding
2
+
3
+ Portable dbx tools brand assets and React/browser bindings for a validated
4
+ `BrandContext` from [`@dbx-tools/shared-core`](../../shared/core).
5
+
6
+ Key features:
7
+
8
+ - Theme-aware dbx tools icon and logo assets as SVG package exports and data URLs.
9
+ - `BrandProvider`, `BrandIcon`, and `BrandLogo` React bindings.
10
+ - Framework-agnostic CSS variable and document metadata helpers.
11
+ - No duplicated hand-maintained artwork: package assets are generated from the
12
+ root [`branding`](../../../branding) source files.
13
+
14
+ ## React
15
+
16
+ ```tsx
17
+ import { BrandIcon, BrandLogo, BrandProvider } from "@dbx-tools/ui-branding/react";
18
+
19
+ <BrandProvider applyToDocument>
20
+ <BrandIcon width={32} height={32} />
21
+ <BrandLogo width={240} />
22
+ </BrandProvider>;
23
+ ```
24
+
25
+ Pass parsed YAML/JSON data through `context` to use another brand. Missing
26
+ fields receive the dbx tools defaults defined by the shared Zod schema.
27
+
28
+ ## Browser Helpers
29
+
30
+ ```ts
31
+ import { applyBrandContext } from "@dbx-tools/ui-branding/browser";
32
+ import { brand } from "@dbx-tools/shared-core";
33
+
34
+ applyBrandContext(brand.parseBrandContext(input));
35
+ ```
36
+
37
+ Import `@dbx-tools/ui-branding/styles.css` once to expose the default CSS custom
38
+ properties. `applyBrandContext()` updates those properties and can set the page
39
+ title and favicon.
40
+
41
+ ## Theming the UI (the `[data-brand]` token bridge)
42
+
43
+ The dbx-tools UI components style off AppKit's shadcn semantic tokens
44
+ (`--primary`, `--ring`, `--sidebar-primary`, `--font-sans`, ...), not the
45
+ `--brand-color-*` variables directly. `brand-bridge.css` connects the two: it
46
+ remaps those AppKit tokens onto the brand variables, scoped to
47
+ `:root[data-brand]`.
48
+
49
+ It is imported (inert) through `@dbx-tools/ui-appkit/styles.css` — the shared UI
50
+ base every feature UI package depends on — so **every dbx-tools UI component
51
+ carries the bridge automatically**. It stays inert until a brand is applied:
52
+ `applyBrandContext()` (and therefore `<BrandProvider applyToDocument>`) sets the
53
+ `data-brand` attribute, which is what activates the remap. With no `data-brand`,
54
+ default AppKit renders untouched.
55
+
56
+ ```tsx
57
+ // Theme the whole app: sets brand vars + data-brand + title/favicon.
58
+ <BrandProvider applyToDocument>
59
+ <App />
60
+ </BrandProvider>
61
+ ```
62
+
63
+ The bridge is **identity-only** (primary/accent/ring/sidebar-primary + fonts). It
64
+ intentionally does not remap neutrals (`--background`/`--foreground`/`--muted`/
65
+ `--border`) because a `BrandContext` carries a single light palette — remapping
66
+ neutrals would disable dark mode. Add new semantic tokens to re-skin in
67
+ `brand-bridge.css`.
68
+
69
+ ## Assets
70
+
71
+ ```ts
72
+ import { dbxToolsAssetDataUrls } from "@dbx-tools/ui-branding/assets";
73
+ ```
74
+
75
+ Static SVG files are also exported from
76
+ `@dbx-tools/ui-branding/assets/icon-light.svg`, `icon-dark.svg`,
77
+ `logo-light.svg`, and `logo-dark.svg`.
78
+
79
+ ## Modules
80
+
81
+ - `./react` - provider, hook, and theme-aware icon/logo components.
82
+ - `./browser` - CSS token, metadata, and built-in asset resolution helpers.
83
+ - `./assets` - generated SVG strings and data URLs.
84
+ - `./assets/*.svg` - static package asset files.
85
+ - `./styles.css` - default brand CSS custom properties.
86
+ - `./brand-bridge.css` - `:root[data-brand]`-scoped remap of AppKit semantic
87
+ tokens onto the brand variables. Imported via `ui-appkit/styles.css`; inert
88
+ until `applyBrandContext()` sets `data-brand`.
package/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as browser from "./src/browser";
6
+ export * as generatedAssets from "./src/generated/assets";
7
+ export * as reactBrand from "./src/react/brand";
8
+ export type { BrandAssetResolver, ApplyBrandContextOptions } from "./src/browser";
9
+ export type { BrandProviderProps, BrandImageProps } from "./src/react/brand";
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@dbx-tools/ui-branding",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/reggie-db/dbx-tools.git",
6
+ "directory": "workspaces/ui/branding"
7
+ },
8
+ "devDependencies": {
9
+ "@types/node": "^24.6.0",
10
+ "@types/react": "^19.2.2",
11
+ "@types/react-dom": "^19.2.2",
12
+ "tsx": "^4.23.0",
13
+ "typescript": "^5.9.3"
14
+ },
15
+ "dependencies": {
16
+ "react": "^19.2.4",
17
+ "react-dom": "^19.2.4",
18
+ "@dbx-tools/shared-core": "0.1.23"
19
+ },
20
+ "main": "index.ts",
21
+ "license": "UNLICENSED",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "version": "0.1.23",
26
+ "types": "index.ts",
27
+ "type": "module",
28
+ "exports": {
29
+ "./react": "./src/react/index.ts",
30
+ "./styles.css": "./src/styles.css",
31
+ "./browser": "./src/browser.ts",
32
+ "./brand-bridge.css": "./src/brand-bridge.css",
33
+ "./assets": "./src/generated/assets.ts",
34
+ "./assets/icon-light.svg": "./src/generated/icon-light.svg",
35
+ "./assets/icon-dark.svg": "./src/generated/icon-dark.svg",
36
+ "./assets/logo-light.svg": "./src/generated/logo-light.svg",
37
+ "./assets/logo-dark.svg": "./src/generated/logo-dark.svg",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "dbxToolsConfig": {
41
+ "tags": [
42
+ "ui"
43
+ ]
44
+ },
45
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\".",
46
+ "scripts": {
47
+ "build": "projen build",
48
+ "compile": "projen compile",
49
+ "default": "projen default",
50
+ "package": "projen package",
51
+ "post-compile": "projen post-compile",
52
+ "pre-compile": "projen pre-compile",
53
+ "test": "projen test",
54
+ "watch": "projen watch",
55
+ "projen": "projen"
56
+ }
57
+ }
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Brand -> AppKit token bridge.
3
+ *
4
+ * Remaps the AppKit / shadcn semantic tokens the dbx-tools UI components
5
+ * actually style off (`--primary`, `--ring`, `--accent`, `--sidebar-*`,
6
+ * `--font-*`) onto the portable `--brand-color-*` / `--brand-font-*` custom
7
+ * properties that `applyBrandContext()` writes. This is the "tag selector":
8
+ * scoped to `:root[data-brand]`, it is completely INERT until a brand is
9
+ * applied to the document (which sets the `data-brand` attribute), so
10
+ * importing it never disturbs default AppKit.
11
+ *
12
+ * Import once via `@dbx-tools/ui-appkit/styles.css` (the shared UI base every
13
+ * feature UI package pulls in), so the bridge travels with every dbx-tools UI
14
+ * component automatically.
15
+ *
16
+ * Scope note: `:root[data-brand]` has higher specificity (0,2,0) than AppKit's
17
+ * `:root` and `.dark` (both 0,1,0), so these win regardless of source order.
18
+ *
19
+ * Deliberately identity-only: it does NOT remap neutrals (`--background`,
20
+ * `--foreground`, `--card`, `--muted`, `--border`). The brand context carries a
21
+ * single light palette, so remapping neutrals would break dark mode; leaving
22
+ * them to AppKit keeps light AND dark themes working while the brand's identity
23
+ * colors + fonts come through.
24
+ */
25
+ :root[data-brand] {
26
+ --primary: var(--brand-color-primary);
27
+ --primary-foreground: var(--brand-color-background);
28
+ --ring: var(--brand-color-primary);
29
+ --accent: var(--brand-color-accent);
30
+ --sidebar-primary: var(--brand-color-primary);
31
+ --sidebar-primary-foreground: var(--brand-color-background);
32
+ --sidebar-ring: var(--brand-color-primary);
33
+ --font-sans: var(--brand-font-sans);
34
+ --font-mono: var(--brand-font-mono);
35
+ }
package/src/browser.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { brand } from "@dbx-tools/shared-core";
2
+ import { dbxToolsAssetDataUrls } from "./generated/assets";
3
+
4
+ const builtInAssets = new Map<string, string>([
5
+ [brand.DEFAULT_BRAND_ASSETS.icon.light, dbxToolsAssetDataUrls.iconLight],
6
+ [brand.DEFAULT_BRAND_ASSETS.icon.dark, dbxToolsAssetDataUrls.iconDark],
7
+ [brand.DEFAULT_BRAND_ASSETS.logo.light, dbxToolsAssetDataUrls.logoLight],
8
+ [brand.DEFAULT_BRAND_ASSETS.logo.dark, dbxToolsAssetDataUrls.logoDark],
9
+ [brand.DEFAULT_BRAND_ASSETS.favicon, dbxToolsAssetDataUrls.iconLight],
10
+ ]);
11
+
12
+ export type BrandAssetResolver = (source: string) => string;
13
+
14
+ /** Resolve built-in package asset ids to portable data URLs. */
15
+ export const resolveBrandAsset: BrandAssetResolver = (source) =>
16
+ builtInAssets.get(source) ?? source;
17
+
18
+ /** Convert a validated context to CSS custom properties. */
19
+ export function brandCssVariables(context: brand.BrandContext): Record<string, string> {
20
+ return {
21
+ "--brand-color-primary": context.colors.primary,
22
+ "--brand-color-primary-hover": context.colors.primaryHover,
23
+ "--brand-color-accent": context.colors.accent,
24
+ "--brand-color-foreground": context.colors.foreground,
25
+ "--brand-color-background": context.colors.background,
26
+ "--brand-color-surface": context.colors.surface,
27
+ "--brand-color-muted": context.colors.muted,
28
+ "--brand-color-border": context.colors.border,
29
+ "--brand-font-sans": context.typography.sans,
30
+ "--brand-font-mono": context.typography.mono,
31
+ };
32
+ }
33
+
34
+ export interface ApplyBrandContextOptions {
35
+ root?: HTMLElement;
36
+ document?: Document;
37
+ resolveAsset?: BrandAssetResolver;
38
+ updateTitle?: boolean;
39
+ updateFavicon?: boolean;
40
+ }
41
+
42
+ /** Apply tokens and optional page metadata to a browser document. */
43
+ export function applyBrandContext(
44
+ context: brand.BrandContext,
45
+ options: ApplyBrandContextOptions = {},
46
+ ): void {
47
+ const documentRef = options.document ?? globalThis.document;
48
+ const root = options.root ?? documentRef?.documentElement;
49
+ for (const [name, value] of Object.entries(brandCssVariables(context))) {
50
+ root?.style.setProperty(name, value);
51
+ }
52
+ // Detection flag for the `brand-bridge.css` tag selector: setting
53
+ // `data-brand` activates the `:root[data-brand]` token remap. Until a
54
+ // brand is applied the bridge is inert, so default AppKit is untouched.
55
+ root?.setAttribute("data-brand", context.name);
56
+
57
+ if (documentRef && options.updateTitle !== false) documentRef.title = context.name;
58
+ if (!documentRef || options.updateFavicon === false) return;
59
+
60
+ const resolveAsset = options.resolveAsset ?? resolveBrandAsset;
61
+ let favicon = documentRef.querySelector<HTMLLinkElement>('link[rel="icon"]');
62
+ if (!favicon) {
63
+ favicon = documentRef.createElement("link");
64
+ favicon.rel = "icon";
65
+ documentRef.head.append(favicon);
66
+ }
67
+ favicon.href = resolveAsset(context.assets.favicon);
68
+ }
@@ -0,0 +1,16 @@
1
+ // Generated from branding/assets by branding/generate-package-assets.mjs.
2
+ // Do not edit this file or the neighboring generated SVG copies.
3
+
4
+ export const dbxToolsAssetSvg = {
5
+ iconLight: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\" role=\"img\" aria-labelledby=\"title description\">\n <title id=\"title\">dbx tools icon</title>\n <desc id=\"description\">Pixel tool mark in dbx tools lava red on a light surface.</desc>\n <rect width=\"64\" height=\"64\" rx=\"12\" fill=\"#F6F7F8\"/>\n <g shape-rendering=\"crispEdges\">\n <path d=\"M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z\" fill=\"#FF3621\"/>\n <path d=\"M4 28h8v8H4zm52 0h4v8h-4z\" fill=\"#D92D18\"/>\n <path d=\"M28 28h8v8h-8z\" fill=\"#FF8974\"/>\n </g>\n</svg>\n",
6
+ iconDark: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\" role=\"img\" aria-labelledby=\"title description\">\n <title id=\"title\">dbx tools icon</title>\n <desc id=\"description\">Pixel tool mark in dbx tools lava red on a dark surface.</desc>\n <rect width=\"64\" height=\"64\" rx=\"12\" fill=\"#0B2026\"/>\n <g shape-rendering=\"crispEdges\">\n <path d=\"M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z\" fill=\"#FF3621\"/>\n <path d=\"M4 28h8v8H4zm52 0h4v8h-4z\" fill=\"#FF5A46\"/>\n <path d=\"M28 28h8v8h-8z\" fill=\"#FF8974\"/>\n </g>\n</svg>\n",
7
+ logoLight: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 72\" role=\"img\" aria-labelledby=\"title description\">\n <title id=\"title\">dbx tools</title>\n <desc id=\"description\">dbx tools logo for light surfaces.</desc>\n <g transform=\"translate(4 4)\">\n <rect width=\"64\" height=\"64\" rx=\"12\" fill=\"#F6F7F8\"/>\n <g shape-rendering=\"crispEdges\">\n <path d=\"M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z\" fill=\"#FF3621\"/>\n <path d=\"M4 28h8v8H4zm52 0h4v8h-4z\" fill=\"#D92D18\"/>\n <path d=\"M28 28h8v8h-8z\" fill=\"#FF8974\"/>\n </g>\n </g>\n <text x=\"84\" y=\"47\" font-family=\"Inter, ui-sans-serif, system-ui, sans-serif\" font-size=\"32\" font-weight=\"700\" letter-spacing=\"0\" fill=\"#0B2026\">dbx <tspan fill=\"#FF3621\">tools</tspan></text>\n</svg>\n",
8
+ logoDark: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 72\" role=\"img\" aria-labelledby=\"title description\">\n <title id=\"title\">dbx tools</title>\n <desc id=\"description\">dbx tools logo for dark surfaces.</desc>\n <g transform=\"translate(4 4)\">\n <rect width=\"64\" height=\"64\" rx=\"12\" fill=\"#0B2026\"/>\n <g shape-rendering=\"crispEdges\">\n <path d=\"M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z\" fill=\"#FF3621\"/>\n <path d=\"M4 28h8v8H4zm52 0h4v8h-4z\" fill=\"#FF5A46\"/>\n <path d=\"M28 28h8v8h-8z\" fill=\"#FF8974\"/>\n </g>\n </g>\n <text x=\"84\" y=\"47\" font-family=\"Inter, ui-sans-serif, system-ui, sans-serif\" font-size=\"32\" font-weight=\"700\" letter-spacing=\"0\" fill=\"#F6F7F8\">dbx <tspan fill=\"#FF5A46\">tools</tspan></text>\n</svg>\n",
9
+ } as const;
10
+
11
+ export const dbxToolsAssetDataUrls = {
12
+ iconLight: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsbGVkYnk9InRpdGxlIGRlc2NyaXB0aW9uIj4KICA8dGl0bGUgaWQ9InRpdGxlIj5kYnggdG9vbHMgaWNvbjwvdGl0bGU+CiAgPGRlc2MgaWQ9ImRlc2NyaXB0aW9uIj5QaXhlbCB0b29sIG1hcmsgaW4gZGJ4IHRvb2xzIGxhdmEgcmVkIG9uIGEgbGlnaHQgc3VyZmFjZS48L2Rlc2M+CiAgPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTIiIGZpbGw9IiNGNkY3RjgiLz4KICA8ZyBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiPgogICAgPHBhdGggZD0iTTQgMjhoOHY4SDR6bTggMGg4djE2aC04em04LThoOHYxNmgtOHptOCA4aDh2OGgtOHptOC04aDh2MTZoLTh6bTggOGg4djE2aC04em04IDBoNHY4aC00eiIgZmlsbD0iI0ZGMzYyMSIvPgogICAgPHBhdGggZD0iTTQgMjhoOHY4SDR6bTUyIDBoNHY4aC00eiIgZmlsbD0iI0Q5MkQxOCIvPgogICAgPHBhdGggZD0iTTI4IDI4aDh2OGgtOHoiIGZpbGw9IiNGRjg5NzQiLz4KICA8L2c+Cjwvc3ZnPgo=",
13
+ iconDark: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsbGVkYnk9InRpdGxlIGRlc2NyaXB0aW9uIj4KICA8dGl0bGUgaWQ9InRpdGxlIj5kYnggdG9vbHMgaWNvbjwvdGl0bGU+CiAgPGRlc2MgaWQ9ImRlc2NyaXB0aW9uIj5QaXhlbCB0b29sIG1hcmsgaW4gZGJ4IHRvb2xzIGxhdmEgcmVkIG9uIGEgZGFyayBzdXJmYWNlLjwvZGVzYz4KICA8cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHJ4PSIxMiIgZmlsbD0iIzBCMjAyNiIvPgogIDxnIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyI+CiAgICA8cGF0aCBkPSJNNCAyOGg4djhINHptOCAwaDh2MTZoLTh6bTgtOGg4djE2aC04em04IDhoOHY4aC04em04LThoOHYxNmgtOHptOCA4aDh2MTZoLTh6bTggMGg0djhoLTR6IiBmaWxsPSIjRkYzNjIxIi8+CiAgICA8cGF0aCBkPSJNNCAyOGg4djhINHptNTIgMGg0djhoLTR6IiBmaWxsPSIjRkY1QTQ2Ii8+CiAgICA8cGF0aCBkPSJNMjggMjhoOHY4aC04eiIgZmlsbD0iI0ZGODk3NCIvPgogIDwvZz4KPC9zdmc+Cg==",
14
+ logoLight: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMjAgNzIiIHJvbGU9ImltZyIgYXJpYS1sYWJlbGxlZGJ5PSJ0aXRsZSBkZXNjcmlwdGlvbiI+CiAgPHRpdGxlIGlkPSJ0aXRsZSI+ZGJ4IHRvb2xzPC90aXRsZT4KICA8ZGVzYyBpZD0iZGVzY3JpcHRpb24iPmRieCB0b29scyBsb2dvIGZvciBsaWdodCBzdXJmYWNlcy48L2Rlc2M+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCA0KSI+CiAgICA8cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHJ4PSIxMiIgZmlsbD0iI0Y2RjdGOCIvPgogICAgPGcgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KICAgICAgPHBhdGggZD0iTTQgMjhoOHY4SDR6bTggMGg4djE2aC04em04LThoOHYxNmgtOHptOCA4aDh2OGgtOHptOC04aDh2MTZoLTh6bTggOGg4djE2aC04em04IDBoNHY4aC00eiIgZmlsbD0iI0ZGMzYyMSIvPgogICAgICA8cGF0aCBkPSJNNCAyOGg4djhINHptNTIgMGg0djhoLTR6IiBmaWxsPSIjRDkyRDE4Ii8+CiAgICAgIDxwYXRoIGQ9Ik0yOCAyOGg4djhoLTh6IiBmaWxsPSIjRkY4OTc0Ii8+CiAgICA8L2c+CiAgPC9nPgogIDx0ZXh0IHg9Ijg0IiB5PSI0NyIgZm9udC1mYW1pbHk9IkludGVyLCB1aS1zYW5zLXNlcmlmLCBzeXN0ZW0tdWksIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMzIiIGZvbnQtd2VpZ2h0PSI3MDAiIGxldHRlci1zcGFjaW5nPSIwIiBmaWxsPSIjMEIyMDI2Ij5kYnggPHRzcGFuIGZpbGw9IiNGRjM2MjEiPnRvb2xzPC90c3Bhbj48L3RleHQ+Cjwvc3ZnPgo=",
15
+ logoDark: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMjAgNzIiIHJvbGU9ImltZyIgYXJpYS1sYWJlbGxlZGJ5PSJ0aXRsZSBkZXNjcmlwdGlvbiI+CiAgPHRpdGxlIGlkPSJ0aXRsZSI+ZGJ4IHRvb2xzPC90aXRsZT4KICA8ZGVzYyBpZD0iZGVzY3JpcHRpb24iPmRieCB0b29scyBsb2dvIGZvciBkYXJrIHN1cmZhY2VzLjwvZGVzYz4KICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDQpIj4KICAgIDxyZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgcng9IjEyIiBmaWxsPSIjMEIyMDI2Ii8+CiAgICA8ZyBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiPgogICAgICA8cGF0aCBkPSJNNCAyOGg4djhINHptOCAwaDh2MTZoLTh6bTgtOGg4djE2aC04em04IDhoOHY4aC04em04LThoOHYxNmgtOHptOCA4aDh2MTZoLTh6bTggMGg0djhoLTR6IiBmaWxsPSIjRkYzNjIxIi8+CiAgICAgIDxwYXRoIGQ9Ik00IDI4aDh2OEg0em01MiAwaDR2OGgtNHoiIGZpbGw9IiNGRjVBNDYiLz4KICAgICAgPHBhdGggZD0iTTI4IDI4aDh2OGgtOHoiIGZpbGw9IiNGRjg5NzQiLz4KICAgIDwvZz4KICA8L2c+CiAgPHRleHQgeD0iODQiIHk9IjQ3IiBmb250LWZhbWlseT0iSW50ZXIsIHVpLXNhbnMtc2VyaWYsIHN5c3RlbS11aSwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIzMiIgZm9udC13ZWlnaHQ9IjcwMCIgbGV0dGVyLXNwYWNpbmc9IjAiIGZpbGw9IiNGNkY3RjgiPmRieCA8dHNwYW4gZmlsbD0iI0ZGNUE0NiI+dG9vbHM8L3RzcGFuPjwvdGV4dD4KPC9zdmc+Cg==",
16
+ } as const;
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title description">
2
+ <title id="title">dbx tools icon</title>
3
+ <desc id="description">Pixel tool mark in dbx tools lava red on a dark surface.</desc>
4
+ <rect width="64" height="64" rx="12" fill="#0B2026"/>
5
+ <g shape-rendering="crispEdges">
6
+ <path d="M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z" fill="#FF3621"/>
7
+ <path d="M4 28h8v8H4zm52 0h4v8h-4z" fill="#FF5A46"/>
8
+ <path d="M28 28h8v8h-8z" fill="#FF8974"/>
9
+ </g>
10
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title description">
2
+ <title id="title">dbx tools icon</title>
3
+ <desc id="description">Pixel tool mark in dbx tools lava red on a light surface.</desc>
4
+ <rect width="64" height="64" rx="12" fill="#F6F7F8"/>
5
+ <g shape-rendering="crispEdges">
6
+ <path d="M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z" fill="#FF3621"/>
7
+ <path d="M4 28h8v8H4zm52 0h4v8h-4z" fill="#D92D18"/>
8
+ <path d="M28 28h8v8h-8z" fill="#FF8974"/>
9
+ </g>
10
+ </svg>
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 72" role="img" aria-labelledby="title description">
2
+ <title id="title">dbx tools</title>
3
+ <desc id="description">dbx tools logo for dark surfaces.</desc>
4
+ <g transform="translate(4 4)">
5
+ <rect width="64" height="64" rx="12" fill="#0B2026"/>
6
+ <g shape-rendering="crispEdges">
7
+ <path d="M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z" fill="#FF3621"/>
8
+ <path d="M4 28h8v8H4zm52 0h4v8h-4z" fill="#FF5A46"/>
9
+ <path d="M28 28h8v8h-8z" fill="#FF8974"/>
10
+ </g>
11
+ </g>
12
+ <text x="84" y="47" font-family="Inter, ui-sans-serif, system-ui, sans-serif" font-size="32" font-weight="700" letter-spacing="0" fill="#F6F7F8">dbx <tspan fill="#FF5A46">tools</tspan></text>
13
+ </svg>
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 72" role="img" aria-labelledby="title description">
2
+ <title id="title">dbx tools</title>
3
+ <desc id="description">dbx tools logo for light surfaces.</desc>
4
+ <g transform="translate(4 4)">
5
+ <rect width="64" height="64" rx="12" fill="#F6F7F8"/>
6
+ <g shape-rendering="crispEdges">
7
+ <path d="M4 28h8v8H4zm8 0h8v16h-8zm8-8h8v16h-8zm8 8h8v8h-8zm8-8h8v16h-8zm8 8h8v16h-8zm8 0h4v8h-4z" fill="#FF3621"/>
8
+ <path d="M4 28h8v8H4zm52 0h4v8h-4z" fill="#D92D18"/>
9
+ <path d="M28 28h8v8h-8z" fill="#FF8974"/>
10
+ </g>
11
+ </g>
12
+ <text x="84" y="47" font-family="Inter, ui-sans-serif, system-ui, sans-serif" font-size="32" font-weight="700" letter-spacing="0" fill="#0B2026">dbx <tspan fill="#FF3621">tools</tspan></text>
13
+ </svg>
@@ -0,0 +1,86 @@
1
+ import {
2
+ createContext,
3
+ type ImgHTMLAttributes,
4
+ type PropsWithChildren,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ } from "react";
9
+ import { brand } from "@dbx-tools/shared-core";
10
+ import { applyBrandContext, type BrandAssetResolver, resolveBrandAsset } from "../browser";
11
+
12
+ interface BrandState {
13
+ context: brand.BrandContext;
14
+ resolveAsset: BrandAssetResolver;
15
+ }
16
+
17
+ const BrandReactContext = createContext<BrandState>({
18
+ context: brand.defaultBrandContext,
19
+ resolveAsset: resolveBrandAsset,
20
+ });
21
+
22
+ export interface BrandProviderProps extends PropsWithChildren {
23
+ context?: brand.BrandContextInput;
24
+ resolveAsset?: BrandAssetResolver;
25
+ applyToDocument?: boolean;
26
+ }
27
+
28
+ export function BrandProvider({
29
+ children,
30
+ context,
31
+ resolveAsset = resolveBrandAsset,
32
+ applyToDocument = false,
33
+ }: BrandProviderProps) {
34
+ const parsed = useMemo(() => brand.parseBrandContext(context), [context]);
35
+ const value = useMemo(() => ({ context: parsed, resolveAsset }), [parsed, resolveAsset]);
36
+
37
+ useEffect(() => {
38
+ if (applyToDocument) applyBrandContext(parsed, { resolveAsset });
39
+ }, [applyToDocument, parsed, resolveAsset]);
40
+
41
+ return <BrandReactContext.Provider value={value}>{children}</BrandReactContext.Provider>;
42
+ }
43
+
44
+ export function useBrand(): BrandState {
45
+ return useContext(BrandReactContext);
46
+ }
47
+
48
+ export interface BrandImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt"> {
49
+ alt?: string;
50
+ mode?: "auto" | "light" | "dark";
51
+ }
52
+
53
+ function BrandImage({
54
+ asset,
55
+ alt,
56
+ mode = "auto",
57
+ ...props
58
+ }: BrandImageProps & { asset: brand.BrandAssetSet }) {
59
+ const { resolveAsset } = useBrand();
60
+ const light = resolveAsset(asset.light);
61
+ const dark = resolveAsset(asset.dark ?? asset.light);
62
+
63
+ if (mode !== "auto") {
64
+ return <img src={mode === "dark" ? dark : light} alt={alt ?? ""} {...props} />;
65
+ }
66
+ return (
67
+ <picture>
68
+ <source media="(prefers-color-scheme: dark)" srcSet={dark} />
69
+ <img src={light} alt={alt ?? ""} {...props} />
70
+ </picture>
71
+ );
72
+ }
73
+
74
+ export function BrandIcon(props: BrandImageProps) {
75
+ const { context } = useBrand();
76
+ const { alt, ...imageProps } = props;
77
+ return (
78
+ <BrandImage asset={context.assets.icon} alt={alt ?? `${context.name} icon`} {...imageProps} />
79
+ );
80
+ }
81
+
82
+ export function BrandLogo(props: BrandImageProps) {
83
+ const { context } = useBrand();
84
+ const { alt, ...imageProps } = props;
85
+ return <BrandImage asset={context.assets.logo} alt={alt ?? context.name} {...imageProps} />;
86
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ BrandIcon,
3
+ BrandLogo,
4
+ BrandProvider,
5
+ useBrand,
6
+ type BrandImageProps,
7
+ type BrandProviderProps,
8
+ } from "./brand";
9
+ export type { BrandContext, BrandContextInput } from "@dbx-tools/shared-core";
package/src/styles.css ADDED
@@ -0,0 +1,12 @@
1
+ :root {
2
+ --brand-color-primary: #ff3621;
3
+ --brand-color-primary-hover: #d92d18;
4
+ --brand-color-accent: #00a972;
5
+ --brand-color-foreground: #0b2026;
6
+ --brand-color-background: #ffffff;
7
+ --brand-color-surface: #f6f7f8;
8
+ --brand-color-muted: #5f6b70;
9
+ --brand-color-border: #dce2e5;
10
+ --brand-font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
11
+ --brand-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
12
+ }
@@ -0,0 +1,14 @@
1
+ // ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
2
+ {
3
+ "extends": "../tsconfig.json",
4
+ "compilerOptions": {
5
+ "noEmit": true,
6
+ "rootDir": ".."
7
+ },
8
+ "include": [
9
+ "**/*.ts"
10
+ ],
11
+ "exclude": [
12
+ "node_modules"
13
+ ]
14
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,43 @@
1
+ // ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
2
+ {
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "lib",
6
+ "alwaysStrict": true,
7
+ "declaration": true,
8
+ "esModuleInterop": true,
9
+ "experimentalDecorators": true,
10
+ "inlineSourceMap": true,
11
+ "inlineSources": true,
12
+ "lib": [
13
+ "ES2022",
14
+ "DOM",
15
+ "DOM.Iterable"
16
+ ],
17
+ "module": "ESNext",
18
+ "noEmitOnError": false,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "noImplicitAny": true,
21
+ "noImplicitReturns": true,
22
+ "noImplicitThis": true,
23
+ "noUnusedLocals": true,
24
+ "noUnusedParameters": true,
25
+ "resolveJsonModule": true,
26
+ "strict": true,
27
+ "strictNullChecks": true,
28
+ "strictPropertyInitialization": true,
29
+ "stripInternal": true,
30
+ "target": "ES2022",
31
+ "types": [],
32
+ "moduleResolution": "bundler",
33
+ "skipLibCheck": true,
34
+ "jsx": "react-jsx"
35
+ },
36
+ "include": [
37
+ "src/**/*.ts",
38
+ "src/**/*.tsx"
39
+ ],
40
+ "exclude": [
41
+ "node_modules"
42
+ ]
43
+ }