@pihanga2/shadcn 0.2.0 → 0.2.2
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/AGENT.building-cards.md +208 -3
- package/AGENT.md +66 -4
- package/AGENT.using-cards.md +197 -250
- package/README.md +4 -1
- package/cards/badge/badge.component.js +1 -0
- package/cards/badge/badge.component.js.map +1 -1
- package/cards/box/box.component.js +2 -2
- package/cards/box/box.component.js.map +1 -1
- package/cards/box/box.types.d.ts +0 -8
- package/cards/box/box.types.js.map +1 -1
- package/cards/button/button.types.d.ts +4 -0
- package/cards/button/button.types.js +2 -2
- package/cards/button/button.types.js.map +1 -1
- package/cards/button/index.js +5 -5
- package/cards/chart/chart.component.d.ts +4 -0
- package/cards/chart/chart.types.d.ts +223 -0
- package/cards/chart/index.d.ts +1 -0
- package/cards/checkbox/checkbox.component.js +1 -0
- package/cards/checkbox/checkbox.component.js.map +1 -1
- package/cards/checkbox/checkbox.types.d.ts +4 -0
- package/cards/checkbox/checkbox.types.js +2 -2
- package/cards/checkbox/checkbox.types.js.map +1 -1
- package/cards/checkbox/index.js +5 -5
- package/cards/conditional/conditional.component.d.ts +14 -3
- package/cards/conditional/conditional.component.js +17 -9
- package/cards/conditional/conditional.component.js.map +1 -1
- package/cards/conditional/conditional.types.d.ts +115 -8
- package/cards/conditional/conditional.types.js.map +1 -1
- package/cards/core-index.d.ts +3 -0
- package/cards/core-index.js +38 -31
- package/cards/dataTable/dataTable.types.js.map +1 -1
- package/cards/dataTable/dataTableRowDetail.component.js +1 -0
- package/cards/dataTable/dataTableRowDetail.component.js.map +1 -1
- package/cards/field/field.types.js.map +1 -1
- package/cards/fileDrop/fileDrop.component.js +90 -0
- package/cards/fileDrop/fileDrop.component.js.map +1 -0
- package/cards/fileDrop/fileDrop.types.js +11 -0
- package/cards/fileDrop/fileDrop.types.js.map +1 -0
- package/cards/fileDrop/index.js +13 -0
- package/cards/fileDrop/index.js.map +1 -0
- package/cards/flexGrid/flexGrid.types.js.map +1 -1
- package/cards/form/form.types.d.ts +4 -0
- package/cards/form/form.types.js +2 -2
- package/cards/form/form.types.js.map +1 -1
- package/cards/form/index.js +5 -5
- package/cards/graphin/contextMenu.component.d.ts +28 -0
- package/cards/graphin/eventDispatcher.component.d.ts +8 -0
- package/cards/graphin/graphin.component.d.ts +2 -2
- package/cards/graphin/graphin.playground-cards.d.ts +1 -0
- package/cards/graphin/graphin.types.d.ts +477 -4
- package/cards/graphin/graphinOpHandler.component.d.ts +8 -0
- package/cards/graphin/index.d.ts +1 -0
- package/cards/graphin/tooltip.component.d.ts +13 -1
- package/cards/input/index.js +5 -5
- package/cards/input/input.component.js +1 -0
- package/cards/input/input.component.js.map +1 -1
- package/cards/input/input.types.d.ts +10 -2
- package/cards/input/input.types.js +2 -2
- package/cards/input/input.types.js.map +1 -1
- package/cards/list/list.types.js +1 -0
- package/cards/list/list.types.js.map +1 -1
- package/cards/pageWithNavbar/pageWithNavbar.component.d.ts +5 -7
- package/cards/pageWithNavbar/pageWithNavbar.component.js +1 -1
- package/cards/pageWithNavbar/pageWithNavbar.component.js.map +1 -1
- package/cards/pasteTarget/pasteTarget.component.js.map +1 -1
- package/cards/select/index.js +5 -5
- package/cards/select/select.component.js +1 -0
- package/cards/select/select.component.js.map +1 -1
- package/cards/select/select.types.d.ts +12 -0
- package/cards/select/select.types.js +2 -2
- package/cards/select/select.types.js.map +1 -1
- package/cards/slider/index.d.ts +1 -0
- package/cards/slider/index.js +13 -0
- package/cards/slider/index.js.map +1 -0
- package/cards/slider/slider.component.d.ts +4 -0
- package/cards/slider/slider.component.js +64 -0
- package/cards/slider/slider.component.js.map +1 -0
- package/cards/slider/slider.types.d.ts +110 -0
- package/cards/slider/slider.types.js +7 -0
- package/cards/slider/slider.types.js.map +1 -0
- package/cards/sliderValue/index.d.ts +1 -0
- package/cards/sliderValue/index.js +12 -0
- package/cards/sliderValue/index.js.map +1 -0
- package/cards/sliderValue/sliderValue.component.d.ts +15 -0
- package/cards/sliderValue/sliderValue.component.js +33 -0
- package/cards/sliderValue/sliderValue.component.js.map +1 -0
- package/cards/sliderValue/sliderValue.types.d.ts +21 -0
- package/cards/sliderValue/sliderValue.types.js +7 -0
- package/cards/sliderValue/sliderValue.types.js.map +1 -0
- package/cards/stack/stack.component.js +1 -1
- package/cards/stack/stack.component.js.map +1 -1
- package/cards/stack/stack.types.js.map +1 -1
- package/cards/switch/index.js +5 -5
- package/cards/switch/switch.component.js +1 -0
- package/cards/switch/switch.component.js.map +1 -1
- package/cards/switch/switch.types.d.ts +4 -0
- package/cards/switch/switch.types.js +2 -2
- package/cards/switch/switch.types.js.map +1 -1
- package/cards/tabs/tabs.component.js +51 -25
- package/cards/tabs/tabs.component.js.map +1 -1
- package/cards/tabs/tabs.types.d.ts +10 -0
- package/cards/tabs/tabs.types.js.map +1 -1
- package/cards/textField/index.js +5 -5
- package/cards/textField/textField.component.js +1 -0
- package/cards/textField/textField.component.js.map +1 -1
- package/cards/textField/textField.types.d.ts +4 -0
- package/cards/textField/textField.types.js +2 -2
- package/cards/textField/textField.types.js.map +1 -1
- package/cards/toast/index.js +7 -7
- package/cards/toast/toast.component.js.map +1 -1
- package/cards/toast/toast.types.d.ts +4 -0
- package/cards/toast/toast.types.js +2 -2
- package/cards/toast/toast.types.js.map +1 -1
- package/cards/toggleGroup/index.js +5 -5
- package/cards/toggleGroup/toggleGroup.component.js +1 -0
- package/cards/toggleGroup/toggleGroup.component.js.map +1 -1
- package/cards/toggleGroup/toggleGroup.types.d.ts +4 -0
- package/cards/toggleGroup/toggleGroup.types.js +2 -2
- package/cards/toggleGroup/toggleGroup.types.js.map +1 -1
- package/cards/typography/typography.types.js +1 -0
- package/cards/typography/typography.types.js.map +1 -1
- package/components/hooks/use-breakpoint.d.ts +49 -0
- package/components/hooks/use-breakpoint.js +73 -0
- package/components/hooks/use-breakpoint.js.map +1 -0
- package/components/ui/slider.js +36 -0
- package/components/ui/slider.js.map +1 -0
- package/components/ui/toggle-group.js +1 -0
- package/components/ui/toggle-group.js.map +1 -1
- package/package.json +19 -2
- package/pihanga-shadcn.css +1 -1
package/AGENT.building-cards.md
CHANGED
|
@@ -1,13 +1,217 @@
|
|
|
1
1
|
# AGENT.building-cards.md — creating new pihanga-shadcn cards
|
|
2
2
|
|
|
3
|
-
> **Scope:**
|
|
3
|
+
> **Scope:** building new cards — either by composing existing cards into a
|
|
4
|
+
> **meta card** (recommended, TypeScript-only) or by adding a brand-new
|
|
5
|
+
> React-based primitive card to `src/cards/`.
|
|
4
6
|
> Read [`AGENT.md`](./AGENT.md) first for orientation and universal rules.
|
|
5
7
|
> If you only need to *use* existing cards in an app, switch to
|
|
6
8
|
> [`AGENT.using-cards.md`](./AGENT.using-cards.md).
|
|
7
9
|
|
|
8
10
|
---
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## ⭐ Meta cards — composing new widgets from existing cards
|
|
13
|
+
|
|
14
|
+
> **This is the recommended approach for building any complex or reusable UI
|
|
15
|
+
> widget.** A meta card is assembled entirely from existing cards using a
|
|
16
|
+
> plain TypeScript mapper function — no JSX, no React imports, no DOM
|
|
17
|
+
> knowledge required. Read this section before considering a primitive card.
|
|
18
|
+
|
|
19
|
+
### Why meta cards?
|
|
20
|
+
|
|
21
|
+
| Concern | Meta card | New primitive card (React component) |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Code volume | ~50 lines TypeScript | ~200 lines JSX + hooks |
|
|
24
|
+
| Maintenance | Inherits upstream card fixes automatically | Must track shadcn/Radix updates manually |
|
|
25
|
+
| Reusability | Portable across any Pihanga app unchanged | Tightly coupled to one project |
|
|
26
|
+
| Testability | Pure function — mock props, assert card tree | Requires DOM + Redux environment |
|
|
27
|
+
| Extendibility | Swap any inner card without touching consumers | Requires internal refactoring |
|
|
28
|
+
| Learning curve | Pihanga config patterns only | JSX, React hooks, Pihanga internals |
|
|
29
|
+
|
|
30
|
+
**Rule of thumb:** if you can describe the widget as *"a layout card containing
|
|
31
|
+
some input and display cards"*, build it as a meta card. Only drop to a
|
|
32
|
+
primitive React component when the widget genuinely needs a DOM API, a
|
|
33
|
+
third-party React hook, or a non-card rendering strategy.
|
|
34
|
+
|
|
35
|
+
### How meta cards work
|
|
36
|
+
|
|
37
|
+
A meta card has three ingredients:
|
|
38
|
+
|
|
39
|
+
1. **Card declaration + actions** — same as any card; use
|
|
40
|
+
`createCardDeclaration`, `registerActions`, `createOnAction`.
|
|
41
|
+
2. **Mapper function** — a plain function `(name, props, registerCard) =>
|
|
42
|
+
PiCardDef` that assembles child cards and returns the root card definition.
|
|
43
|
+
3. **Registration** — `registerMetaCard({type, mapper, events})`.
|
|
44
|
+
|
|
45
|
+
The Pihanga runtime calls the mapper whenever the meta card needs to be
|
|
46
|
+
rendered or re-evaluated. Child cards can use `resolve(props.foo)` inside
|
|
47
|
+
their own prop functions to lazily read a value that may itself be a state
|
|
48
|
+
selector.
|
|
49
|
+
|
|
50
|
+
### Full worked example — `Counter` meta card
|
|
51
|
+
|
|
52
|
+
The complete, annotated source lives at
|
|
53
|
+
[`example/src/counter.card.ts`](./example/src/counter.card.ts).
|
|
54
|
+
Abbreviated for reference:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import {
|
|
58
|
+
createCardDeclaration, createOnAction, registerActions, registerMetaCard,
|
|
59
|
+
} from "@pihanga2/core";
|
|
60
|
+
import type {PiCardDef, PiMapProps, PiRegisterMetaCard, ReduxState, RegisterCardF} from "@pihanga2/core";
|
|
61
|
+
import {Stack, Button, Typography} from "@pihanga2/shadcn";
|
|
62
|
+
|
|
63
|
+
// ── 1. Card identity ─────────────────────────────────────────────────────────
|
|
64
|
+
const COUNTER_CARD = "meta/counter"; // convention: "meta/<name>" for app-level
|
|
65
|
+
|
|
66
|
+
// ── 2. Public factory (consumers call Counter({value: ...})) ────────────────
|
|
67
|
+
export const Counter = createCardDeclaration<CounterProps, CounterEvents>(COUNTER_CARD);
|
|
68
|
+
|
|
69
|
+
// ── 3. Actions + typed event helper ─────────────────────────────────────────
|
|
70
|
+
export const COUNTER_ACTION = registerActions(COUNTER_CARD, ["changed"]);
|
|
71
|
+
export const onCounterChanged = createOnAction<CounterChangeEvent>(COUNTER_ACTION.CHANGED);
|
|
72
|
+
|
|
73
|
+
// ── 4. Types ─────────────────────────────────────────────────────────────────
|
|
74
|
+
type CounterProps = { value: number };
|
|
75
|
+
type CounterChangeEvent = { value: number };
|
|
76
|
+
type CounterEvents = { onChange: CounterChangeEvent };
|
|
77
|
+
type CounterMapperProps = CounterProps & CounterEvents;
|
|
78
|
+
|
|
79
|
+
// ── 5. Mapper — assembles child cards ────────────────────────────────────────
|
|
80
|
+
function CounterMapper(
|
|
81
|
+
_: string,
|
|
82
|
+
props: PiMapProps<CounterMapperProps, ReduxState, object>,
|
|
83
|
+
registerCard: RegisterCardF,
|
|
84
|
+
): PiCardDef {
|
|
85
|
+
// Register a child card under a stable name (optional but shown for reference)
|
|
86
|
+
const plusButton = registerCard("plus", Button({
|
|
87
|
+
label: "+",
|
|
88
|
+
opts: {size: "lg"},
|
|
89
|
+
// Re-map raw click → COUNTER_ACTION.CHANGED { value + 1 }
|
|
90
|
+
onClickedMapper: (_, {resolve}) => ({
|
|
91
|
+
type: COUNTER_ACTION.CHANGED,
|
|
92
|
+
value: resolve(props.value) + 1, // resolve() unwraps lazy state selectors
|
|
93
|
+
}),
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
return Stack({
|
|
97
|
+
direction: "row", alignItems: "center", spacing: 4,
|
|
98
|
+
className: "p-16 justify-center",
|
|
99
|
+
content: [
|
|
100
|
+
Button({
|
|
101
|
+
label: "−",
|
|
102
|
+
opts: {size: "lg"},
|
|
103
|
+
onClickedMapper: (_, {resolve}) => ({
|
|
104
|
+
type: COUNTER_ACTION.CHANGED,
|
|
105
|
+
value: resolve(props.value) - 1,
|
|
106
|
+
}),
|
|
107
|
+
}),
|
|
108
|
+
Typography({
|
|
109
|
+
text: (_, {resolve}) => `Count: ${resolve(props.value)}`, // reactive text
|
|
110
|
+
level: "h2",
|
|
111
|
+
className: "min-w-[120px] text-center",
|
|
112
|
+
}),
|
|
113
|
+
plusButton,
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── 6. Register ───────────────────────────────────────────────────────────────
|
|
119
|
+
registerMetaCard({
|
|
120
|
+
type: COUNTER_CARD,
|
|
121
|
+
mapper: CounterMapper,
|
|
122
|
+
events: COUNTER_ACTION,
|
|
123
|
+
} satisfies PiRegisterMetaCard);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Usage at the app level** (`example/src/app.pihanga.ts`):
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import {Counter} from "./counter.card";
|
|
130
|
+
import type {AppState} from "./app.state";
|
|
131
|
+
|
|
132
|
+
registerCard("page", Counter<AppState>({
|
|
133
|
+
value: (s) => s.count, // state selector — auto-reactive
|
|
134
|
+
}));
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Handling the emitted event** (`example/src/app.reducer.ts`):
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import {onCounterChanged} from "./counter.card";
|
|
141
|
+
|
|
142
|
+
register((r) => {
|
|
143
|
+
onCounterChanged<AppState>(r, (state, {value}) => {
|
|
144
|
+
state.count = value; // raw button clicks never surface here — encapsulated
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Key API reference for meta cards
|
|
150
|
+
|
|
151
|
+
| Symbol | Source | Purpose |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `registerMetaCard(opts)` | `@pihanga2/core` | Registers a mapper as a named card type |
|
|
154
|
+
| `PiRegisterMetaCard` | `@pihanga2/core` | Type for the `opts` object (use `satisfies`) |
|
|
155
|
+
| `MetaCardMapperF` | `@pihanga2/core` | Type of the mapper function |
|
|
156
|
+
| `RegisterCardF` | `@pihanga2/core` | Type of the `registerCard` arg inside the mapper |
|
|
157
|
+
| `PiMapProps<Props, State, Ctx>` | `@pihanga2/core` | Prop type inside the mapper (each prop may be a state selector) |
|
|
158
|
+
| `resolve(prop)` | `StateMapperContext` | Unwraps a lazy prop value inside a child card's prop function |
|
|
159
|
+
| `onClickedMapper` / `on*Mapper` | any card | Event mapper suffix — re-maps a child card's action to a different action type |
|
|
160
|
+
|
|
161
|
+
### Patterns to know
|
|
162
|
+
|
|
163
|
+
**Lazy prop propagation with `resolve`**
|
|
164
|
+
|
|
165
|
+
When a meta card receives `value: (s) => s.count` (a state selector), child
|
|
166
|
+
cards can't use that selector directly — it has to be unwrapped at the time the
|
|
167
|
+
child's own prop function runs:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// ❌ Doesn't work — selector is passed as-is; child sees a function, not a number
|
|
171
|
+
Typography({ text: props.value })
|
|
172
|
+
|
|
173
|
+
// ✅ Correct — resolve() unwraps the selector inside the child's prop function
|
|
174
|
+
Typography({ text: (_, {resolve}) => `Count: ${resolve(props.value)}` })
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Event remapping with `onXxxMapper`**
|
|
178
|
+
|
|
179
|
+
Every card that emits an event supports an `on<EventName>Mapper` prop. The
|
|
180
|
+
mapper intercepts the raw event and returns a different action (or `null` to
|
|
181
|
+
suppress):
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
Button({
|
|
185
|
+
label: "Save",
|
|
186
|
+
// Re-map generic click → domain-specific action
|
|
187
|
+
onClickedMapper: (_ev, _ctx) => ({ type: MY_ACTION.SAVED }),
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Registering a stable child card name**
|
|
192
|
+
|
|
193
|
+
Pass the result of `registerCard(name, def)` as a child in the `content` array.
|
|
194
|
+
This is optional but gives the child a stable registry identity:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const saveBtn = registerCard("save-btn", Button({label: "Save", …}));
|
|
198
|
+
return Stack({ content: [saveBtn] });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Accessing metacard context from a deeply nested child**
|
|
202
|
+
|
|
203
|
+
Use `metaCtxtProps` in a child card's state mapper when you need the context
|
|
204
|
+
props that were passed to the meta card's top-level card:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// Inside a child card declaration within the mapper:
|
|
208
|
+
properties: (s, { metaCtxtProps }) => metaCtxtProps.elementData.properties,
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## ⚠️ Hard constraints before you write a single line (primitive cards)
|
|
214
|
+
|
|
11
215
|
|
|
12
216
|
1. **`src/components/` is read-only.** It is managed exclusively by the shadcn
|
|
13
217
|
CLI (`npx shadcn@latest add <name>`). Never create or edit files there.
|
|
@@ -383,7 +587,8 @@ Rules:
|
|
|
383
587
|
|
|
384
588
|
| Need | Card to study |
|
|
385
589
|
|---|---|
|
|
386
|
-
|
|
|
590
|
+
| **Meta card (composition)** | **`example/src/counter.card.ts`** — fully annotated: Stack + Button × 2 + Typography, event remapping, `resolve()`, stable child names |
|
|
591
|
+
| Minimal primitive card structure | `src/cards/emptyCard.tsx` |
|
|
387
592
|
| Button with variants, icons, tooltip | `src/cards/button/` |
|
|
388
593
|
| Dropdown / context menu | `src/cards/dropDownMenu/` |
|
|
389
594
|
| Form input pattern | `src/cards/textField/`, `src/cards/checkbox/` |
|
package/AGENT.md
CHANGED
|
@@ -30,12 +30,67 @@ https://github.com/ivcap-works/pihanga-shadcn
|
|
|
30
30
|
|
|
31
31
|
| If your task is … | Read this file |
|
|
32
32
|
|---|---|
|
|
33
|
+
| **Getting started** — brand-new project, shadcn/ui + pihanga setup, file layout | [`AGENTS.getting-started.md`](./AGENTS.getting-started.md) |
|
|
33
34
|
| **Using** existing cards — install, wire, compose, navigate | [`AGENT.using-cards.md`](./AGENT.using-cards.md) |
|
|
34
|
-
| **Building
|
|
35
|
+
| **Building complex UI** — assembling a reusable widget from existing cards | [`AGENT.building-cards.md`](./AGENT.building-cards.md) — § *Meta cards* |
|
|
36
|
+
| **Building** a new primitive card from scratch — new React component for this repo | [`AGENT.building-cards.md`](./AGENT.building-cards.md) |
|
|
35
37
|
|
|
36
|
-
If you are **
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
If you are **starting from scratch**, read `AGENTS.getting-started.md` first — it
|
|
39
|
+
covers Vite setup, shadcn/ui init, pihanga-core installation, and the initial file
|
|
40
|
+
layout. Then continue with `AGENT.using-cards.md` for wiring and composing cards.
|
|
41
|
+
|
|
42
|
+
If you are adding cards to an **existing project**, go straight to
|
|
43
|
+
`AGENT.using-cards.md`.
|
|
44
|
+
|
|
45
|
+
### ⭐ Need a complex, reusable UI widget? Use a meta card.
|
|
46
|
+
|
|
47
|
+
Before writing a new React component, ask: *can I build this by composing
|
|
48
|
+
existing cards?* If yes — and that is almost always the answer — use a
|
|
49
|
+
**meta card** instead.
|
|
50
|
+
|
|
51
|
+
A meta card is a TypeScript-only module (no JSX, no React imports) that
|
|
52
|
+
assembles other cards inside a mapper function and registers itself with
|
|
53
|
+
`registerMetaCard`. The result looks and feels exactly like a primitive card to
|
|
54
|
+
consumers: a typed factory function, typed event helpers, and full `memo()`/state
|
|
55
|
+
reactivity.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// example/src/counter.card.ts (abbreviated)
|
|
59
|
+
import {registerMetaCard, createCardDeclaration, registerActions} from "@pihanga2/core";
|
|
60
|
+
import {Stack, Button, Typography} from "@pihanga2/shadcn";
|
|
61
|
+
|
|
62
|
+
const COUNTER_CARD = "meta/counter";
|
|
63
|
+
export const Counter = createCardDeclaration<CounterProps, CounterEvents>(COUNTER_CARD);
|
|
64
|
+
export const COUNTER_ACTION = registerActions(COUNTER_CARD, ["changed"]);
|
|
65
|
+
|
|
66
|
+
registerMetaCard({
|
|
67
|
+
type: COUNTER_CARD,
|
|
68
|
+
mapper(_name, props, registerCard) {
|
|
69
|
+
return Stack({ content: [
|
|
70
|
+
Button({ label: "−", onClickedMapper: (_, {resolve}) => ({type: COUNTER_ACTION.CHANGED, value: resolve(props.value) - 1}) }),
|
|
71
|
+
Typography({ text: (_, {resolve}) => `Count: ${resolve(props.value)}` }),
|
|
72
|
+
Button({ label: "+", onClickedMapper: (_, {resolve}) => ({type: COUNTER_ACTION.CHANGED, value: resolve(props.value) + 1}) }),
|
|
73
|
+
]});
|
|
74
|
+
},
|
|
75
|
+
events: COUNTER_ACTION,
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Why meta cards over new React components?**
|
|
80
|
+
|
|
81
|
+
| Concern | Meta card | New React component |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Code complexity | Low — pure TypeScript config | High — JSX, hooks, event wiring |
|
|
84
|
+
| Maintenance | Inherits upstream fixes automatically | Must be kept in sync manually |
|
|
85
|
+
| Reusability | Drops into any Pihanga app | Tightly coupled to one project |
|
|
86
|
+
| Testability | Plain function, mockable | Requires DOM + Redux setup |
|
|
87
|
+
| Extendibility | Swap out any inner card without touching consumers | Requires internal refactoring |
|
|
88
|
+
|
|
89
|
+
See the full meta card guide in [`AGENT.building-cards.md`](./AGENT.building-cards.md#meta-cards--composing-new-widgets-from-existing-cards)
|
|
90
|
+
and the complete working example at [`example/src/counter.card.ts`](./example/src/counter.card.ts).
|
|
91
|
+
|
|
92
|
+
Switch to `AGENT.building-cards.md` — § *Building a primitive card from scratch* — only
|
|
93
|
+
when no combination of existing cards can achieve the required UI.
|
|
39
94
|
|
|
40
95
|
---
|
|
41
96
|
|
|
@@ -48,3 +103,10 @@ add a new card *type* to `src/cards/` itself.
|
|
|
48
103
|
must ensure only one of them calls it.
|
|
49
104
|
- **`children` is a reserved React prop** — never use it in a card's `…Props`
|
|
50
105
|
type; use `content`, `contentCard`, or `items` instead.
|
|
106
|
+
- **npm channel: add `lucide-react` to `optimizeDeps.include`** — excluding
|
|
107
|
+
`@pihanga2/shadcn` from Vite's pre-bundler (required to prevent duplicate
|
|
108
|
+
registries) also causes `lucide-react` to be served as raw ESM, triggering
|
|
109
|
+
~1 000 individual icon file requests in the dev browser. Adblockers block
|
|
110
|
+
`fingerprint.js` as a tracking script. Always include `"lucide-react"` in
|
|
111
|
+
`optimizeDeps.include` (see `AGENTS.getting-started.md` Step 5). Production
|
|
112
|
+
builds are unaffected — Rollup tree-shakes unused icons normally.
|