@pihanga2/shadcn 0.2.7 → 0.2.9

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.
@@ -703,3 +703,121 @@ Rules:
703
703
  - `src/cards/BUILDING_CARDS_HOWTO.md` — human-oriented narrative version of this guide
704
704
  - `scripts/gen-card-dependencies.mjs` — dependency scanner
705
705
  - `scripts/gen-registry.mjs` — registry builder
706
+
707
+ ---
708
+
709
+ ## Theme-aware cards — working with CSS variables
710
+
711
+ shadcn/ui components (and therefore pihanga-shadcn cards built on top of them)
712
+ use **CSS custom properties** for all semantic colours, radii, and shadows.
713
+ Understanding the token system is essential for cards that carry their own
714
+ visual chrome (borders, backgrounds, elevation).
715
+
716
+ ### How the token system works
717
+
718
+ `src/theme.css` (shipped as `@pihanga2/shadcn/theme.css`) defines three layers:
719
+
720
+ | Layer | Purpose | Example |
721
+ |---|---|---|
722
+ | `:root { --card: oklch(...) }` | **Palette** — raw colour values | `--card`, `--border`, `--radius` |
723
+ | `@theme inline { --color-card: var(--card) }` | **Tailwind mapping** — wires vars to utility classes | `bg-card`, `border-border`, `rounded-xl` |
724
+ | `.dark { --card: oklch(...) }` | **Dark-mode overrides** | Same tokens, different values |
725
+
726
+ A consumer who overrides `:root { --card: oklch(0.9 0 0); }` in their own CSS
727
+ automatically rethemes every card that uses `bg-card` — no card code changes
728
+ needed.
729
+
730
+ ### Using theme tokens in a new card
731
+
732
+ **Prefer shadcn semantic tokens over hardcoded colours.** This makes the card
733
+ rethemable by the consumer:
734
+
735
+ ```tsx
736
+ // ✅ Rethemable — uses CSS variable tokens
737
+ <div className="bg-card text-card-foreground border border-border rounded-xl shadow-sm">
738
+
739
+ // ❌ Hard-wired — ignores consumer theme
740
+ <div className="bg-white text-gray-900 border border-gray-200 rounded-xl shadow-sm">
741
+ ```
742
+
743
+ Standard shadcn tokens available as Tailwind utilities:
744
+
745
+ | Tailwind class | CSS variable | Default (light) |
746
+ |---|---|---|
747
+ | `bg-card` | `--card` | white |
748
+ | `text-card-foreground` | `--card-foreground` | near-black |
749
+ | `bg-background` | `--background` | white |
750
+ | `text-foreground` | `--foreground` | near-black |
751
+ | `bg-muted` | `--muted` | light gray |
752
+ | `text-muted-foreground` | `--muted-foreground` | medium gray |
753
+ | `bg-primary` | `--primary` | dark |
754
+ | `text-primary-foreground` | `--primary-foreground` | near-white |
755
+ | `border-border` | `--border` | light gray |
756
+ | `rounded-xl` | `--radius-xl` = `--radius + 4px` | ~14px |
757
+ | `rounded-lg` | `--radius-lg` = `--radius` | 10px |
758
+ | `shadow-sm` | Tailwind built-in | subtle shadow |
759
+
760
+ ### Adding a new theme token for a card
761
+
762
+ When a card needs a design token consumers can override (e.g. a brand-specific
763
+ colour or radius), add it to **`src/theme.css`** following the same pattern:
764
+
765
+ ```css
766
+ /* In src/theme.css — @theme inline block */
767
+ @theme inline {
768
+ /* … existing tokens … */
769
+ --color-my-card-accent: var(--my-card-accent);
770
+ }
771
+
772
+ /* default in :root */
773
+ :root {
774
+ --my-card-accent: oklch(0.5 0.2 260); /* a blue */
775
+ }
776
+ ```
777
+
778
+ Then use `bg-my-card-accent` / `text-my-card-accent` in your component.
779
+ Consumers override by setting `--my-card-accent` in their own `:root`.
780
+
781
+ > **Do not** add tokens that are only useful for a single card to the global
782
+ > `theme.css`. For card-local defaults, prefer explicit Tailwind colour classes
783
+ > or a `className` prop.
784
+
785
+ ### The `@container` gotcha with shadcn composites
786
+
787
+ shadcn's `<CardHeader>` applies `@container/card-header`
788
+ (`container-type: inline-size`). CSS containment makes the element's
789
+ intrinsic inline size appear as **0** to its parent for `min-width` / `max-content`
790
+ calculations — your card will collapse to a tiny width if it relies on `min-w-max`.
791
+
792
+ **Fix:** replace `<CardHeader>` with a plain `<div data-slot="card-header">` that
793
+ carries the same Tailwind classes but without the `@container` prefix:
794
+
795
+ ```tsx
796
+ // ❌ @container breaks min-w-max on the parent Card
797
+ <CardHeader className={headerClassName}>
798
+
799
+ // ✅ Same visual styles, no CSS containment
800
+ <div
801
+ data-slot="card-header"
802
+ className={cn(
803
+ "grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6",
804
+ "has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
805
+ headerClassName,
806
+ )}
807
+ >
808
+ ```
809
+
810
+ This is safe because the `has-data-[slot=card-action]` selector is a CSS `:has()`
811
+ rule, not a container query — it doesn't require `@container` to function.
812
+
813
+ ### Consumer setup (reminder)
814
+
815
+ Cards in this library render correctly only when the consuming app's CSS
816
+ includes `@import "@pihanga2/shadcn/theme.css"`. That single import:
817
+
818
+ 1. Defines the CSS custom property palette (`:root`, `.dark`)
819
+ 2. Maps them to Tailwind v4 utility tokens (`@theme inline`)
820
+ 3. Tells Tailwind to scan the library's compiled files (`@source "."`)
821
+
822
+ Apps that already use shadcn/ui with their own theme setup do not need the
823
+ import — their `:root` variables cascade over the defaults.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pihanga2/shadcn",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Pihanga core card components built on shadcn/ui and Radix UI — the npm distribution of pihanga-shadcn.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -179,6 +179,14 @@
179
179
  "./lib/utils": {
180
180
  "import": "./lib/utils.js",
181
181
  "types": "./lib/utils.d.ts"
182
+ },
183
+ "./theme.css": {
184
+ "style": "./theme.css",
185
+ "default": "./theme.css"
186
+ },
187
+ "./theme": {
188
+ "style": "./theme.css",
189
+ "default": "./theme.css"
182
190
  }
