@pihanga2/shadcn 0.2.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 (39) hide show
  1. package/AGENT.md +10 -3
  2. package/AGENT.using-cards.md +55 -246
  3. package/cards/box/box.component.js +2 -2
  4. package/cards/box/box.component.js.map +1 -1
  5. package/cards/button/button.types.d.ts +3 -0
  6. package/cards/button/button.types.js +2 -2
  7. package/cards/button/button.types.js.map +1 -1
  8. package/cards/button/index.js +5 -5
  9. package/cards/chartGraph/chartGraph.component.d.ts +4 -0
  10. package/cards/chartGraph/chartGraph.types.d.ts +102 -0
  11. package/cards/chartGraph/index.d.ts +1 -0
  12. package/cards/conditional/conditional.component.d.ts +14 -3
  13. package/cards/conditional/conditional.component.js +17 -9
  14. package/cards/conditional/conditional.component.js.map +1 -1
  15. package/cards/conditional/conditional.types.d.ts +100 -8
  16. package/cards/conditional/conditional.types.js.map +1 -1
  17. package/cards/core-index.js +31 -31
  18. package/cards/graphin/contextMenu.component.d.ts +28 -0
  19. package/cards/graphin/eventDispatcher.component.d.ts +8 -0
  20. package/cards/graphin/graphin.component.d.ts +2 -2
  21. package/cards/graphin/graphin.playground-cards.d.ts +1 -0
  22. package/cards/graphin/graphin.types.d.ts +444 -4
  23. package/cards/graphin/graphinOpHandler.component.d.ts +8 -0
  24. package/cards/graphin/index.d.ts +1 -0
  25. package/cards/graphin/tooltip.component.d.ts +13 -1
  26. package/cards/slider/index.d.ts +1 -0
  27. package/cards/slider/slider.component.d.ts +4 -0
  28. package/cards/slider/slider.types.d.ts +102 -0
  29. package/cards/sliderValue/index.d.ts +1 -0
  30. package/cards/sliderValue/sliderValue.component.d.ts +15 -0
  31. package/cards/sliderValue/sliderValue.types.d.ts +21 -0
  32. package/cards/tabs/tabs.component.js +51 -25
  33. package/cards/tabs/tabs.component.js.map +1 -1
  34. package/cards/tabs/tabs.types.d.ts +10 -0
  35. package/cards/tabs/tabs.types.js.map +1 -1
  36. package/components/hooks/use-breakpoint.d.ts +49 -0
  37. package/components/hooks/use-breakpoint.js +73 -0
  38. package/components/hooks/use-breakpoint.js.map +1 -0
  39. package/package.json +1 -1
@@ -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"}
@@ -63,6 +63,16 @@ export type TabsProps = {
63
63
  listClassName?: string;
64
64
  /** Additional classes applied to each TabsContent element. */
65
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;
66
76
  };
67
77
  export type TabsTabChangedEvent = {
68
78
  /** The id of the newly selected tab. */
@@ -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 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// ─── 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"}
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"}
@@ -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.2.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": {