@pihanga2/shadcn 0.1.0 → 0.2.1

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.
Files changed (99) hide show
  1. package/AGENT.building-cards.md +413 -0
  2. package/AGENT.md +57 -0
  3. package/AGENT.using-cards.md +1051 -0
  4. package/cards/box/box.component.js +2 -2
  5. package/cards/box/box.component.js.map +1 -1
  6. package/cards/box/box.types.d.ts +1 -4
  7. package/cards/box/box.types.js.map +1 -1
  8. package/cards/button/button.types.d.ts +3 -0
  9. package/cards/button/button.types.js +2 -2
  10. package/cards/button/button.types.js.map +1 -1
  11. package/cards/button/index.js +5 -5
  12. package/cards/chartGraph/chartGraph.component.d.ts +4 -0
  13. package/cards/chartGraph/chartGraph.types.d.ts +102 -0
  14. package/cards/chartGraph/index.d.ts +1 -0
  15. package/cards/conditional/conditional.component.d.ts +14 -3
  16. package/cards/conditional/conditional.component.js +17 -9
  17. package/cards/conditional/conditional.component.js.map +1 -1
  18. package/cards/conditional/conditional.types.d.ts +100 -8
  19. package/cards/conditional/conditional.types.js.map +1 -1
  20. package/cards/core-index.js +31 -31
  21. package/cards/fileDrop/fileDrop.component.d.ts +5 -0
  22. package/cards/fileDrop/fileDrop.types.d.ts +43 -0
  23. package/cards/fileDrop/index.d.ts +2 -0
  24. package/cards/graphin/contextMenu.component.d.ts +28 -0
  25. package/cards/graphin/eventDispatcher.component.d.ts +8 -0
  26. package/cards/graphin/graphin.component.d.ts +2 -2
  27. package/cards/graphin/graphin.playground-cards.d.ts +1 -0
  28. package/cards/graphin/graphin.types.d.ts +444 -4
  29. package/cards/graphin/graphinOpHandler.component.d.ts +8 -0
  30. package/cards/graphin/index.d.ts +1 -0
  31. package/cards/graphin/tooltip.component.d.ts +13 -1
  32. package/cards/icons.js +7 -3
  33. package/cards/icons.js.map +1 -1
  34. package/cards/index.d.ts +1 -0
  35. package/cards/slider/index.d.ts +1 -0
  36. package/cards/slider/slider.component.d.ts +4 -0
  37. package/cards/slider/slider.types.d.ts +102 -0
  38. package/cards/sliderValue/index.d.ts +1 -0
  39. package/cards/sliderValue/sliderValue.component.d.ts +15 -0
  40. package/cards/sliderValue/sliderValue.types.d.ts +21 -0
  41. package/cards/stack/stack.types.d.ts +19 -22
  42. package/cards/stack/stack.types.js +1 -1
  43. package/cards/stack/stack.types.js.map +1 -1
  44. package/cards/tabs/index.js +4 -4
  45. package/cards/tabs/tabs.component.js +51 -25
  46. package/cards/tabs/tabs.component.js.map +1 -1
  47. package/cards/tabs/tabs.types.d.ts +12 -0
  48. package/cards/tabs/tabs.types.js +2 -2
  49. package/cards/tabs/tabs.types.js.map +1 -1
  50. package/cards/types.d.ts +4 -0
  51. package/cards/types.js.map +1 -1
  52. package/components/hooks/use-breakpoint.d.ts +49 -0
  53. package/components/hooks/use-breakpoint.js +73 -0
  54. package/components/hooks/use-breakpoint.js.map +1 -0
  55. package/package.json +9 -1
  56. package/pihanga-shadcn.css +1 -1
  57. package/mountain-snow.svg +0 -4
  58. package/r/badge.json +0 -35
  59. package/r/box.json +0 -26
  60. package/r/button.json +0 -39
  61. package/r/checkbox.json +0 -31
  62. package/r/conditional.json +0 -30
  63. package/r/dataTable.json +0 -43
  64. package/r/dialog.json +0 -36
  65. package/r/dropDownMenu.json +0 -47
  66. package/r/field.json +0 -35
  67. package/r/flexGrid.json +0 -32
  68. package/r/form.json +0 -37
  69. package/r/framework.json +0 -28
  70. package/r/graphin.json +0 -42
  71. package/r/input.json +0 -35
  72. package/r/jsonViewer.json +0 -40
  73. package/r/list.json +0 -39
  74. package/r/loadingOverlay.json +0 -39
  75. package/r/loadingSkeleton.json +0 -30
  76. package/r/markdownViewer.json +0 -51
  77. package/r/menu.json +0 -34
  78. package/r/modeToggle.json +0 -36
  79. package/r/navbarSearch.json +0 -31
  80. package/r/pageWithNavbar.json +0 -46
  81. package/r/pasteTarget.json +0 -41
  82. package/r/pihanga-base.json +0 -11
  83. package/r/pihanga-cards-icons.json +0 -16
  84. package/r/pihanga-cards-types.json +0 -16
  85. package/r/pihanga-hook-use-is-touch-device.json +0 -15
  86. package/r/pihanga-lib-utils.json +0 -18
  87. package/r/pihanga-theme-provider.json +0 -25
  88. package/r/pihanga-ui-extras.json +0 -68
  89. package/r/registry.json +0 -171
  90. package/r/resizable.json +0 -35
  91. package/r/select.json +0 -35
  92. package/r/stack.json +0 -27
  93. package/r/stepper.json +0 -39
  94. package/r/switch.json +0 -36
  95. package/r/tabs.json +0 -33
  96. package/r/textField.json +0 -32
  97. package/r/toast.json +0 -32
  98. package/r/toggleGroup.json +0 -35
  99. package/r/typography.json +0 -31