183
191
  },
184
192
  "sideEffects": [
package/theme.css ADDED
@@ -0,0 +1,102 @@
1
+ /*
2
+ * pihanga-shadcn — theme.css
3
+ *
4
+ * Add ONE line to your app's main CSS (processed by Tailwind v4):
5
+ *
6
+ * @import "tailwindcss";
7
+ * @import "@pihanga2/shadcn/theme.css";
8
+ *
9
+ * The @source directive below tells Tailwind to scan this package's
10
+ * compiled JS files for utility class names, so you don't need a
11
+ * separate @source line in your own CSS.
12
+ *
13
+ * Skip the import if your app already has a shadcn/ui theme — your
14
+ * :root variables will take precedence over the defaults below.
15
+ */
16
+
17
+ /* Scan this package's compiled files for Tailwind utility classes */
18
+ @source ".";
19
+
20
+ /* Map shadcn CSS vars to Tailwind v4 utility tokens */
21
+ @theme inline {
22
+ --color-background: var(--background);
23
+ --color-foreground: var(--foreground);
24
+ --color-card: var(--card);
25
+ --color-card-foreground: var(--card-foreground);
26
+ --color-popover: var(--popover);
27
+ --color-popover-foreground: var(--popover-foreground);
28
+ --color-primary: var(--primary);
29
+ --color-primary-foreground: var(--primary-foreground);
30
+ --color-secondary: var(--secondary);
31
+ --color-secondary-foreground: var(--secondary-foreground);
32
+ --color-muted: var(--muted);
33
+ --color-muted-foreground: var(--muted-foreground);
34
+ --color-accent: var(--accent);
35
+ --color-accent-foreground: var(--accent-foreground);
36
+ --color-destructive: var(--destructive);
37
+ --color-border: var(--border);
38
+ --color-input: var(--input);
39
+ --color-ring: var(--ring);
40
+ --radius-sm: calc(var(--radius) - 4px);
41
+ --radius-md: calc(var(--radius) - 2px);
42
+ --radius-lg: var(--radius);
43
+ --radius-xl: calc(var(--radius) + 4px);
44
+ --color-btn-brand: var(--primary);
45
+ --color-btn-brand-foreground: var(--primary-foreground);
46
+ --radius-btn-brand: var(--radius-md);
47
+ }
48
+
49
+ /* Light theme (shadcn neutral / new-york) */
50
+ :root {
51
+ --background: oklch(1 0 0);
52
+ --foreground: oklch(0.145 0 0);
53
+ --card: oklch(1 0 0);
54
+ --card-foreground: oklch(0.145 0 0);
55
+ --popover: oklch(1 0 0);
56
+ --popover-foreground: oklch(0.145 0 0);
57
+ --primary: oklch(0.205 0 0);
58
+ --primary-foreground: oklch(0.985 0 0);
59
+ --secondary: oklch(0.97 0 0);
60
+ --secondary-foreground: oklch(0.205 0 0);
61
+ --muted: oklch(0.97 0 0);
62
+ --muted-foreground: oklch(0.556 0 0);
63
+ --accent: oklch(0.97 0 0);
64
+ --accent-foreground: oklch(0.205 0 0);
65
+ --destructive: oklch(0.577 0.245 27.325);
66
+ --border: oklch(0.922 0 0);
67
+ --input: oklch(0.922 0 0);
68
+ --ring: oklch(0.708 0 0);
69
+ --radius: 0.625rem;
70
+ --chart-1: oklch(0.646 0.222 41.116);
71
+ --chart-2: oklch(0.6 0.118 184.704);
72
+ --chart-3: oklch(0.398 0.07 227.392);
73
+ --chart-4: oklch(0.828 0.189 84.429);
74
+ --chart-5: oklch(0.769 0.188 70.08);
75
+ }
76
+
77
+ /* Dark theme */
78
+ .dark {
79
+ --background: oklch(0.145 0 0);
80
+ --foreground: oklch(0.985 0 0);
81
+ --card: oklch(0.205 0 0);
82
+ --card-foreground: oklch(0.985 0 0);
83
+ --popover: oklch(0.205 0 0);
84
+ --popover-foreground: oklch(0.985 0 0);
85
+ --primary: oklch(0.922 0 0);
86
+ --primary-foreground: oklch(0.205 0 0);
87
+ --secondary: oklch(0.269 0 0);
88
+ --secondary-foreground: oklch(0.985 0 0);
89
+ --muted: oklch(0.269 0 0);
90
+ --muted-foreground: oklch(0.708 0 0);
91
+ --accent: oklch(0.269 0 0);
92
+ --accent-foreground: oklch(0.985 0 0);
93
+ --destructive: oklch(0.704 0.191 22.216);
94
+ --border: oklch(1 0 0 / 10%);
95
+ --input: oklch(1 0 0 / 15%);
96
+ --ring: oklch(0.556 0 0);
97
+ --chart-1: oklch(0.488 0.243 264.376);
98
+ --chart-2: oklch(0.696 0.17 162.48);
99
+ --chart-3: oklch(0.769 0.188 70.08);
100
+ --chart-4: oklch(0.627 0.265 303.9);
101
+ --chart-5: oklch(0.645 0.246 16.439);
102
+ }