@olwiba/ui 0.2.18 → 0.2.20

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,88 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cn, useUIVariant } from '@olwiba/cn';
5
+
6
+ /**
7
+ * How a marketing section sits on the page.
8
+ *
9
+ * Every section used to hard-code the same island — an opaque `bg-card` panel
10
+ * with a border and a radius — which is right for a page that is only its
11
+ * sections, and wrong for one with an ambient background behind it: a run of
12
+ * opaque slabs blocks the thing that makes the page feel alive.
13
+ *
14
+ * - `'card'` — the opaque island. Contained, high contrast, reads as a panel.
15
+ * - `'soft'` — translucent and blurred. Still a shape, but the page behind it
16
+ * shows through, which is the treatment that works over an aura or gradient.
17
+ * - `'plain'` — no panel at all. The content sits directly on the page.
18
+ *
19
+ * `'card'` stays the default so nothing changes for a page that never sets it.
20
+ */
21
+ export type MarketingSurface = 'card' | 'soft' | 'plain';
22
+
23
+ const MarketingSurfaceContext = React.createContext<MarketingSurface | undefined>(undefined);
24
+
25
+ /**
26
+ * Sets the default surface for every marketing section beneath it.
27
+ *
28
+ * Page-level rather than per-section because "this page has an ambient
29
+ * background, so its sections should not be opaque" is one decision, and making
30
+ * it once beats repeating it on every section and having a new section silently
31
+ * default back to an island.
32
+ */
33
+ export function MarketingSurfaceProvider({
34
+ surface,
35
+ children,
36
+ }: {
37
+ surface: MarketingSurface | undefined;
38
+ children: React.ReactNode;
39
+ }) {
40
+ return (
41
+ <MarketingSurfaceContext.Provider value={surface}>{children}</MarketingSurfaceContext.Provider>
42
+ );
43
+ }
44
+
45
+ export function useMarketingSurface(): MarketingSurface | undefined {
46
+ return React.useContext(MarketingSurfaceContext);
47
+ }
48
+
49
+ /**
50
+ * The section wrapper's classes for the resolved surface and UI mode.
51
+ *
52
+ * `overflow-hidden` is unconditional on every surface, including `'plain'` —
53
+ * the marquee and carousel sections clip their own content against it, so
54
+ * dropping it with the panel would let them bleed across the page.
55
+ *
56
+ * Resolution order is prop, then `MarketingSurfaceProvider`, then `'card'`.
57
+ * Both context reads happen every render rather than short-circuiting on the
58
+ * prop: `modeProp ?? useHook()` skips a hook call and changes hook order
59
+ * between renders, which is the exact bug @olwiba/cn 0.1.28 and 0.1.30 fixed in
60
+ * `Toaster` and `Input`.
61
+ *
62
+ * Only `smooth`, `playful` and unset are branched on. olwibaCN's source has a
63
+ * fourth `glass` variant, but the published `UIVariant` type does not carry it
64
+ * yet, so a branch for it does not typecheck here.
65
+ */
66
+ export function useSectionSurface(override?: MarketingSurface): string {
67
+ const mode = useUIVariant();
68
+ const contextSurface = useMarketingSurface();
69
+ const surface = override ?? contextSurface ?? 'card';
70
+
71
+ if (surface === 'plain') return 'overflow-hidden';
72
+
73
+ if (surface === 'soft') {
74
+ return cn(
75
+ 'overflow-hidden border backdrop-blur-md',
76
+ mode === 'smooth' && 'rounded-[2rem] border-border/40 bg-card/40',
77
+ mode === 'playful' && 'rounded-3xl border-primary/20 bg-card/50',
78
+ !mode && 'rounded-3xl border-border/50 bg-card/45',
79
+ );
80
+ }
81
+
82
+ return cn(
83
+ 'overflow-hidden bg-card',
84
+ mode === 'smooth' && 'rounded-3xl border',
85
+ mode === 'playful' && 'rounded-2xl border-primary/25 border',
86
+ !mode && 'rounded-2xl border',
87
+ );
88
+ }