@@ -0,0 +1,102 @@
1
+ export declare const PI_SLIDER_CARD = "shad/slider";
2
+ export declare const Slider: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<PiSliderProps, S, PiSliderEvents>) => import('@pihanga2/core').PiCardDef;
3
+ export declare const PI_SLIDER_ACTION: {
4
+ CHANGED: string;
5
+ COMMITTED: string;
6
+ };
7
+ export declare const onPiSliderChanged: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
8
+ cardID: string;
9
+ } & PiSliderChangedEvent>) => void;
10
+ /**
11
+ * Fired once when the user **finishes** dragging (mouse-up / touch-end).
12
+ *
13
+ * Use this instead of `onPiSliderChanged` when you only care about the final
14
+ * settled value and do not want to process every intermediate drag position.
15
+ */
16
+ export declare const onPiSliderCommitted: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
17
+ cardID: string;
18
+ } & PiSliderCommittedEvent>) => void;
19
+ export type PiSliderProps = {
20
+ /**
21
+ * Field name used to bind to FormContext when inside a pi/form card.
22
+ * When provided the component reads its value from form data and writes
23
+ * back via form.handleChange.
24
+ */
25
+ name?: string;
26
+ /**
27
+ * Controlled numeric value (single thumb).
28
+ * Ignored when `name` is set and the component is inside a pi/form,
29
+ * and also ignored when `selfManaged` is true (use `defaultValue` instead).
30
+ */
31
+ value?: number;
32
+ /**
33
+ * Initial value used when `selfManaged` is true.
34
+ * Falls back to `value` → `min` → 0 if not provided.
35
+ */
36
+ defaultValue?: number;
37
+ /**
38
+ * When true the slider keeps its own internal value state.
39
+ * Dragging updates the displayed value immediately without needing an
40
+ * external state update. `onChanged` is still fired on every change so
41
+ * the app can react (or log) without being responsible for feeding the
42
+ * value back as a prop.
43
+ *
44
+ * Ignored when inside a pi/form (form state takes precedence).
45
+ */
46
+ selfManaged?: boolean;
47
+ /** Minimum value of the slider range. Defaults to 0. */
48
+ min?: number;
49
+ /** Maximum value of the slider range. Defaults to 100. */
50
+ max?: number;
51
+ /** Stepping interval between selectable values. Defaults to 1. */
52
+ step?: number;
53
+ /** When true, the slider is disabled and non-interactive. */
54
+ disabled?: boolean;
55
+ /**
56
+ * Debounce delay in milliseconds applied to `onChanged` during continuous
57
+ * drag. Only the last value within each quiet window is dispatched.
58
+ *
59
+ * Useful when the `onChanged` handler is expensive (e.g. triggers an API
60
+ * call) — set e.g. `debounceMs: 200` to cap the event rate.
61
+ *
62
+ * Has no effect on `onCommitted`, which always fires exactly once at
63
+ * drag-end regardless of this setting.
64
+ *
65
+ * Omit (or set to 0) to dispatch on every drag position (default behaviour).
66
+ */
67
+ debounceMs?: number;
68
+ /**
69
+ * When true, `onPiSliderChanged` is **never** dispatched during drag.
70
+ *
71
+ * Use this together with `onPiSliderCommitted` when you only care about
72
+ * the final settled value and want to eliminate all intermediate Redux
73
+ * actions entirely (stricter than `debounceMs`).
74
+ *
75
+ * `onPiSliderCommitted` is unaffected and always fires on release.
76
+ * `selfManaged` visual updates are also unaffected.
77
+ */
78
+ suppressChangedEvents?: boolean;
79
+ /**
80
+ * Optional label text rendered above the slider.
81
+ * When provided, the current numeric value is shown to the right of the label.
82
+ */
83
+ label?: string;
84
+ /** Extra Tailwind / CSS classes forwarded to the root wrapper element. */
85
+ className?: string;
86
+ };
87
+ export type PiSliderChangedEvent = {
88
+ /** Field name, mirrors the `name` prop if provided. */
89
+ name?: string;
90
+ /** New numeric value after the change. */
91
+ value: number;
92
+ };
93
+ export type PiSliderCommittedEvent = {
94
+ /** Field name, mirrors the `name` prop if provided. */
95
+ name?: string;
96
+ /** Final settled value when the user released the thumb. */
97
+ value: number;
98
+ };
99
+ export type PiSliderEvents = {
100
+ onChanged: PiSliderChangedEvent;
101
+ onCommitted: PiSliderCommittedEvent;
102
+ };
@@ -0,0 +1 @@
1
+ export * from './sliderValue.types';
@@ -0,0 +1,15 @@
1
+ import { default as React } from 'react';
2
+ import { PiCardProps } from '@pihanga2/core';
3
+ import { PiSliderValueProps } from './sliderValue.types';
4
+ /**
5
+ * Display-only companion to `shad/slider`.
6
+ *
7
+ * Renders the same track + filled-range visual as the interactive slider but
8
+ * with no thumb and no user interaction. Use this card to show a read-only
9
+ * numeric value in contexts where editing is not permitted (e.g. a summary
10
+ * panel, a table cell, or a loading state).
11
+ *
12
+ * The component uses the same Tailwind classes as `src/components/ui/slider.tsx`
13
+ * so the two cards look identical.
14
+ */
15
+ export declare const SliderValueComponent: (props: PiCardProps<PiSliderValueProps>) => React.ReactNode;
@@ -0,0 +1,21 @@
1
+ export declare const PI_SLIDER_VALUE_CARD = "shad/slider-value";
2
+ export declare const SliderValue: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<PiSliderValueProps, S, {}>) => import('@pihanga2/core').PiCardDef;
3
+ export type PiSliderValueProps = {
4
+ /**
5
+ * The numeric value to display on the track.
6
+ * Clamped between `min` and `max`.
7
+ */
8
+ value: number;
9
+ /** Minimum value of the range. Defaults to 0. */
10
+ min?: number;
11
+ /** Maximum value of the range. Defaults to 100. */
12
+ max?: number;
13
+ /**
14
+ * Optional label text rendered above the track.
15
+ * When provided, the numeric value is shown to the right of the label,
16
+ * matching the interactive `shad/slider` layout exactly.
17
+ */
18
+ label?: string;
19
+ /** Extra Tailwind / CSS classes forwarded to the root wrapper element. */
20
+ className?: string;
21
+ };
@@ -1,5 +1,23 @@
1
1
  import { PiCardRef } from '@pihanga2/core';
2
- export declare const STACK_CARD = "shad/stack";
2
+ import { AlignItemsT } from '../types';
3
+ export declare const STACK_CARD = "pi/stack";
4
+ /**
5
+ * Factory function for declaring a `stack` card instance.
6
+ *
7
+ * ```ts
8
+ * import {registerCard} from "@pihanga2/core";
9
+ * import {Stack} from "@/cards/stack";
10
+ *
11
+ * registerCard("myApp/toolbar", Stack({
12
+ * direction: "row",
13
+ * spacing: 2,
14
+ * alignItems: "center",
15
+ * justifyContent: "space-between",
16
+ * content: ["myApp/logo", "myApp/navLinks", "myApp/userMenu"],
17
+ * }));
18
+ * ```
19
+ */
20
+ export declare const Stack: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<StackProps<object>, S, {}>) => import('@pihanga2/core').PiCardDef;
3
21
  /**
4
22
  * CSS `flex-direction` values supported by the Stack card.
5
23
  */
@@ -8,10 +26,6 @@ export type DirectionT = "column-reverse" | "column" | "row-reverse" | "row";
8
26
  * CSS `justify-content` values supported by the Stack card.
9
27
  */
10
28
  export type JustifyContentT = "flex-start" | "center" | "flex-end" | "space-between" | "space-around" | "space-evenly";
11
- /**
12
- * CSS `align-items` values supported by the Stack card.
13
- */
14
- export type AlignItemsT = "normal" | "stretch" | "center" | "flex-start" | "flex-end" | "start" | "end" | "baseline" | "initial" | "inherit";
15
29
  export type StackProps<S = object> = {
16
30
  /** Ordered list of child card refs to render */
17
31
  content: PiCardRef[];
@@ -28,20 +42,3 @@ export type StackProps<S = object> = {
28
42
  /** Card style object (use the `shad` key for Tailwind-compatible overrides) */
29
43
  style?: S;
30
44
  };
31
- /**
32
- * Factory function for declaring a `shad/stack` card instance.
33
- *
34
- * ```ts
35
- * import {registerCard} from "@pihanga2/core";
36
- * import {Stack} from "@/cards/stack";
37
- *
38
- * registerCard("myApp/toolbar", Stack({
39
- * direction: "row",
40
- * spacing: 2,
41
- * alignItems: "center",
42
- * justifyContent: "space-between",
43
- * content: ["myApp/logo", "myApp/navLinks", "myApp/userMenu"],
44
- * }));
45
- * ```
46
- */
47
- export declare const Stack: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<StackProps<object>, S, {}>) => import('@pihanga2/core').PiCardDef;
@@ -1,6 +1,6 @@
1
1
  import { createCardDeclaration as e } from "@pihanga2/core";
2
2
  //#region src/cards/stack/stack.types.ts
3
- var t = "shad/stack", n = e(t);
3
+ var t = "pi/stack", n = e(t);
4
4
  //#endregion
5
5
  export { t as STACK_CARD, n as Stack };
6
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"stack.types.js","names":[],"sources":["../../../src/cards/stack/stack.types.ts"],"sourcesContent":["import {type PiCardRef, createCardDeclaration} from \"@pihanga2/core\";\n\nexport const STACK_CARD = \"shad/stack\";\n\n/**\n * CSS `flex-direction` values supported by the Stack card.\n */\nexport type DirectionT = \"column-reverse\" | \"column\" | \"row-reverse\" | \"row\";\n\n/**\n * CSS `justify-content` values supported by the Stack card.\n */\nexport type JustifyContentT =\n | \"flex-start\"\n | \"center\"\n | \"flex-end\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\n\n/**\n * CSS `align-items` values supported by the Stack card.\n */\nexport type AlignItemsT =\n | \"normal\"\n | \"stretch\"\n | \"center\"\n | \"flex-start\"\n | \"flex-end\"\n | \"start\"\n | \"end\"\n | \"baseline\"\n | \"initial\"\n | \"inherit\";\n\nexport type StackProps<S = object> = {\n /** Ordered list of child card refs to render */\n content: PiCardRef[];\n\n /** Flex direction — defaults to `\"column\"` */\n direction?: DirectionT;\n /** Optional card ref rendered as a separator between children */\n divider?: PiCardRef;\n /** Tailwind gap spacing units between children */\n spacing?: number;\n\n justifyContent?: JustifyContentT;\n alignItems?: AlignItemsT;\n\n /** Additional Tailwind utility classes */\n className?: string;\n /** Card style object (use the `shad` key for Tailwind-compatible overrides) */\n style?: S;\n};\n\n/**\n * Factory function for declaring a `shad/stack` card instance.\n *\n * ```ts\n * import {registerCard} from \"@pihanga2/core\";\n * import {Stack} from \"@/cards/stack\";\n *\n * registerCard(\"myApp/toolbar\", Stack({\n * direction: \"row\",\n * spacing: 2,\n * alignItems: \"center\",\n * justifyContent: \"space-between\",\n * content: [\"myApp/logo\", \"myApp/navLinks\", \"myApp/userMenu\"],\n * }));\n * ```\n */\nexport const Stack = createCardDeclaration<StackProps>(STACK_CARD);\n"],"mappings":";;AAEA,IAAa,IAAa,cAqEb,IAAQ,EAAkC,CAAU"}
1
+ {"version":3,"file":"stack.types.js","names":[],"sources":["../../../src/cards/stack/stack.types.ts"],"sourcesContent":["import {type PiCardRef, createCardDeclaration} from \"@pihanga2/core\";\nimport {AlignItemsT} from \"../types\";\n\nexport const STACK_CARD = \"pi/stack\";\n\n/**\n * Factory function for declaring a `stack` card instance.\n *\n * ```ts\n * import {registerCard} from \"@pihanga2/core\";\n * import {Stack} from \"@/cards/stack\";\n *\n * registerCard(\"myApp/toolbar\", Stack({\n * direction: \"row\",\n * spacing: 2,\n * alignItems: \"center\",\n * justifyContent: \"space-between\",\n * content: [\"myApp/logo\", \"myApp/navLinks\", \"myApp/userMenu\"],\n * }));\n * ```\n */\nexport const Stack = createCardDeclaration<StackProps>(STACK_CARD);\n\n/**\n * CSS `flex-direction` values supported by the Stack card.\n */\nexport type DirectionT = \"column-reverse\" | \"column\" | \"row-reverse\" | \"row\";\n\n/**\n * CSS `justify-content` values supported by the Stack card.\n */\nexport type JustifyContentT =\n | \"flex-start\"\n | \"center\"\n | \"flex-end\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\n\nexport type StackProps<S = object> = {\n /** Ordered list of child card refs to render */\n content: PiCardRef[];\n\n /** Flex direction — defaults to `\"column\"` */\n direction?: DirectionT;\n /** Optional card ref rendered as a separator between children */\n divider?: PiCardRef;\n /** Tailwind gap spacing units between children */\n spacing?: number;\n\n justifyContent?: JustifyContentT;\n alignItems?: AlignItemsT;\n\n /** Additional Tailwind utility classes */\n className?: string;\n /** Card style object (use the `shad` key for Tailwind-compatible overrides) */\n style?: S;\n};\n"],"mappings":";;AAGA,IAAa,IAAa,YAkBb,IAAQ,EAAkC,CAAU"}
@@ -1,13 +1,13 @@
1
1
  import { TabsComponent as e } from "./tabs.component.js";
