@bsuite/theme 0.4.1 → 0.5.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.
@@ -47,5 +47,5 @@ export interface BrandingProviderProps {
47
47
  */
48
48
  applyPersistedOnMount?: boolean;
49
49
  }
50
- export declare function BrandingProvider({ children, supabaseClient, brand, applyPersistedOnMount, }: BrandingProviderProps): import("react/jsx-runtime").JSX.Element;
50
+ export declare function BrandingProvider({ children, supabaseClient, brand, applyPersistedOnMount, }: BrandingProviderProps): import("react").JSX.Element;
51
51
  //# sourceMappingURL=BrandingProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BrandingProvider.d.ts","sourceRoot":"","sources":["../../src/react/BrandingProvider.tsx"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAO3D,eAAO,MAAM,oBAAoB,2BAA2B,CAAA;AAC5D,eAAO,MAAM,sBAAsB,kCAAkC,CAAA;AAErE,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+BAA+B;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,eAAO,MAAM,eAAe,2DAA6D,CAAA;AAEzF,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE9C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,SAAS,CAAA;IACnB,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAA;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AA+KD,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,cAAc,EACd,KAAgB,EAChB,qBAA4B,GAC7B,EAAE,qBAAqB,2CA+EvB"}
1
+ {"version":3,"file":"BrandingProvider.d.ts","sourceRoot":"","sources":["../../src/react/BrandingProvider.tsx"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAO3D,eAAO,MAAM,oBAAoB,2BAA2B,CAAA;AAC5D,eAAO,MAAM,sBAAsB,kCAAkC,CAAA;AAErE,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+BAA+B;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,eAAO,MAAM,eAAe,2DAA6D,CAAA;AAEzF,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE9C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,SAAS,CAAA;IACnB,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAA;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AA+KD,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,cAAc,EACd,KAAgB,EAChB,qBAA4B,GAC7B,EAAE,qBAAqB,+BA4GvB"}
@@ -214,6 +214,9 @@ export function BrandingProvider({ children, supabaseClient, brand = 'bsuite', a
214
214
  const [isLoading, setIsLoading] = useState(brand !== 'braden');
215
215
  const [error, setError] = useState(null);
216
216
  const channelRef = useRef(null);
217
+ // True when the last fetch was skipped (or failed) for lack of a session —
218
+ // the SIGNED_IN listener uses this to refetch exactly once when auth lands.
219
+ const needsAuthedRefetchRef = useRef(false);
217
220
  // ── Braden short-circuit ──────────────────────────────────────────────────
218
221
  // Braden corporate uses static CSS from @bsuite/theme/braden-css — no runtime
219
222
  // tenant overrides needed. Skip all Supabase calls entirely.
@@ -230,6 +233,18 @@ export function BrandingProvider({ children, supabaseClient, brand = 'bsuite', a
230
233
  setIsLoading(true);
231
234
  setError(null);
232
235
  try {
236
+ // branding_json_for_tenant is granted to `authenticated` only — calling
237
+ // it in anon context is a guaranteed PostgREST 401 (42501). Consumers
238
+ // can mount this provider before session hydration completes (or on
239
+ // genuinely public pages); skip the RPC and let defaults stand. The
240
+ // SIGNED_IN listener below refetches once auth arrives.
241
+ const { data: sessionData } = await supabaseClient.auth.getSession();
242
+ if (!sessionData.session) {
243
+ needsAuthedRefetchRef.current = true;
244
+ setIsLoading(false);
245
+ return;
246
+ }
247
+ needsAuthedRefetchRef.current = false;
233
248
  const { data, error: rpcError } = await supabaseClient.rpc('branding_json_for_tenant');
234
249
  if (rpcError)
235
250
  throw new Error(rpcError.message);
@@ -261,6 +276,19 @@ export function BrandingProvider({ children, supabaseClient, brand = 'bsuite', a
261
276
  useEffect(() => {
262
277
  void fetchBranding();
263
278
  }, [fetchBranding]);
279
+ // Refetch once when a session arrives after an anon-skipped mount (session
280
+ // hydration race). Deferred via setTimeout: onAuthStateChange callbacks run
281
+ // while supabase-js holds the auth lock, and fetchBranding calls
282
+ // getSession() — invoking it inline would self-deadlock (see the suite-wide
283
+ // "never await supabase calls inside onAuthStateChange" rule).
284
+ useEffect(() => {
285
+ const { data: { subscription } } = supabaseClient.auth.onAuthStateChange((event) => {
286
+ if (event === 'SIGNED_IN' && needsAuthedRefetchRef.current) {
287
+ setTimeout(() => { void fetchBranding(); }, 0);
288
+ }
289
+ });
290
+ return () => { subscription.unsubscribe(); };
291
+ }, [supabaseClient, fetchBranding]);
264
292
  // Subscribe to Realtime tenant row changes
265
293
  useEffect(() => {
266
294
  if (!isBrandingEnabled())
@@ -11,5 +11,5 @@ export interface ThemeProviderProps {
11
11
  */
12
12
  storageKey?: string;
13
13
  }
14
- export declare function ThemeProvider({ children, defaultTheme, storageKey, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function ThemeProvider({ children, defaultTheme, storageKey, }: ThemeProviderProps): import("react").JSX.Element;
15
15
  //# sourceMappingURL=ThemeProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/react/ThemeProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAyC,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAExG,eAAO,MAAM,YAAY,wDAA0D,CAAA;AAEnF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAA;IACnB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AA6BD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAuB,EACvB,UAA8B,GAC/B,EAAE,kBAAkB,2CA4DpB"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/react/ThemeProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAyC,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAExG,eAAO,MAAM,YAAY,wDAA0D,CAAA;AAEnF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAA;IACnB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AA6BD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAuB,EACvB,UAA8B,GAC/B,EAAE,kBAAkB,+BA4DpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/theme",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -61,18 +61,18 @@
61
61
  }
62
62
  },
63
63
  "devDependencies": {
64
- "@supabase/supabase-js": "^2.105.3",
64
+ "@supabase/supabase-js": "^2.108.1",
65
65
  "@testing-library/jest-dom": "^6.9.1",
66
66
  "@testing-library/react": "^16.3.2",
67
- "@types/node": "^24.12.2",
68
- "@types/react": "^19.2.14",
67
+ "@types/node": "^25.9.2",
68
+ "@types/react": "^19.2.17",
69
69
  "@types/react-dom": "^19.2.3",
70
- "@vitejs/plugin-react": "^6.0.1",
70
+ "@vitejs/plugin-react": "^6.0.2",
71
71
  "jsdom": "^29.1.1",
72
- "react": "^19.2.5",
73
- "react-dom": "^19.2.5",
74
- "typescript": "~5.9.3",
75
- "vite": "^8.0.10",
76
- "vitest": "^4.1.5"
72
+ "react": "^19.2.7",
73
+ "react-dom": "^19.2.7",
74
+ "typescript": "~6.0.3",
75
+ "vite": "^8.0.16",
76
+ "vitest": "^4.1.8"
77
77
  }
78
78
  }
@@ -164,6 +164,11 @@
164
164
  --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.08);
165
165
  --shadow-md: 0 4px 6px oklch(0 0 0 / 0.10), 0 2px 4px oklch(0 0 0 / 0.06);
166
166
  --shadow-lg: 0 10px 15px oklch(0 0 0 / 0.10), 0 4px 6px oklch(0 0 0 / 0.05);
167
+
168
+ /* Card glow is a dark-mode-only device (see .dark below); light mode
169
+ stays border-only, consistent with the corporate "no neon glow" rule. */
170
+ --glow-card: none;
171
+ --glow-card-hover: none;
167
172
  }
168
173
 
169
174
  /* ============================================================
@@ -206,4 +211,20 @@
206
211
  --muted-foreground:var(--braden-dark-text-muted);
207
212
  --border: oklch(0.30 0.020 250);
208
213
  --input: oklch(0.30 0.020 250);
214
+
215
+ /*
216
+ * Card glow — Braden dark mode gets a restrained gold separation glow,
217
+ * NOT the D2C neon treatment (no cyan, lower alpha, same two-layer
218
+ * ring+halo shape for consumer-code parity with @bsuite/theme's D2C
219
+ * --glow-card). Source is --app-accent (Braden Gold), which is
220
+ * inherited from :root since this file does not rebind it per-mode.
221
+ * Deliberately more subdued than D2C to honour the "corporate
222
+ * subtle, no neon glow" shadow philosophy above, adapted for dark UI
223
+ * where SOME separation glow is still needed for surface legibility.
224
+ */
225
+ --card-glow-source: var(--app-accent, var(--braden-gold-500));
226
+ --glow-card: 0 0 0 1px color-mix(in oklch, var(--card-glow-source) 18%, transparent),
227
+ 0 0 10px -4px color-mix(in oklch, var(--card-glow-source) 20%, transparent);
228
+ --glow-card-hover: 0 0 0 1px color-mix(in oklch, var(--card-glow-source) 26%, transparent),
229
+ 0 0 14px -4px color-mix(in oklch, var(--card-glow-source) 28%, transparent);
209
230
  }
@@ -77,8 +77,23 @@
77
77
  /* ===== Neon glow helpers ===== */
78
78
  --glow-button: 0 0 20px oklch(0.769 0.132 191.7 / 0.3);
79
79
  --glow-button-hover: 0 0 30px oklch(0.769 0.132 191.7 / 0.5);
80
- --glow-card: 0 0 15px oklch(0.769 0.132 191.7 / 0.08);
81
- --glow-card-hover: 0 0 25px oklch(0.769 0.132 191.7 / 0.15);
80
+
81
+ /*
82
+ * Card glow — subtle neon separation for card surfaces, dark mode only.
83
+ * Derived from the app's accent colour (--app-accent, falling back to
84
+ * the shared --role-accent default, matching the live vars.css) so
85
+ * per-tenant
86
+ * BrandingProvider overrides and per-app brand accents (Conduit green,
87
+ * R80.3 amber, etc.) automatically re-colour the glow. Two layers:
88
+ * a 1px accent ring (edge definition, replaces a hard border) plus a
89
+ * soft negative-spread halo (ambient separation). Kept subtle by
90
+ * design — this is surface separation, not a decorative outline.
91
+ */
92
+ --card-glow-source: var(--app-accent, var(--role-accent));
93
+ --glow-card: 0 0 0 1px color-mix(in oklch, var(--card-glow-source) 30%, transparent),
94
+ 0 0 14px -4px color-mix(in oklch, var(--card-glow-source) 35%, transparent);
95
+ --glow-card-hover: 0 0 0 1px color-mix(in oklch, var(--card-glow-source) 42%, transparent),
96
+ 0 0 20px -4px color-mix(in oklch, var(--card-glow-source) 48%, transparent);
82
97
 
83
98
  /* ===== Button shadow ===== */
84
99
  --button-shadow: 0 0 20px oklch(0.769 0.132 191.7 / 0.3);
package/src/css/vars.css CHANGED
@@ -160,6 +160,16 @@
160
160
  --glow-strength: 0.4;
161
161
  --shadow-strength: 1;
162
162
 
163
+ /* ============================================================
164
+ CARD SURFACE GLOW — subtle neon separation, dark mode only.
165
+ Light mode keeps the existing border-only treatment (--role-border);
166
+ these resolve to `none` here so any consumer applying the utility
167
+ unconditionally (i.e. without a `dark:` guard) degrades safely.
168
+ ============================================================ */
169
+ --card-glow-source: var(--app-accent, var(--role-accent));
170
+ --glow-card: none;
171
+ --glow-card-hover: none;
172
+
163
173
  /* ============================================================
164
174
  SRGB FALLBACKS (for browsers without oklch support)
165
175
  Tested against Chrome 105-. Values are closest sRGB equivalents.
@@ -233,5 +243,22 @@
233
243
  --glow-strength: 0.6;
234
244
  --shadow-strength: 0.8;
235
245
 
246
+ /*
247
+ * Card glow — subtle neon separation for card surfaces, dark mode only.
248
+ * Derived from the app's accent colour (--app-accent, falling back to
249
+ * the shared --role-accent default) via color-mix() so per-tenant
250
+ * BrandingProvider overrides and per-app brand accents (Conduit green,
251
+ * R80.3 amber, etc.) automatically re-colour the glow. Two layers:
252
+ * a 1px accent ring (edge definition, replaces a hard border) plus a
253
+ * soft negative-spread halo (ambient separation). Kept subtle by
254
+ * design — this is surface separation, not a decorative outline.
255
+ */
256
+ --glow-card:
257
+ 0 0 0 1px color-mix(in oklch, var(--card-glow-source) 30%, transparent),
258
+ 0 0 14px -4px color-mix(in oklch, var(--card-glow-source) 35%, transparent);
259
+ --glow-card-hover:
260
+ 0 0 0 1px color-mix(in oklch, var(--card-glow-source) 42%, transparent),
261
+ 0 0 20px -4px color-mix(in oklch, var(--card-glow-source) 48%, transparent);
262
+
236
263
  /* Shadcn bridge picks up rebindings automatically via var() chain */
237
264
  }