2
- import { SdTabs as t, TABS_ACTION as n, TABS_CARD as r, onTabsTabChanged as i } from "./tabs.types.js";
3
- import { registerCardComponent as a } from "@pihanga2/core";
2
+ import { SdTabs as t, TABS_ACTION as n, TABS_CARD as r, Tabs as i, onTabsTabChanged as a } from "./tabs.types.js";
3
+ import { registerCardComponent as o } from "@pihanga2/core";
4
4
  //#region src/cards/tabs/index.ts
5
- a({
5
+ o({
6
6
  name: r,
7
7
  component: e,
8
8
  events: { onTabChanged: n.TABCHANGED }
9
9
  });
10
10
  //#endregion
11
- export { t as SdTabs, n as TABS_ACTION, r as TABS_CARD, i as onTabsTabChanged };
11
+ export { t as SdTabs, n as TABS_ACTION, r as TABS_CARD, i as Tabs, a as onTabsTabChanged };
12
12
 
13
13
  //# sourceMappingURL=index.js.map
@@ -1,42 +1,68 @@
1
- import { Tabs as e, TabsContent as t, TabsList as n, TabsTrigger as r } from "../../components/ui/tabs.js";
2
- import { Card as i } from "@pihanga2/core";
3
- import { useState as a } from "react";
4
- import { jsx as o, jsxs as s } from "react/jsx-runtime";
1
+ import { Select as e, SelectContent as t, SelectItem as n, SelectTrigger as r, SelectValue as i } from "../../components/ui/select.js";
2
+ import { Tabs as a, TabsContent as o, TabsList as s, TabsTrigger as c } from "../../components/ui/tabs.js";
3
+ import { Card as l } from "@pihanga2/core";
4
+ import { useState as u } from "react";
5
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
5
6
  //#region src/cards/tabs/tabs.component.tsx
6
- var c = (c) => {
7
- let { tabs: l, value: u, defaultValue: d, selfManaged: f = !1, orientation: p = "horizontal", className: m, listClassName: h, contentClassName: g, cardName: _, onTabChanged: v } = c, [y, b] = a(u ?? d ?? l[0]?.id ?? ""), x = f ? y : u ?? d ?? l[0]?.id ?? "";
8
- function S(e) {
9
- f && b(e), typeof v == "function" && v({ tabId: e });
7
+ var p = (p) => {
8
+ let { tabs: m, value: h, defaultValue: g, selfManaged: _ = !1, orientation: v = "horizontal", className: y, listClassName: b, contentClassName: x, maxTabs: S, cardName: C, onTabChanged: w } = p, [T, E] = u(h ?? g ?? m[0]?.id ?? ""), D = _ ? T : h ?? g ?? m[0]?.id ?? "";
9
+ function O(e) {
10
+ _ && E(e), typeof w == "function" && w({ tabId: e });
10
11
  }
11
- return /* @__PURE__ */ o("div", {
12
- "data-pihanga": _,
13
- children: /* @__PURE__ */ s(e, {
14
- value: x,
15
- onValueChange: S,
16
- orientation: p,
17
- className: m,
18
- children: [/* @__PURE__ */ o(n, {
19
- className: h,
20
- children: l.map((e) => /* @__PURE__ */ o(r, {
12
+ if (S !== void 0 && m.length > S) {
13
+ let a = m.find((e) => e.id === D);
14
+ return /* @__PURE__ */ f("div", {
15
+ "data-pihanga": C,
16
+ className: y,
17
+ children: [/* @__PURE__ */ f(e, {
18
+ value: D,
19
+ onValueChange: O,
20
+ children: [/* @__PURE__ */ d(r, {
21
+ className: b,
22
+ children: /* @__PURE__ */ d(i, { placeholder: "Select a tab…" })
23
+ }), /* @__PURE__ */ d(t, { children: m.map((e) => /* @__PURE__ */ d(n, {
21
24
  value: e.id,
22
25
  disabled: e.disabled,
23
- children: typeof e.title == "string" ? e.title : /* @__PURE__ */ o(i, {
26
+ children: typeof e.title == "string" ? e.title : e.id
27
+ }, e.id)) })]
28
+ }), a && /* @__PURE__ */ d("div", {
29
+ className: x,
30
+ children: /* @__PURE__ */ d(l, {
31
+ cardName: a.contentCard,
32
+ parentCard: C
33
+ })
34
+ })]
35
+ });
36
+ }
37
+ return /* @__PURE__ */ d("div", {
38
+ "data-pihanga": C,
39
+ children: /* @__PURE__ */ f(a, {
40
+ value: D,
41
+ onValueChange: O,
42
+ orientation: v,
43
+ className: y,
44
+ children: [/* @__PURE__ */ d(s, {
45
+ className: b,
46
+ children: m.map((e) => /* @__PURE__ */ d(c, {
47
+ value: e.id,
48
+ disabled: e.disabled,
49
+ children: typeof e.title == "string" ? e.title : /* @__PURE__ */ d(l, {
24
50
  cardName: e.title,
25
- parentCard: _
51
+ parentCard: C
26
52
  })
27
53
  }, e.id))
28
- }), l.map((e) => /* @__PURE__ */ o(t, {
54
+ }), m.map((e) => /* @__PURE__ */ d(o, {
29
55
  value: e.id,
30
- className: g,
31
- children: /* @__PURE__ */ o(i, {
56
+ className: x,
57
+ children: /* @__PURE__ */ d(l, {
32
58
  cardName: e.contentCard,
33
- parentCard: _
59
+ parentCard: C
34
60
  })
35
61
  }, e.id))]
36
62
  })
37
63
  });
38
64
  };
39
65
  //#endregion
40
- export { c as TabsComponent };
66
+ export { p as TabsComponent };
41
67
 
42
68
  //# sourceMappingURL=tabs.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.component.js","names":[],"sources":["../../../src/cards/tabs/tabs.component.tsx"],"sourcesContent":["import React, {useState} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {Tabs, TabsContent, TabsList, TabsTrigger} from \"@/components/ui/tabs\";\nimport type {TabsEvents, TabsProps} from \"./tabs.types\";\n\nexport const TabsComponent = (\n props: PiCardProps<TabsProps, TabsEvents>,\n): React.ReactNode => {\n const {\n tabs,\n value: propValue,\n defaultValue,\n selfManaged = false,\n orientation = \"horizontal\",\n className,\n listClassName,\n contentClassName,\n cardName,\n onTabChanged,\n } = props;\n\n // Resolve the initial value for self-managed mode:\n // prefer propValue, then defaultValue, then first tab's id.\n const initialValue = propValue ?? defaultValue ?? tabs[0]?.id ?? \"\";\n\n // Internal state used only when selfManaged=true.\n const [managedValue, setManagedValue] = useState<string>(initialValue);\n\n // Effective active tab:\n // selfManaged → use internal state\n // controlled → use the prop (fall back to first tab if not set)\n const activeValue = selfManaged\n ? managedValue\n : (propValue ?? defaultValue ?? tabs[0]?.id ?? \"\");\n\n function handleValueChange(tabId: string) {\n if (selfManaged) {\n setManagedValue(tabId);\n }\n // Always dispatch the Pihanga event so reducers can observe the change\n // even in self-managed mode.\n if (typeof onTabChanged === \"function\") {\n onTabChanged({tabId});\n }\n }\n\n return (\n <div data-pihanga={cardName}>\n <Tabs\n value={activeValue}\n onValueChange={handleValueChange}\n orientation={orientation}\n className={className}\n >\n <TabsList className={listClassName}>\n {tabs.map((tab) => (\n <TabsTrigger key={tab.id} value={tab.id} disabled={tab.disabled}>\n {typeof tab.title !== \"string\" ? (\n <Card cardName={tab.title} parentCard={cardName} />\n ) : (\n tab.title\n )}\n </TabsTrigger>\n ))}\n </TabsList>\n\n {tabs.map((tab) => (\n <TabsContent key={tab.id} value={tab.id} className={contentClassName}>\n <Card cardName={tab.contentCard} parentCard={cardName} />\n </TabsContent>\n ))}\n </Tabs>\n </div>\n );\n};\n"],"mappings":";;;;;AAKA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,SACA,OAAO,GACP,iBACA,iBAAc,IACd,iBAAc,cACd,cACA,kBACA,qBACA,aACA,oBACE,GAOE,CAAC,GAAc,KAAmB,EAHnB,KAAa,KAAgB,EAAK,IAAI,MAAM,EAGI,GAK/D,IAAc,IAChB,IACC,KAAa,KAAgB,EAAK,IAAI,MAAM;CAEjD,SAAS,EAAkB,GAAe;EAMxC,AALI,KACF,EAAgB,CAAK,GAInB,OAAO,KAAiB,cAC1B,EAAa,EAAC,SAAK,CAAC;CAExB;CAEA,OACE,kBAAC,OAAD;EAAK,gBAAc;YACjB,kBAAC,GAAD;GACE,OAAO;GACP,eAAe;GACF;GACF;aAJb,CAME,kBAAC,GAAD;IAAU,WAAW;cAClB,EAAK,KAAK,MACT,kBAAC,GAAD;KAA0B,OAAO,EAAI;KAAI,UAAU,EAAI;eACpD,OAAO,EAAI,SAAU,WAGpB,EAAI,QAFJ,kBAAC,GAAD;MAAM,UAAU,EAAI;MAAO,YAAY;KAAW,CAAA;IAIzC,GANK,EAAI,EAMT,CACd;GACO,CAAA,GAET,EAAK,KAAK,MACT,kBAAC,GAAD;IAA0B,OAAO,EAAI;IAAI,WAAW;cAClD,kBAAC,GAAD;KAAM,UAAU,EAAI;KAAa,YAAY;IAAW,CAAA;GAC7C,GAFK,EAAI,EAET,CACd,CACG;;CACH,CAAA;AAET"}
1
+ {"version":3,"file":"tabs.component.js","names":[],"sources":["../../../src/cards/tabs/tabs.component.tsx"],"sourcesContent":["import React, {useState} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {Tabs, TabsContent, TabsList, TabsTrigger} from \"@/components/ui/tabs\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport type {TabsEvents, TabsProps} from \"./tabs.types\";\n\nexport const TabsComponent = (\n props: PiCardProps<TabsProps, TabsEvents>,\n): React.ReactNode => {\n const {\n tabs,\n value: propValue,\n defaultValue,\n selfManaged = false,\n orientation = \"horizontal\",\n className,\n listClassName,\n contentClassName,\n maxTabs,\n cardName,\n onTabChanged,\n } = props;\n\n // Resolve the initial value for self-managed mode:\n // prefer propValue, then defaultValue, then first tab's id.\n const initialValue = propValue ?? defaultValue ?? tabs[0]?.id ?? \"\";\n\n // Internal state used only when selfManaged=true.\n const [managedValue, setManagedValue] = useState<string>(initialValue);\n\n // Effective active tab:\n // selfManaged → use internal state\n // controlled → use the prop (fall back to first tab if not set)\n const activeValue = selfManaged\n ? managedValue\n : (propValue ?? defaultValue ?? tabs[0]?.id ?? \"\");\n\n function handleValueChange(tabId: string) {\n if (selfManaged) {\n setManagedValue(tabId);\n }\n // Always dispatch the Pihanga event so reducers can observe the change\n // even in self-managed mode.\n if (typeof onTabChanged === \"function\") {\n onTabChanged({tabId});\n }\n }\n\n // When maxTabs is set and the number of tabs exceeds it, replace the tab\n // strip with a Select drop-down for navigation.\n const useDropdown = maxTabs !== undefined && tabs.length > maxTabs;\n\n if (useDropdown) {\n const activeTab = tabs.find((t) => t.id === activeValue);\n\n return (\n <div data-pihanga={cardName} className={className}>\n {/* Drop-down tab selector */}\n <Select value={activeValue} onValueChange={handleValueChange}>\n <SelectTrigger className={listClassName}>\n <SelectValue placeholder=\"Select a tab…\" />\n </SelectTrigger>\n <SelectContent>\n {tabs.map((tab) => (\n <SelectItem key={tab.id} value={tab.id} disabled={tab.disabled}>\n {typeof tab.title === \"string\" ? tab.title : tab.id}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Content area — render the active tab's card directly */}\n {activeTab && (\n <div className={contentClassName}>\n <Card cardName={activeTab.contentCard} parentCard={cardName} />\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div data-pihanga={cardName}>\n <Tabs\n value={activeValue}\n onValueChange={handleValueChange}\n orientation={orientation}\n className={className}\n >\n <TabsList className={listClassName}>\n {tabs.map((tab) => (\n <TabsTrigger key={tab.id} value={tab.id} disabled={tab.disabled}>\n {typeof tab.title !== \"string\" ? (\n <Card cardName={tab.title} parentCard={cardName} />\n ) : (\n tab.title\n )}\n </TabsTrigger>\n ))}\n </TabsList>\n\n {tabs.map((tab) => (\n <TabsContent key={tab.id} value={tab.id} className={contentClassName}>\n <Card cardName={tab.contentCard} parentCard={cardName} />\n </TabsContent>\n ))}\n </Tabs>\n </div>\n );\n};\n"],"mappings":";;;;;;AAYA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,SACA,OAAO,GACP,iBACA,iBAAc,IACd,iBAAc,cACd,cACA,kBACA,qBACA,YACA,aACA,oBACE,GAOE,CAAC,GAAc,KAAmB,EAHnB,KAAa,KAAgB,EAAK,IAAI,MAAM,EAGI,GAK/D,IAAc,IAChB,IACC,KAAa,KAAgB,EAAK,IAAI,MAAM;CAEjD,SAAS,EAAkB,GAAe;EAMxC,AALI,KACF,EAAgB,CAAK,GAInB,OAAO,KAAiB,cAC1B,EAAa,EAAC,SAAK,CAAC;CAExB;CAMA,IAFoB,MAAY,KAAA,KAAa,EAAK,SAAS,GAE1C;EACf,IAAM,IAAY,EAAK,MAAM,MAAM,EAAE,OAAO,CAAW;EAEvD,OACE,kBAAC,OAAD;GAAK,gBAAc;GAAqB;aAAxC,CAEE,kBAAC,GAAD;IAAQ,OAAO;IAAa,eAAe;cAA3C,CACE,kBAAC,GAAD;KAAe,WAAW;eACxB,kBAAC,GAAD,EAAa,aAAY,gBAAiB,CAAA;IAC7B,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAK,KAAK,MACT,kBAAC,GAAD;KAAyB,OAAO,EAAI;KAAI,UAAU,EAAI;eACnD,OAAO,EAAI,SAAU,WAAW,EAAI,QAAQ,EAAI;IACvC,GAFK,EAAI,EAET,CACb,EACY,CAAA,CACT;OAGP,KACC,kBAAC,OAAD;IAAK,WAAW;cACd,kBAAC,GAAD;KAAM,UAAU,EAAU;KAAa,YAAY;IAAW,CAAA;GAC3D,CAAA,CAEJ;;CAET;CAEA,OACE,kBAAC,OAAD;EAAK,gBAAc;YACjB,kBAAC,GAAD;GACE,OAAO;GACP,eAAe;GACF;GACF;aAJb,CAME,kBAAC,GAAD;IAAU,WAAW;cAClB,EAAK,KAAK,MACT,kBAAC,GAAD;KAA0B,OAAO,EAAI;KAAI,UAAU,EAAI;eACpD,OAAO,EAAI,SAAU,WAGpB,EAAI,QAFJ,kBAAC,GAAD;MAAM,UAAU,EAAI;MAAO,YAAY;KAAW,CAAA;IAIzC,GANK,EAAI,EAMT,CACd;GACO,CAAA,GAET,EAAK,KAAK,MACT,kBAAC,GAAD;IAA0B,OAAO,EAAI;IAAI,WAAW;cAClD,kBAAC,GAAD;KAAM,UAAU,EAAI;KAAa,YAAY;IAAW,CAAA;GAC7C,GAFK,EAAI,EAET,CACd,CACG;;CACH,CAAA;AAET"}
@@ -1,5 +1,7 @@
1
1
  import { PiCardRef } from '@pihanga2/core';
2
2
  export declare const TABS_CARD = "shad/tabs";
3
+ export declare const Tabs: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<TabsProps, S, TabsEvents>) => import('@pihanga2/core').PiCardDef;
4
+ /** @deprecated Use {@link Tabs} instead. */
3
5
  export declare const SdTabs: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<TabsProps, S, TabsEvents>) => import('@pihanga2/core').PiCardDef;
4
6
  export declare const TABS_ACTION: {
5
7
  TABCHANGED: string;
@@ -61,6 +63,16 @@ export type TabsProps = {
61
63
  listClassName?: string;
62
64
  /** Additional classes applied to each TabsContent element. */
63
65
  contentClassName?: string;
66
+ /**
67
+ * Maximum number of tabs to display as a standard tab strip.
68
+ * When the number of tabs exceeds this value the tab selector is replaced
69
+ * by a drop-down `<Select>` menu while the tab content area continues to
70
+ * work as normal.
71
+ *
72
+ * When omitted (or `undefined`) the tab strip is always used regardless of
73
+ * how many tabs there are.
74
+ */
75
+ maxTabs?: number;
64
76
  };
65
77
  export type TabsTabChangedEvent = {
66
78
  /** The id of the newly selected tab. */
@@ -1,7 +1,7 @@
1
1
  import { createCardDeclaration as e, createOnAction as t, registerActions as n } from "@pihanga2/core";
2
2
  //#region src/cards/tabs/tabs.types.ts
3
- var r = "shad/tabs", i = e(r), a = n(r, ["tabChanged"]), o = t(a.TABCHANGED);
3
+ var r = "shad/tabs", i = e(r), a = i, o = n(r, ["tabChanged"]), s = t(o.TABCHANGED);
4
4
  //#endregion
5
- export { i as SdTabs, a as TABS_ACTION, r as TABS_CARD, o as onTabsTabChanged };
5
+ export { a as SdTabs, o as TABS_ACTION, r as TABS_CARD, i as Tabs, s as onTabsTabChanged };
6
6
 
7
7
  //# sourceMappingURL=tabs.types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.types.js","names":[],"sources":["../../../src/cards/tabs/tabs.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n type PiCardRef,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const TABS_CARD = \"shad/tabs\";\n\nexport const SdTabs = createCardDeclaration<TabsProps, TabsEvents>(TABS_CARD);\n\nexport const TABS_ACTION = registerActions(TABS_CARD, [\"tabChanged\"]);\n\nexport const onTabsTabChanged = createOnAction<TabsTabChangedEvent>(\n TABS_ACTION.TABCHANGED,\n);\n\n// ─── Tab item definition ──────────────────────────────────────────────────────\n\n/**\n * A single tab entry in the tabs component.\n */\nexport type TabItem = {\n /** Unique identifier used for controlled value and events. */\n id: string;\n\n /**\n * The label displayed on the tab trigger.\n * Can be a plain string or a PiCardRef for custom/rich tab labels.\n */\n title: string | PiCardRef;\n\n /**\n * The Pihanga card rendered as the body of this tab when it is active.\n */\n contentCard: PiCardRef;\n\n /** When true the tab trigger is rendered as disabled. */\n disabled?: boolean;\n};\n\n// ─── Card props ───────────────────────────────────────────────────────────────\n\nexport type TabsProps = {\n /** Ordered list of tab definitions. */\n tabs: TabItem[];\n\n /**\n * Controlled: the currently active tab id.\n * Must be provided (or supplied via a state mapper) when selfManaged is\n * false (the default).\n */\n value?: string;\n\n /**\n * Uncontrolled initial active tab id.\n * Used as the initial value in self-managed mode when value is not set;\n * falls back to the first tab id if omitted.\n */\n defaultValue?: string;\n\n /**\n * When true, the component manages its own active tab state internally.\n * The onTabChanged event is still dispatched on every change so external\n * reducers can observe the selection.\n *\n * When false (default), value fully controls the displayed tab.\n * @default false\n */\n selfManaged?: boolean;\n\n /**\n * Layout orientation of the tab list.\n * Passed directly to the Radix Tabs primitive.\n * @default \"horizontal\"\n */\n orientation?: \"horizontal\" | \"vertical\";\n\n /** Additional Tailwind/CSS classes applied to the root Tabs element. */\n className?: string;\n\n /** Additional classes applied to the TabsList element. */\n listClassName?: string;\n\n /** Additional classes applied to each TabsContent element. */\n contentClassName?: string;\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nexport type TabsTabChangedEvent = {\n /** The id of the newly selected tab. */\n tabId: string;\n};\n\nexport type TabsEvents = {\n /** Fired whenever the user selects a different tab. */\n onTabChanged: TabsTabChangedEvent;\n};\n"],"mappings":";;AAOA,IAAa,IAAY,aAEZ,IAAS,EAA6C,CAAS,GAE/D,IAAc,EAAgB,GAAW,CAAC,YAAY,CAAC,GAEvD,IAAmB,EAC9B,EAAY,UACd"}
1
+ {"version":3,"file":"tabs.types.js","names":[],"sources":["../../../src/cards/tabs/tabs.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n type PiCardRef,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const TABS_CARD = \"shad/tabs\";\n\nexport const Tabs = createCardDeclaration<TabsProps, TabsEvents>(TABS_CARD);\n\n/** @deprecated Use {@link Tabs} instead. */\nexport const SdTabs = Tabs;\n\nexport const TABS_ACTION = registerActions(TABS_CARD, [\"tabChanged\"]);\n\nexport const onTabsTabChanged = createOnAction<TabsTabChangedEvent>(\n TABS_ACTION.TABCHANGED,\n);\n\n// ─── Tab item definition ──────────────────────────────────────────────────────\n\n/**\n * A single tab entry in the tabs component.\n */\nexport type TabItem = {\n /** Unique identifier used for controlled value and events. */\n id: string;\n\n /**\n * The label displayed on the tab trigger.\n * Can be a plain string or a PiCardRef for custom/rich tab labels.\n */\n title: string | PiCardRef;\n\n /**\n * The Pihanga card rendered as the body of this tab when it is active.\n */\n contentCard: PiCardRef;\n\n /** When true the tab trigger is rendered as disabled. */\n disabled?: boolean;\n};\n\n// ─── Card props ───────────────────────────────────────────────────────────────\n\nexport type TabsProps = {\n /** Ordered list of tab definitions. */\n tabs: TabItem[];\n\n /**\n * Controlled: the currently active tab id.\n * Must be provided (or supplied via a state mapper) when selfManaged is\n * false (the default).\n */\n value?: string;\n\n /**\n * Uncontrolled initial active tab id.\n * Used as the initial value in self-managed mode when value is not set;\n * falls back to the first tab id if omitted.\n */\n defaultValue?: string;\n\n /**\n * When true, the component manages its own active tab state internally.\n * The onTabChanged event is still dispatched on every change so external\n * reducers can observe the selection.\n *\n * When false (default), value fully controls the displayed tab.\n * @default false\n */\n selfManaged?: boolean;\n\n /**\n * Layout orientation of the tab list.\n * Passed directly to the Radix Tabs primitive.\n * @default \"horizontal\"\n */\n orientation?: \"horizontal\" | \"vertical\";\n\n /** Additional Tailwind/CSS classes applied to the root Tabs element. */\n className?: string;\n\n /** Additional classes applied to the TabsList element. */\n listClassName?: string;\n\n /** Additional classes applied to each TabsContent element. */\n contentClassName?: string;\n\n /**\n * Maximum number of tabs to display as a standard tab strip.\n * When the number of tabs exceeds this value the tab selector is replaced\n * by a drop-down `<Select>` menu while the tab content area continues to\n * work as normal.\n *\n * When omitted (or `undefined`) the tab strip is always used regardless of\n * how many tabs there are.\n */\n maxTabs?: number;\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nexport type TabsTabChangedEvent = {\n /** The id of the newly selected tab. */\n tabId: string;\n};\n\nexport type TabsEvents = {\n /** Fired whenever the user selects a different tab. */\n onTabChanged: TabsTabChangedEvent;\n};\n"],"mappings":";;AAOA,IAAa,IAAY,aAEZ,IAAO,EAA6C,CAAS,GAG7D,IAAS,GAET,IAAc,EAAgB,GAAW,CAAC,YAAY,CAAC,GAEvD,IAAmB,EAC9B,EAAY,UACd"}
package/cards/types.d.ts CHANGED
@@ -70,3 +70,7 @@ export type ColorT = string;
70
70
  * registry) or an arbitrary Tailwind-class string for inline SVG icons.
71
71
  */
72
72
  export type DecoratorT = string;
73
+ /**
74
+ * CSS `align-items` values supported by the Box & Stack card.
75
+ */
76
+ export type AlignItemsT = "normal" | "stretch" | "center" | "flex-start" | "flex-end" | "start" | "end" | "baseline" | "initial" | "inherit";
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../src/cards/types.ts"],"sourcesContent":["/**\n * Screen size breakpoints matching Tailwind CSS defaults.\n *\n * Used with {@link SizeMap} to render different cards (or none at all)\n * depending on the viewport width.\n */\nexport enum ScreenSize {\n XS = \"xs\", // default / < 640px\n SM = \"sm\", // ≥ 640px\n MD = \"md\", // ≥ 768px\n LG = \"lg\", // ≥ 1024px\n XL = \"xl\", // ≥ 1280px\n XXL = \"2xl\", // ≥ 1536px\n}\n\n/**\n * Ascending order of breakpoints — used for cascade resolution.\n * @internal\n */\nexport const SCREEN_SIZE_ORDER: ScreenSize[] = [\n ScreenSize.XS,\n ScreenSize.SM,\n ScreenSize.MD,\n ScreenSize.LG,\n ScreenSize.XL,\n ScreenSize.XXL,\n];\n\n/**\n * Minimum viewport widths (px) for each breakpoint.\n * @internal\n */\nexport const SCREEN_SIZE_WIDTHS: Record<ScreenSize, number> = {\n [ScreenSize.XS]: 0,\n [ScreenSize.SM]: 640,\n [ScreenSize.MD]: 768,\n [ScreenSize.LG]: 1024,\n [ScreenSize.XL]: 1280,\n [ScreenSize.XXL]: 1536,\n};\n\n/**\n * Map a value {@link T} to specific screen-size breakpoints.\n *\n * Resolution follows a **mobile-first cascade**: the entry with the\n * largest breakpoint that is ≤ the current viewport size wins.\n *\n * ```ts\n * // Show \"desktopCard\" on md and above, nothing on xs/sm:\n * { [ScreenSize.MD]: \"desktopCard\" }\n *\n * // Show a different card per breakpoint:\n * { [ScreenSize.XS]: \"mobileCard\", [ScreenSize.MD]: \"desktopCard\" }\n *\n * // Explicitly hide on md and above:\n * { [ScreenSize.XS]: \"card\", [ScreenSize.MD]: null }\n * ```\n *\n * Set `default` as a final fallback when no breakpoint entry matches.\n */\nexport type SizeMap<T> = Partial<Record<ScreenSize, T | null>> & {\n default?: T | null;\n};\n\n// ---------------------------------------------------------------------------\n// Shadcn / Tailwind-native shared primitive types\n//\n// These types replace what was previously imported from `@pihanga2/cards` and\n// are expressed in terms of Shadcn's design system and Tailwind utility tokens.\n// ---------------------------------------------------------------------------\n\n/**\n * Shadcn button / badge visual variant.\n *\n * Maps directly to the `variant` prop accepted by `@/components/ui/button`\n * and `@/components/ui/badge`.\n */\nexport type VariantT =\n | \"default\"\n | \"destructive\"\n | \"outline\"\n | \"secondary\"\n | \"ghost\"\n | \"link\";\n\n/**\n * Shadcn component size token.\n *\n * Maps directly to the `size` prop accepted by `@/components/ui/button`.\n */\nexport type SizeT = \"default\" | \"sm\" | \"lg\" | \"icon\";\n\n/**\n * Tailwind colour token (e.g. `\"red\"`, `\"emerald\"`, `\"sky\"`) or an arbitrary\n * CSS colour string. Kept as `string` so consumers can pass any Tailwind\n * palette key without being locked to a finite union.\n */\nexport type ColorT = string;\n\n/**\n * A decorator displayed before or after a button / menu-item label.\n *\n * Expressed as a registered icon name (resolved at render time via the icon\n * registry) or an arbitrary Tailwind-class string for inline SVG icons.\n */\nexport type DecoratorT = string;\n"],"mappings":";AAMA,IAAY,IAAL,yBAAA,GAAA;QACL,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,MAAA;AACF,EAAA,CAAA,CAAA,GAMa,IAAkC;;;;;;;AAO/C,GAMa,IAAiD;KAC3C;KACA;KACA;KACA;KACA;QACC;AACpB"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/cards/types.ts"],"sourcesContent":["/**\n * Screen size breakpoints matching Tailwind CSS defaults.\n *\n * Used with {@link SizeMap} to render different cards (or none at all)\n * depending on the viewport width.\n */\nexport enum ScreenSize {\n XS = \"xs\", // default / < 640px\n SM = \"sm\", // ≥ 640px\n MD = \"md\", // ≥ 768px\n LG = \"lg\", // ≥ 1024px\n XL = \"xl\", // ≥ 1280px\n XXL = \"2xl\", // ≥ 1536px\n}\n\n/**\n * Ascending order of breakpoints — used for cascade resolution.\n * @internal\n */\nexport const SCREEN_SIZE_ORDER: ScreenSize[] = [\n ScreenSize.XS,\n ScreenSize.SM,\n ScreenSize.MD,\n ScreenSize.LG,\n ScreenSize.XL,\n ScreenSize.XXL,\n];\n\n/**\n * Minimum viewport widths (px) for each breakpoint.\n * @internal\n */\nexport const SCREEN_SIZE_WIDTHS: Record<ScreenSize, number> = {\n [ScreenSize.XS]: 0,\n [ScreenSize.SM]: 640,\n [ScreenSize.MD]: 768,\n [ScreenSize.LG]: 1024,\n [ScreenSize.XL]: 1280,\n [ScreenSize.XXL]: 1536,\n};\n\n/**\n * Map a value {@link T} to specific screen-size breakpoints.\n *\n * Resolution follows a **mobile-first cascade**: the entry with the\n * largest breakpoint that is ≤ the current viewport size wins.\n *\n * ```ts\n * // Show \"desktopCard\" on md and above, nothing on xs/sm:\n * { [ScreenSize.MD]: \"desktopCard\" }\n *\n * // Show a different card per breakpoint:\n * { [ScreenSize.XS]: \"mobileCard\", [ScreenSize.MD]: \"desktopCard\" }\n *\n * // Explicitly hide on md and above:\n * { [ScreenSize.XS]: \"card\", [ScreenSize.MD]: null }\n * ```\n *\n * Set `default` as a final fallback when no breakpoint entry matches.\n */\nexport type SizeMap<T> = Partial<Record<ScreenSize, T | null>> & {\n default?: T | null;\n};\n\n// ---------------------------------------------------------------------------\n// Shadcn / Tailwind-native shared primitive types\n//\n// These types replace what was previously imported from `@pihanga2/cards` and\n// are expressed in terms of Shadcn's design system and Tailwind utility tokens.\n// ---------------------------------------------------------------------------\n\n/**\n * Shadcn button / badge visual variant.\n *\n * Maps directly to the `variant` prop accepted by `@/components/ui/button`\n * and `@/components/ui/badge`.\n */\nexport type VariantT =\n | \"default\"\n | \"destructive\"\n | \"outline\"\n | \"secondary\"\n | \"ghost\"\n | \"link\";\n\n/**\n * Shadcn component size token.\n *\n * Maps directly to the `size` prop accepted by `@/components/ui/button`.\n */\nexport type SizeT = \"default\" | \"sm\" | \"lg\" | \"icon\";\n\n/**\n * Tailwind colour token (e.g. `\"red\"`, `\"emerald\"`, `\"sky\"`) or an arbitrary\n * CSS colour string. Kept as `string` so consumers can pass any Tailwind\n * palette key without being locked to a finite union.\n */\nexport type ColorT = string;\n\n/**\n * A decorator displayed before or after a button / menu-item label.\n *\n * Expressed as a registered icon name (resolved at render time via the icon\n * registry) or an arbitrary Tailwind-class string for inline SVG icons.\n */\nexport type DecoratorT = string;\n\n/**\n * CSS `align-items` values supported by the Box & Stack card.\n */\nexport type AlignItemsT =\n | \"normal\"\n | \"stretch\"\n | \"center\"\n | \"flex-start\"\n | \"flex-end\"\n | \"start\"\n | \"end\"\n | \"baseline\"\n | \"initial\"\n | \"inherit\";\n"],"mappings":";AAMA,IAAY,IAAL,yBAAA,GAAA;QACL,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,KAAA,MACA,EAAA,MAAA;AACF,EAAA,CAAA,CAAA,GAMa,IAAkC;;;;;;;AAO/C,GAMa,IAAiD;KAC3C;KACA;KACA;KACA;KACA;QACC;AACpB"}
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Convert a user-supplied breakpoint selector string into a CSS media-query
4
+ * string suitable for `window.matchMedia`.
5
+ *
6
+ * Supported selector formats
7
+ * ──────────────────────────
8
+ * Named (Tailwind) │ `sm` `md` `lg` `xl` `2xl`
9
+ * Min-width │ `640px` `>=640px`
10
+ * Exclusive min │ `>640px` (≡ min-width: 641px)
11
+ * Max-width │ `<=1024px`
12
+ * Exclusive max │ `<1024px` (≡ max-width: 1023px)
13
+ */
14
+ export declare function selectorToMediaQuery(selector: string): string;
15
+ /**
16
+ * Convert a breakpoint selector string into a predicate that tests a pixel
17
+ * width directly. Used by `useContainerBreakpoint` to evaluate the selector
18
+ * against an element's `contentRect.width` rather than the viewport.
19
+ *
20
+ * Returns `() => true` for unrecognised selectors (fail-open).
21
+ */
22
+ export declare function selectorToWidthTest(selector: string): (width: number) => boolean;
23
+ /**
24
+ * Returns `true` when the **viewport** matches the given breakpoint selector,
25
+ * and reactively updates on resize via `window.matchMedia`.
26
+ *
27
+ * When `selector` is `undefined` the hook always returns `true` (no restriction).
28
+ *
29
+ * @param selector A breakpoint name (`sm` | `md` | `lg` | `xl` | `2xl`),
30
+ * a pixel expression (`>400px`, `>=640px`, `<768px`,
31
+ * `<=1024px`, `400px`), or `undefined`.
32
+ */
33
+ export declare function useBreakpoint(selector: string | undefined): boolean;
34
+ /**
35
+ * Returns `true` when the width of the element referenced by `ref` matches
36
+ * the given breakpoint selector. Uses `ResizeObserver` so it reacts to
37
+ * any layout change — not just window resize.
38
+ *
39
+ * When `selector` is `undefined` the hook always returns `true`.
40
+ *
41
+ * **Note:** The caller must attach `ref` to a DOM element. A thin wrapper
42
+ * `<div ref={ref} style={{ width: "100%" }}>` whose width equals its parent's
43
+ * content width is the canonical usage pattern, allowing the breakpoint to be
44
+ * evaluated against the **enclosing container** rather than the viewport.
45
+ *
46
+ * @param selector Same format as {@link useBreakpoint}.
47
+ * @param ref A React ref attached to the element whose width is measured.
48
+ */
49
+ export declare function useContainerBreakpoint(selector: string | undefined, ref: React.RefObject<HTMLElement | null>): boolean;
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ import * as e from "react";
3
+ //#region src/components/hooks/use-breakpoint.ts
4
+ var t = {
5
+ sm: 640,
6
+ md: 768,
7
+ lg: 1024,
8
+ xl: 1280,
9
+ "2xl": 1536
10
+ };
11
+ function n(e) {
12
+ return t[e] ? `(min-width: ${t[e]}px)` : /^>=\d+(\.\d+)?px$/.test(e) ? `(min-width: ${e.slice(2)})` : /^>\d+(\.\d+)?px$/.test(e) ? `(min-width: ${parseFloat(e.slice(1)) + 1}px)` : /^<=\d+(\.\d+)?px$/.test(e) ? `(max-width: ${e.slice(2)})` : /^<\d+(\.\d+)?px$/.test(e) ? `(max-width: ${parseFloat(e.slice(1)) - 1}px)` : /^\d+(\.\d+)?px$/.test(e) ? `(min-width: ${e})` : e;
13
+ }
14
+ function r(e) {
15
+ if (t[e]) {
16
+ let n = t[e];
17
+ return (e) => e >= n;
18
+ }
19
+ if (/^>=\d+(\.\d+)?px$/.test(e)) {
20
+ let t = parseFloat(e.slice(2));
21
+ return (e) => e >= t;
22
+ }
23
+ if (/^>\d+(\.\d+)?px$/.test(e)) {
24
+ let t = parseFloat(e.slice(1));
25
+ return (e) => e > t;
26
+ }
27
+ if (/^<=\d+(\.\d+)?px$/.test(e)) {
28
+ let t = parseFloat(e.slice(2));
29
+ return (e) => e <= t;
30
+ }
31
+ if (/^<\d+(\.\d+)?px$/.test(e)) {
32
+ let t = parseFloat(e.slice(1));
33
+ return (e) => e < t;
34
+ }
35
+ if (/^\d+(\.\d+)?px$/.test(e)) {
36
+ let t = parseFloat(e);
37
+ return (e) => e >= t;
38
+ }
39
+ return () => !0;
40
+ }
41
+ function i(t) {
42
+ let r = e.useMemo(() => t ? n(t) : null, [t]), [i, a] = e.useState(() => !r || typeof window > "u" ? !0 : window.matchMedia(r).matches);
43
+ return e.useEffect(() => {
44
+ if (!r) {
45
+ a(!0);
46
+ return;
47
+ }
48
+ let e = window.matchMedia(r);
49
+ a(e.matches);
50
+ let t = (e) => a(e.matches);
51
+ return e.addEventListener ? (e.addEventListener("change", t), () => e.removeEventListener("change", t)) : (e.addListener(t), () => e.removeListener(t));
52
+ }, [r]), i;
53
+ }
54
+ function a(t, n) {
55
+ let i = e.useMemo(() => t ? r(t) : null, [t]), [a, o] = e.useState(!0);
56
+ return e.useEffect(() => {
57
+ if (!i) {
58
+ o(!0);
59
+ return;
60
+ }
61
+ let e = n.current;
62
+ if (!e) return;
63
+ o(i(e.getBoundingClientRect().width));
64
+ let t = new ResizeObserver((e) => {
65
+ for (let t of e) o(i(t.contentBoxSize?.[0]?.inlineSize ?? t.contentRect.width));
66
+ });
67
+ return t.observe(e), () => t.disconnect();
68
+ }, [i, n]), a;
69
+ }
70
+ //#endregion
71
+ export { i as useBreakpoint, a as useContainerBreakpoint };
72
+
73
+ //# sourceMappingURL=use-breakpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-breakpoint.js","names":[],"sources":["../../../src/components/hooks/use-breakpoint.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n// ── Named breakpoints ─────────────────────────────────────────────────────────\n\n/** Named Tailwind-compatible breakpoints → pixel values. */\nconst NAMED_PX: Record<string, number> = {\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n \"2xl\": 1536,\n};\n\n// ── Selector parsers ──────────────────────────────────────────────────────────\n\n/**\n * Convert a user-supplied breakpoint selector string into a CSS media-query\n * string suitable for `window.matchMedia`.\n *\n * Supported selector formats\n * ──────────────────────────\n * Named (Tailwind) │ `sm` `md` `lg` `xl` `2xl`\n * Min-width │ `640px` `>=640px`\n * Exclusive min │ `>640px` (≡ min-width: 641px)\n * Max-width │ `<=1024px`\n * Exclusive max │ `<1024px` (≡ max-width: 1023px)\n */\nexport function selectorToMediaQuery(selector: string): string {\n if (NAMED_PX[selector]) return `(min-width: ${NAMED_PX[selector]}px)`;\n\n if (/^>=\\d+(\\.\\d+)?px$/.test(selector))\n return `(min-width: ${selector.slice(2)})`;\n\n if (/^>\\d+(\\.\\d+)?px$/.test(selector)) {\n const px = parseFloat(selector.slice(1));\n return `(min-width: ${px + 1}px)`;\n }\n\n if (/^<=\\d+(\\.\\d+)?px$/.test(selector))\n return `(max-width: ${selector.slice(2)})`;\n\n if (/^<\\d+(\\.\\d+)?px$/.test(selector)) {\n const px = parseFloat(selector.slice(1));\n return `(max-width: ${px - 1}px)`;\n }\n\n if (/^\\d+(\\.\\d+)?px$/.test(selector)) return `(min-width: ${selector})`;\n\n // Fallback: treat as a raw CSS media feature\n return selector;\n}\n\n/**\n * Convert a breakpoint selector string into a predicate that tests a pixel\n * width directly. Used by `useContainerBreakpoint` to evaluate the selector\n * against an element's `contentRect.width` rather than the viewport.\n *\n * Returns `() => true` for unrecognised selectors (fail-open).\n */\nexport function selectorToWidthTest(\n selector: string,\n): (width: number) => boolean {\n // Named Tailwind breakpoints → min-width\n if (NAMED_PX[selector]) {\n const threshold = NAMED_PX[selector];\n return (w) => w >= threshold;\n }\n\n // >=Npx\n if (/^>=\\d+(\\.\\d+)?px$/.test(selector)) {\n const px = parseFloat(selector.slice(2));\n return (w) => w >= px;\n }\n\n // >Npx\n if (/^>\\d+(\\.\\d+)?px$/.test(selector)) {\n const px = parseFloat(selector.slice(1));\n return (w) => w > px;\n }\n\n // <=Npx\n if (/^<=\\d+(\\.\\d+)?px$/.test(selector)) {\n const px = parseFloat(selector.slice(2));\n return (w) => w <= px;\n }\n\n // <Npx\n if (/^<\\d+(\\.\\d+)?px$/.test(selector)) {\n const px = parseFloat(selector.slice(1));\n return (w) => w < px;\n }\n\n // Bare Npx → min-width\n if (/^\\d+(\\.\\d+)?px$/.test(selector)) {\n const px = parseFloat(selector);\n return (w) => w >= px;\n }\n\n // Unrecognised → always visible\n return () => true;\n}\n\n// ── Hooks ─────────────────────────────────────────────────────────────────────\n\n/**\n * Returns `true` when the **viewport** matches the given breakpoint selector,\n * and reactively updates on resize via `window.matchMedia`.\n *\n * When `selector` is `undefined` the hook always returns `true` (no restriction).\n *\n * @param selector A breakpoint name (`sm` | `md` | `lg` | `xl` | `2xl`),\n * a pixel expression (`>400px`, `>=640px`, `<768px`,\n * `<=1024px`, `400px`), or `undefined`.\n */\nexport function useBreakpoint(selector: string | undefined): boolean {\n const mediaQuery = React.useMemo(\n () => (selector ? selectorToMediaQuery(selector) : null),\n [selector],\n );\n\n const [matches, setMatches] = React.useState<boolean>(() => {\n if (!mediaQuery || typeof window === \"undefined\") return true;\n return window.matchMedia(mediaQuery).matches;\n });\n\n React.useEffect(() => {\n if (!mediaQuery) {\n setMatches(true);\n return;\n }\n\n const mql = window.matchMedia(mediaQuery);\n setMatches(mql.matches);\n\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches);\n if (mql.addEventListener) {\n mql.addEventListener(\"change\", handler);\n return () => mql.removeEventListener(\"change\", handler);\n } else {\n mql.addListener(handler);\n return () => mql.removeListener(handler);\n }\n }, [mediaQuery]);\n\n return matches;\n}\n\n/**\n * Returns `true` when the width of the element referenced by `ref` matches\n * the given breakpoint selector. Uses `ResizeObserver` so it reacts to\n * any layout change — not just window resize.\n *\n * When `selector` is `undefined` the hook always returns `true`.\n *\n * **Note:** The caller must attach `ref` to a DOM element. A thin wrapper\n * `<div ref={ref} style={{ width: \"100%\" }}>` whose width equals its parent's\n * content width is the canonical usage pattern, allowing the breakpoint to be\n * evaluated against the **enclosing container** rather than the viewport.\n *\n * @param selector Same format as {@link useBreakpoint}.\n * @param ref A React ref attached to the element whose width is measured.\n */\nexport function useContainerBreakpoint(\n selector: string | undefined,\n ref: React.RefObject<HTMLElement | null>,\n): boolean {\n // Build the width-test predicate once per selector change.\n const test = React.useMemo(\n () => (selector ? selectorToWidthTest(selector) : null),\n [selector],\n );\n\n // Default to `true` (optimistic / SSR-safe) until the observer fires.\n const [matches, setMatches] = React.useState(true);\n\n React.useEffect(() => {\n if (!test) {\n setMatches(true);\n return;\n }\n\n const el = ref.current;\n if (!el) return;\n\n // Run immediately so the first render is correct.\n setMatches(test(el.getBoundingClientRect().width));\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n // `contentBoxSize` is the standard; fall back to `contentRect` for\n // older Safari.\n const width =\n entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;\n setMatches(test(width));\n }\n });\n\n observer.observe(el);\n return () => observer.disconnect();\n }, [test, ref]);\n\n return matches;\n}\n"],"mappings":";;;AAOA,IAAM,IAAmC;CACvC,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;AACT;AAgBA,SAAgB,EAAqB,GAA0B;CAsB7D,OArBI,EAAS,KAAkB,eAAe,EAAS,GAAU,OAE7D,oBAAoB,KAAK,CAAQ,IAC5B,eAAe,EAAS,MAAM,CAAC,EAAE,KAEtC,mBAAmB,KAAK,CAAQ,IAE3B,eADI,WAAW,EAAS,MAAM,CAAC,CAChB,IAAK,EAAE,OAG3B,oBAAoB,KAAK,CAAQ,IAC5B,eAAe,EAAS,MAAM,CAAC,EAAE,KAEtC,mBAAmB,KAAK,CAAQ,IAE3B,eADI,WAAW,EAAS,MAAM,CAAC,CAChB,IAAK,EAAE,OAG3B,kBAAkB,KAAK,CAAQ,IAAU,eAAe,EAAS,KAG9D;AACT;AASA,SAAgB,EACd,GAC4B;CAE5B,IAAI,EAAS,IAAW;EACtB,IAAM,IAAY,EAAS;EAC3B,QAAQ,MAAM,KAAK;CACrB;CAGA,IAAI,oBAAoB,KAAK,CAAQ,GAAG;EACtC,IAAM,IAAK,WAAW,EAAS,MAAM,CAAC,CAAC;EACvC,QAAQ,MAAM,KAAK;CACrB;CAGA,IAAI,mBAAmB,KAAK,CAAQ,GAAG;EACrC,IAAM,IAAK,WAAW,EAAS,MAAM,CAAC,CAAC;EACvC,QAAQ,MAAM,IAAI;CACpB;CAGA,IAAI,oBAAoB,KAAK,CAAQ,GAAG;EACtC,IAAM,IAAK,WAAW,EAAS,MAAM,CAAC,CAAC;EACvC,QAAQ,MAAM,KAAK;CACrB;CAGA,IAAI,mBAAmB,KAAK,CAAQ,GAAG;EACrC,IAAM,IAAK,WAAW,EAAS,MAAM,CAAC,CAAC;EACvC,QAAQ,MAAM,IAAI;CACpB;CAGA,IAAI,kBAAkB,KAAK,CAAQ,GAAG;EACpC,IAAM,IAAK,WAAW,CAAQ;EAC9B,QAAQ,MAAM,KAAK;CACrB;CAGA,aAAa;AACf;AAcA,SAAgB,EAAc,GAAuC;CACnE,IAAM,IAAa,EAAM,cAChB,IAAW,EAAqB,CAAQ,IAAI,MACnD,CAAC,CAAQ,CACX,GAEM,CAAC,GAAS,KAAc,EAAM,eAC9B,CAAC,KAAc,OAAO,SAAW,MAAoB,KAClD,OAAO,WAAW,CAAU,EAAE,OACtC;CAqBD,OAnBA,EAAM,gBAAgB;EACpB,IAAI,CAAC,GAAY;GACf,EAAW,EAAI;GACf;EACF;EAEA,IAAM,IAAM,OAAO,WAAW,CAAU;EACxC,EAAW,EAAI,OAAO;EAEtB,IAAM,KAAW,MAA2B,EAAW,EAAE,OAAO;EAM9D,OALE,EAAI,oBACN,EAAI,iBAAiB,UAAU,CAAO,SACzB,EAAI,oBAAoB,UAAU,CAAO,MAEtD,EAAI,YAAY,CAAO,SACV,EAAI,eAAe,CAAO;CAE3C,GAAG,CAAC,CAAU,CAAC,GAER;AACT;AAiBA,SAAgB,EACd,GACA,GACS;CAET,IAAM,IAAO,EAAM,cACV,IAAW,EAAoB,CAAQ,IAAI,MAClD,CAAC,CAAQ,CACX,GAGM,CAAC,GAAS,KAAc,EAAM,SAAS,EAAI;CA4BjD,OA1BA,EAAM,gBAAgB;EACpB,IAAI,CAAC,GAAM;GACT,EAAW,EAAI;GACf;EACF;EAEA,IAAM,IAAK,EAAI;EACf,IAAI,CAAC,GAAI;EAGT,EAAW,EAAK,EAAG,sBAAsB,EAAE,KAAK,CAAC;EAEjD,IAAM,IAAW,IAAI,gBAAgB,MAAY;GAC/C,KAAK,IAAM,KAAS,GAKlB,EAAW,EADT,EAAM,iBAAiB,IAAI,cAAc,EAAM,YAAY,KACxC,CAAC;EAE1B,CAAC;EAGD,OADA,EAAS,QAAQ,CAAE,SACN,EAAS,WAAW;CACnC,GAAG,CAAC,GAAM,CAAG,CAAC,GAEP;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pihanga2/shadcn",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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": {
@@ -127,6 +127,14 @@
127
127
  "./cards/typography": {
128
128
  "import": "./cards/typography/index.js",
129
129
  "types": "./cards/typography/index.d.ts"
130
+ },
131
+ "./cards/types": {
132
+ "import": "./cards/types.js",
133
+ "types": "./cards/types.d.ts"
134
+ },
135
+ "./cards/icons": {
136
+ "import": "./cards/icons.js",
137
+ "types": "./cards/icons.d.ts"
130
138
  }
131
139
  },
132
140
  "main": "./cards/core-index.js",