@pihanga2/shadcn 0.2.11 → 0.2.12

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.
@@ -1,35 +1,39 @@
1
1
  import { useFormContext as e } from "../form/form.context.js";
2
2
  import { Label as t } from "../../components/ui/label.js";
3
3
  import { Switch as n } from "../../components/ui/switch.js";
4
- import "react";
4
+ import r from "react";
5
5
  import "@pihanga2/core";
6
- import { jsx as r, jsxs as i } from "react/jsx-runtime";
6
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
7
7
  //#region src/cards/switch/switch.component.tsx
8
- var a = (a) => {
9
- let { name: o, checked: s = !1, disabled: c, label: l, className: u, cardName: d, onChanged: f } = a, p = a.id, m = e(), h = m.isInForm && !!o, g = h ? !!(m.formData[o] ?? !1) : s;
10
- function _(e) {
11
- h ? m.handleChange(o, e) : f({
12
- name: o,
8
+ var o = (o) => {
9
+ let { name: s, checked: c = !1, selfManaged: l = !1, disabled: u, label: d, className: f, cardName: p, onChanged: m } = o, h = o.id, g = e(), _ = g.isInForm && !!s, [v, y] = r.useState(c);
10
+ r.useEffect(() => {
11
+ l && y(c);
12
+ }, [c, l]);
13
+ let b = _ ? !!(g.formData[s] ?? !1) : l ? v : c;
14
+ function x(e) {
15
+ _ ? g.handleChange(s, e) : (l && y(e), m({
16
+ name: s,
13
17
  checked: e
14
- });
18
+ }));
15
19
  }
16
- let v = `${d}-${o ?? "switch"}`, y = p ?? v;
17
- return /* @__PURE__ */ i("div", {
18
- "data-pihanga": d,
19
- className: `flex items-center gap-2 ${u ?? ""}`,
20
- children: [/* @__PURE__ */ r(n, {
21
- id: y,
22
- checked: g,
23
- onCheckedChange: _,
24
- disabled: c
25
- }), l && /* @__PURE__ */ r(t, {
26
- htmlFor: y,
20
+ let S = `${p}-${s ?? "switch"}`, C = h ?? S;
21
+ return /* @__PURE__ */ a("div", {
22
+ "data-pihanga": p,
23
+ className: `flex items-center gap-2 ${f ?? ""}`,
24
+ children: [/* @__PURE__ */ i(n, {
25
+ id: C,
26
+ checked: b,
27
+ onCheckedChange: x,
28
+ disabled: u
29
+ }), d && /* @__PURE__ */ i(t, {
30
+ htmlFor: C,
27
31
  className: "cursor-pointer select-none",
28
- children: l
32
+ children: d
29
33
  })]
30
34
  });
31
35
  };
32
36
  //#endregion
33
- export { a as SwitchComponent };
37
+ export { o as SwitchComponent };
34
38
 
35
39
  //# sourceMappingURL=switch.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"switch.component.js","names":[],"sources":["../../../src/cards/switch/switch.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {Switch as SwitchUI} from \"@/components/ui/switch\";\nimport {Label} from \"@/components/ui/label\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {PiSwitchEvents, PiSwitchProps} from \"./switch.types\";\n\nexport const SwitchComponent = (\n props: PiCardProps<PiSwitchProps, PiSwitchEvents>,\n): React.ReactNode => {\n const {\n name,\n checked: propChecked = false,\n disabled,\n label,\n className,\n cardName,\n onChanged,\n } = props;\n\n // `id` and `invalid` may be injected by a parent pi/field card via\n // Pihanga's extra-prop forwarding. They are not in the official type so\n // we read them through an escape hatch.\n const injectedId = (props as {id?: string}).id;\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n const checked = useFormData\n ? Boolean(form.formData[name!] ?? false)\n : propChecked;\n\n function handleCheckedChange(newChecked: boolean) {\n if (useFormData) {\n form.handleChange(name!, newChecked);\n } else {\n onChanged({name, checked: newChecked});\n }\n }\n\n // Use the id injected by pi/field; fall back to a local id when standalone.\n const selfId = `${cardName}-${name ?? \"switch\"}`;\n const fieldId = injectedId ?? selfId;\n\n return (\n <div\n data-pihanga={cardName}\n className={`flex items-center gap-2 ${className ?? \"\"}`}\n >\n <SwitchUI\n id={fieldId}\n checked={checked}\n onCheckedChange={handleCheckedChange}\n disabled={disabled}\n />\n {label && (\n <Label htmlFor={fieldId} className=\"cursor-pointer select-none\">\n {label}\n </Label>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;AAOA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,SACA,SAAS,IAAc,IACvB,aACA,UACA,cACA,aACA,iBACE,GAKE,IAAc,EAAwB,IAGtC,IAAO,EAAe,GACtB,IAAc,EAAK,YAAY,EAAQ,GAEvC,IAAU,IACZ,GAAQ,EAAK,SAAS,MAAU,MAChC;CAEJ,SAAS,EAAoB,GAAqB;EAChD,AAAI,IACF,EAAK,aAAa,GAAO,CAAU,IAEnC,EAAU;GAAC;GAAM,SAAS;EAAU,CAAC;CAEzC;CAGA,IAAM,IAAS,GAAG,EAAS,GAAG,KAAQ,YAChC,IAAU,KAAc;CAE9B,OACE,kBAAC,OAAD;EACE,gBAAc;EACd,WAAW,2BAA2B,KAAa;YAFrD,CAIE,kBAAC,GAAD;GACE,IAAI;GACK;GACT,iBAAiB;GACP;EACX,CAAA,GACA,KACC,kBAAC,GAAD;GAAO,SAAS;GAAS,WAAU;aAChC;EACI,CAAA,CAEN;;AAET"}
1
+ {"version":3,"file":"switch.component.js","names":[],"sources":["../../../src/cards/switch/switch.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {Switch as SwitchUI} from \"@/components/ui/switch\";\nimport {Label} from \"@/components/ui/label\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {PiSwitchEvents, PiSwitchProps} from \"./switch.types\";\n\nexport const SwitchComponent = (\n props: PiCardProps<PiSwitchProps, PiSwitchEvents>,\n): React.ReactNode => {\n const {\n name,\n checked: propChecked = false,\n selfManaged = false,\n disabled,\n label,\n className,\n cardName,\n onChanged,\n } = props;\n\n // `id` and `invalid` may be injected by a parent pi/field card via\n // Pihanga's extra-prop forwarding. They are not in the official type so\n // we read them through an escape hatch.\n const injectedId = (props as {id?: string}).id;\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // Self-managed internal state — seeded from propChecked, only active when\n // selfManaged=true and we are NOT inside a pi/form.\n const [internalChecked, setInternalChecked] = React.useState(propChecked);\n\n // Keep internal state in sync when the external prop changes (e.g. when the\n // playground switches to a different facet and propChecked changes).\n React.useEffect(() => {\n if (selfManaged) setInternalChecked(propChecked);\n }, [propChecked, selfManaged]);\n\n const checked = useFormData\n ? Boolean(form.formData[name!] ?? false)\n : selfManaged\n ? internalChecked\n : propChecked;\n\n function handleCheckedChange(newChecked: boolean) {\n if (useFormData) {\n form.handleChange(name!, newChecked);\n } else {\n if (selfManaged) setInternalChecked(newChecked);\n onChanged({name, checked: newChecked});\n }\n }\n\n // Use the id injected by pi/field; fall back to a local id when standalone.\n const selfId = `${cardName}-${name ?? \"switch\"}`;\n const fieldId = injectedId ?? selfId;\n\n return (\n <div\n data-pihanga={cardName}\n className={`flex items-center gap-2 ${className ?? \"\"}`}\n >\n <SwitchUI\n id={fieldId}\n checked={checked}\n onCheckedChange={handleCheckedChange}\n disabled={disabled}\n />\n {label && (\n <Label htmlFor={fieldId} className=\"cursor-pointer select-none\">\n {label}\n </Label>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;AAOA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,SACA,SAAS,IAAc,IACvB,iBAAc,IACd,aACA,UACA,cACA,aACA,iBACE,GAKE,IAAc,EAAwB,IAGtC,IAAO,EAAe,GACtB,IAAc,EAAK,YAAY,EAAQ,GAIvC,CAAC,GAAiB,KAAsB,EAAM,SAAS,CAAW;CAIxE,EAAM,gBAAgB;EACpB,AAAI,KAAa,EAAmB,CAAW;CACjD,GAAG,CAAC,GAAa,CAAW,CAAC;CAE7B,IAAM,IAAU,IACZ,GAAQ,EAAK,SAAS,MAAU,MAChC,IACE,IACA;CAEN,SAAS,EAAoB,GAAqB;EAChD,AAAI,IACF,EAAK,aAAa,GAAO,CAAU,KAE/B,KAAa,EAAmB,CAAU,GAC9C,EAAU;GAAC;GAAM,SAAS;EAAU,CAAC;CAEzC;CAGA,IAAM,IAAS,GAAG,EAAS,GAAG,KAAQ,YAChC,IAAU,KAAc;CAE9B,OACE,kBAAC,OAAD;EACE,gBAAc;EACd,WAAW,2BAA2B,KAAa;YAFrD,CAIE,kBAAC,GAAD;GACE,IAAI;GACK;GACT,iBAAiB;GACP;EACX,CAAA,GACA,KACC,kBAAC,GAAD;GAAO,SAAS;GAAS,WAAU;aAChC;EACI,CAAA,CAEN;;AAET"}
@@ -22,6 +22,14 @@ export type PiSwitchProps = {
22
22
  * Ignored when `name` is set and the component is inside a pi/form.
23
23
  */
24
24
  checked?: boolean;
25
+ /**
26
+ * When true the component manages its own checked state internally via
27
+ * React.useState, seeded from the `checked` prop.
28
+ * The onChanged event still fires on every toggle so external listeners can
29
+ * react. Useful in playground previews and self-contained demos where no
30
+ * Redux state is wired up.
31
+ */
32
+ selfManaged?: boolean;
25
33
  /** When true, the switch is disabled and non-interactive. */
26
34
  disabled?: boolean;
27
35
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"switch.types.js","names":[],"sources":["../../../src/cards/switch/switch.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_SWITCH_CARD = \"pi/switch\";\n\nexport const Switch = createCardDeclaration<PiSwitchProps, PiSwitchEvents>(\n PI_SWITCH_CARD,\n);\n\nexport const PI_SWITCH_ACTION = registerActions(PI_SWITCH_CARD, [\"changed\"]);\n\nexport const onSwitchChanged = createOnAction<PiSwitchChangedEvent>(\n PI_SWITCH_ACTION.CHANGED,\n);\n\n/** @deprecated Use `onSwitchChanged` instead. */\nexport const onPiSwitchChanged = onSwitchChanged;\n\n// ---------------------------------------------------------------------------\n// Props & Events\n// ---------------------------------------------------------------------------\n\nexport type PiSwitchProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its checked state from form data and\n * writes back via form.handleChange.\n */\n name?: string;\n\n /**\n * Controlled checked state used in standalone mode (outside a Form).\n * Ignored when `name` is set and the component is inside a pi/form.\n */\n checked?: boolean;\n\n /** When true, the switch is disabled and non-interactive. */\n disabled?: boolean;\n\n /**\n * Optional label text rendered beside the switch.\n * The label is automatically associated with the switch via `htmlFor`.\n */\n label?: string;\n\n /** Extra Tailwind / CSS classes forwarded to the root wrapper element. */\n className?: string;\n};\n\nexport type PiSwitchChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** New checked state after the toggle. */\n checked: boolean;\n};\n\nexport type PiSwitchEvents = {\n onChanged: PiSwitchChangedEvent;\n};\n"],"mappings":";;AAMA,IAAa,IAAiB,aAEjB,IAAS,EACpB,CACF,GAEa,IAAmB,EAAgB,GAAgB,CAAC,SAAS,CAAC,GAE9D,IAAkB,EAC7B,EAAiB,OACnB,GAGa,IAAoB"}
1
+ {"version":3,"file":"switch.types.js","names":[],"sources":["../../../src/cards/switch/switch.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_SWITCH_CARD = \"pi/switch\";\n\nexport const Switch = createCardDeclaration<PiSwitchProps, PiSwitchEvents>(\n PI_SWITCH_CARD,\n);\n\nexport const PI_SWITCH_ACTION = registerActions(PI_SWITCH_CARD, [\"changed\"]);\n\nexport const onSwitchChanged = createOnAction<PiSwitchChangedEvent>(\n PI_SWITCH_ACTION.CHANGED,\n);\n\n/** @deprecated Use `onSwitchChanged` instead. */\nexport const onPiSwitchChanged = onSwitchChanged;\n\n// ---------------------------------------------------------------------------\n// Props & Events\n// ---------------------------------------------------------------------------\n\nexport type PiSwitchProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its checked state from form data and\n * writes back via form.handleChange.\n */\n name?: string;\n\n /**\n * Controlled checked state used in standalone mode (outside a Form).\n * Ignored when `name` is set and the component is inside a pi/form.\n */\n checked?: boolean;\n\n /**\n * When true the component manages its own checked state internally via\n * React.useState, seeded from the `checked` prop.\n * The onChanged event still fires on every toggle so external listeners can\n * react. Useful in playground previews and self-contained demos where no\n * Redux state is wired up.\n */\n selfManaged?: boolean;\n\n /** When true, the switch is disabled and non-interactive. */\n disabled?: boolean;\n\n /**\n * Optional label text rendered beside the switch.\n * The label is automatically associated with the switch via `htmlFor`.\n */\n label?: string;\n\n /** Extra Tailwind / CSS classes forwarded to the root wrapper element. */\n className?: string;\n};\n\nexport type PiSwitchChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** New checked state after the toggle. */\n checked: boolean;\n};\n\nexport type PiSwitchEvents = {\n onChanged: PiSwitchChangedEvent;\n};\n"],"mappings":";;AAMA,IAAa,IAAiB,aAEjB,IAAS,EACpB,CACF,GAEa,IAAmB,EAAgB,GAAgB,CAAC,SAAS,CAAC,GAE9D,IAAkB,EAC7B,EAAiB,OACnB,GAGa,IAAoB"}
@@ -4,53 +4,55 @@ import r from "react";
4
4
  import "@pihanga2/core";
5
5
  import { jsx as i } from "react/jsx-runtime";
6
6
  //#region src/cards/toggleGroup/toggleGroup.component.tsx
7
- var a = (a) => {
8
- let { name: o, items: s = [], type: c = "single", value: l, selfManaged: u = !1, variant: d = "default", size: f = "default", spacing: p = 0, disabled: m, className: h, cardName: g, onChanged: _ } = a, v = e(), y = v.isInForm && !!o, b = c === "multiple" ? Array.isArray(l) ? l : l == null ? [] : [String(l)] : typeof l == "string" ? l : "", [x, S] = r.useState(b), C = y ? v.formData[o] : void 0, w = y ? c === "multiple" ? Array.isArray(C) ? C : C == null ? [] : [String(C)] : C == null ? "" : String(C) : u ? x : l ?? (c === "multiple" ? [] : "");
9
- function T(e) {
10
- y ? v.handleChange(o, e) : (u && S(e), _({
11
- name: o ?? g,
7
+ var a = "bg-muted rounded-full p-1 border [&_[data-slot=toggle-group-item][data-state=on]]:bg-foreground [&_[data-slot=toggle-group-item][data-state=on]]:text-background [&_[data-slot=toggle-group-item][data-state=on]]:shadow-sm", o = (o) => {
8
+ let { name: s, items: c = [], type: l = "single", value: u, selfManaged: d = !1, variant: f = "default", size: p = "default", spacing: m = 0, disabled: h, className: g, cardName: _, onChanged: v } = o, y = f === "pill", b = y ? "default" : f, x = [y ? a : void 0, g].filter(Boolean).join(" ") || void 0, S = e(), C = S.isInForm && !!s, w = l === "multiple" ? Array.isArray(u) ? u : u == null ? [] : [String(u)] : typeof u == "string" ? u : "", [T, E] = r.useState(w), D = C ? S.formData[s] : void 0, O = C ? l === "multiple" ? Array.isArray(D) ? D : D == null ? [] : [String(D)] : D == null ? "" : String(D) : d ? T : u ?? (l === "multiple" ? [] : "");
9
+ function k(e) {
10
+ C ? S.handleChange(s, e) : (d && E(e), v({
11
+ name: s ?? _,
12
12
  value: e
13
13
  }));
14
14
  }
15
- function E(e) {
16
- y ? v.handleChange(o, e) : (u && S(e), _({
17
- name: o ?? g,
15
+ function A(e) {
16
+ C ? S.handleChange(s, e) : (d && E(e), v({
17
+ name: s ?? _,
18
18
  value: e
19
19
  }));
20
20
  }
21
- return c === "single" ? /* @__PURE__ */ i(t, {
22
- "data-pihanga": g,
21
+ return l === "single" ? /* @__PURE__ */ i(t, {
22
+ "data-pihanga": _,
23
23
  type: "single",
24
- value: typeof w == "string" ? w : "",
25
- onValueChange: T,
26
- variant: d,
27
- size: f,
28
- spacing: p,
29
- disabled: m,
30
- className: h,
31
- children: s.map((e) => /* @__PURE__ */ i(n, {
24
+ value: typeof O == "string" ? O : "",
25
+ onValueChange: k,
26
+ variant: b,
27
+ size: p,
28
+ spacing: m,
29
+ disabled: h,
30
+ className: x,
31
+ children: c.map((e) => /* @__PURE__ */ i(n, {
32
32
  value: e.value,
33
33
  disabled: e.disabled,
34
+ className: y ? "!rounded-full" : void 0,
34
35
  children: e.label
35
36
  }, e.value))
36
37
  }) : /* @__PURE__ */ i(t, {
37
- "data-pihanga": g,
38
+ "data-pihanga": _,
38
39
  type: "multiple",
39
- value: Array.isArray(w) ? w : w ? [w] : [],
40
- onValueChange: E,
41
- variant: d,
42
- size: f,
43
- spacing: p,
44
- disabled: m,
45
- className: h,
46
- children: s.map((e) => /* @__PURE__ */ i(n, {
40
+ value: Array.isArray(O) ? O : O ? [O] : [],
41
+ onValueChange: A,
42
+ variant: b,
43
+ size: p,
44
+ spacing: m,
45
+ disabled: h,
46
+ className: x,
47
+ children: c.map((e) => /* @__PURE__ */ i(n, {
47
48
  value: e.value,
48
49
  disabled: e.disabled,
50
+ className: y ? "!rounded-full" : void 0,
49
51
  children: e.label
50
52
  }, e.value))
51
53
  });
52
54
  };
53
55
  //#endregion
54
- export { a as ToggleGroupComponent };
56
+ export { o as ToggleGroupComponent };
55
57
 
56
58
  //# sourceMappingURL=toggleGroup.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"toggleGroup.component.js","names":[],"sources":["../../../src/cards/toggleGroup/toggleGroup.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {\n ToggleGroup as ToggleGroupUI,\n ToggleGroupItem,\n} from \"@/components/ui/toggle-group\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {\n PiToggleGroupEvents,\n PiToggleGroupProps,\n} from \"./toggleGroup.types\";\n\nexport const ToggleGroupComponent = (\n props: PiCardProps<PiToggleGroupProps, PiToggleGroupEvents>,\n): React.ReactNode => {\n const {\n name,\n items = [],\n type = \"single\",\n value: propValue,\n selfManaged = false,\n variant = \"default\",\n size = \"default\",\n spacing = 0,\n disabled,\n className,\n cardName,\n onChanged,\n } = props;\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // ---------------------------------------------------------------------------\n // Self-managed (internal) state — only active when selfManaged=true and we\n // are NOT inside a pi/form card.\n // ---------------------------------------------------------------------------\n const initialSelfValue: string | string[] =\n type === \"multiple\"\n ? Array.isArray(propValue)\n ? propValue\n : propValue != null\n ? [String(propValue)]\n : []\n : typeof propValue === \"string\"\n ? propValue\n : \"\";\n\n const [internalValue, setInternalValue] = React.useState<string | string[]>(\n initialSelfValue,\n );\n\n // ---------------------------------------------------------------------------\n // Derive the effective value from the appropriate source.\n // 1. Form context (highest priority)\n // 2. Self-managed internal state\n // 3. Externally controlled prop value\n // ---------------------------------------------------------------------------\n const formRawValue = useFormData ? form.formData[name!] : undefined;\n const value: string | string[] = useFormData\n ? type === \"multiple\"\n ? Array.isArray(formRawValue)\n ? (formRawValue as string[])\n : formRawValue != null\n ? [String(formRawValue)]\n : []\n : formRawValue != null\n ? String(formRawValue)\n : \"\"\n : selfManaged\n ? internalValue\n : (propValue ?? (type === \"multiple\" ? [] : \"\"));\n\n function handleSingleChange(newValue: string) {\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n if (selfManaged) {\n setInternalValue(newValue);\n }\n onChanged({name: name ?? cardName, value: newValue});\n }\n }\n\n function handleMultipleChange(newValue: string[]) {\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n if (selfManaged) {\n setInternalValue(newValue);\n }\n onChanged({name: name ?? cardName, value: newValue});\n }\n }\n\n // Render single-selection group\n if (type === \"single\") {\n return (\n <ToggleGroupUI\n data-pihanga={cardName}\n type=\"single\"\n value={typeof value === \"string\" ? value : \"\"}\n onValueChange={handleSingleChange}\n variant={variant}\n size={size}\n spacing={spacing}\n disabled={disabled}\n className={className}\n >\n {items.map((item) => (\n <ToggleGroupItem\n key={item.value}\n value={item.value}\n disabled={item.disabled}\n >\n {item.label}\n </ToggleGroupItem>\n ))}\n </ToggleGroupUI>\n );\n }\n\n // Render multiple-selection group\n return (\n <ToggleGroupUI\n data-pihanga={cardName}\n type=\"multiple\"\n value={Array.isArray(value) ? value : value ? [value] : []}\n onValueChange={handleMultipleChange}\n variant={variant}\n size={size}\n spacing={spacing}\n disabled={disabled}\n className={className}\n >\n {items.map((item) => (\n <ToggleGroupItem\n key={item.value}\n value={item.value}\n disabled={item.disabled}\n >\n {item.label}\n </ToggleGroupItem>\n ))}\n </ToggleGroupUI>\n );\n};\n"],"mappings":";;;;;;AAYA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,SACA,WAAQ,CAAC,GACT,UAAO,UACP,OAAO,GACP,iBAAc,IACd,aAAU,WACV,UAAO,WACP,aAAU,GACV,aACA,cACA,aACA,iBACE,GAGE,IAAO,EAAe,GACtB,IAAc,EAAK,YAAY,EAAQ,GAMvC,IACJ,MAAS,aACL,MAAM,QAAQ,CAAS,IACrB,IACA,KAAa,OAEX,CAAC,IADD,CAAC,OAAO,CAAS,CAAC,IAEtB,OAAO,KAAc,WACnB,IACA,IAEF,CAAC,GAAe,KAAoB,EAAM,SAC9C,CACF,GAQM,IAAe,IAAc,EAAK,SAAS,KAAS,KAAA,GACpD,IAA2B,IAC7B,MAAS,aACP,MAAM,QAAQ,CAAY,IACvB,IACD,KAAgB,OAEd,CAAC,IADD,CAAC,OAAO,CAAY,CAAC,IAEzB,KAAgB,OAEd,KADA,OAAO,CAAY,IAEvB,IACE,IACC,MAAc,MAAS,aAAa,CAAC,IAAI;CAEhD,SAAS,EAAmB,GAAkB;EAC5C,AAAI,IACF,EAAK,aAAa,GAAO,CAAQ,KAE7B,KACF,EAAiB,CAAQ,GAE3B,EAAU;GAAC,MAAM,KAAQ;GAAU,OAAO;EAAQ,CAAC;CAEvD;CAEA,SAAS,EAAqB,GAAoB;EAChD,AAAI,IACF,EAAK,aAAa,GAAO,CAAQ,KAE7B,KACF,EAAiB,CAAQ,GAE3B,EAAU;GAAC,MAAM,KAAQ;GAAU,OAAO;EAAQ,CAAC;CAEvD;CA8BA,OA3BI,MAAS,WAET,kBAAC,GAAD;EACE,gBAAc;EACd,MAAK;EACL,OAAO,OAAO,KAAU,WAAW,IAAQ;EAC3C,eAAe;EACN;EACH;EACG;EACC;EACC;YAEV,EAAM,KAAK,MACV,kBAAC,GAAD;GAEE,OAAO,EAAK;GACZ,UAAU,EAAK;aAEd,EAAK;EACS,GALV,EAAK,KAKK,CAClB;CACY,CAAA,IAMjB,kBAAC,GAAD;EACE,gBAAc;EACd,MAAK;EACL,OAAO,MAAM,QAAQ,CAAK,IAAI,IAAQ,IAAQ,CAAC,CAAK,IAAI,CAAC;EACzD,eAAe;EACN;EACH;EACG;EACC;EACC;YAEV,EAAM,KAAK,MACV,kBAAC,GAAD;GAEE,OAAO,EAAK;GACZ,UAAU,EAAK;aAEd,EAAK;EACS,GALV,EAAK,KAKK,CAClB;CACY,CAAA;AAEnB"}
1
+ {"version":3,"file":"toggleGroup.component.js","names":[],"sources":["../../../src/cards/toggleGroup/toggleGroup.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {\n ToggleGroup as ToggleGroupUI,\n ToggleGroupItem,\n} from \"@/components/ui/toggle-group\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {\n PiToggleGroupEvents,\n PiToggleGroupProps,\n} from \"./toggleGroup.types\";\n\n// Applied on the container when variant=\"pill\".\n// Item rounding is handled directly via the ToggleGroupItem className prop\n// (using !rounded-full) so Tailwind scans it and generates !important CSS.\nconst PILL_CLASS =\n \"bg-muted rounded-full p-1 border\" +\n \" [&_[data-slot=toggle-group-item][data-state=on]]:bg-foreground\" +\n \" [&_[data-slot=toggle-group-item][data-state=on]]:text-background\" +\n \" [&_[data-slot=toggle-group-item][data-state=on]]:shadow-sm\";\n\nexport const ToggleGroupComponent = (\n props: PiCardProps<PiToggleGroupProps, PiToggleGroupEvents>,\n): React.ReactNode => {\n const {\n name,\n items = [],\n type = \"single\",\n value: propValue,\n selfManaged = false,\n variant = \"default\",\n size = \"default\",\n spacing = 0,\n disabled,\n className,\n cardName,\n onChanged,\n } = props;\n\n // Pill variant: apply segmented-control styling and fall back to \"default\"\n // for the underlying UI primitive.\n const isPill = variant === \"pill\";\n const resolvedVariant = isPill ? \"default\" : variant;\n const resolvedClassName =\n [isPill ? PILL_CLASS : undefined, className].filter(Boolean).join(\" \") ||\n undefined;\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // ---------------------------------------------------------------------------\n // Self-managed (internal) state — only active when selfManaged=true and we\n // are NOT inside a pi/form card.\n // ---------------------------------------------------------------------------\n const initialSelfValue: string | string[] =\n type === \"multiple\"\n ? Array.isArray(propValue)\n ? propValue\n : propValue != null\n ? [String(propValue)]\n : []\n : typeof propValue === \"string\"\n ? propValue\n : \"\";\n\n const [internalValue, setInternalValue] = React.useState<string | string[]>(\n initialSelfValue,\n );\n\n // ---------------------------------------------------------------------------\n // Derive the effective value from the appropriate source.\n // 1. Form context (highest priority)\n // 2. Self-managed internal state\n // 3. Externally controlled prop value\n // ---------------------------------------------------------------------------\n const formRawValue = useFormData ? form.formData[name!] : undefined;\n const value: string | string[] = useFormData\n ? type === \"multiple\"\n ? Array.isArray(formRawValue)\n ? (formRawValue as string[])\n : formRawValue != null\n ? [String(formRawValue)]\n : []\n : formRawValue != null\n ? String(formRawValue)\n : \"\"\n : selfManaged\n ? internalValue\n : (propValue ?? (type === \"multiple\" ? [] : \"\"));\n\n function handleSingleChange(newValue: string) {\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n if (selfManaged) {\n setInternalValue(newValue);\n }\n onChanged({name: name ?? cardName, value: newValue});\n }\n }\n\n function handleMultipleChange(newValue: string[]) {\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n if (selfManaged) {\n setInternalValue(newValue);\n }\n onChanged({name: name ?? cardName, value: newValue});\n }\n }\n\n // Render single-selection group\n if (type === \"single\") {\n return (\n <ToggleGroupUI\n data-pihanga={cardName}\n type=\"single\"\n value={typeof value === \"string\" ? value : \"\"}\n onValueChange={handleSingleChange}\n variant={resolvedVariant}\n size={size}\n spacing={spacing}\n disabled={disabled}\n className={resolvedClassName}\n >\n {items.map((item) => (\n <ToggleGroupItem\n key={item.value}\n value={item.value}\n disabled={item.disabled}\n className={isPill ? \"!rounded-full\" : undefined}\n >\n {item.label}\n </ToggleGroupItem>\n ))}\n </ToggleGroupUI>\n );\n }\n\n // Render multiple-selection group\n return (\n <ToggleGroupUI\n data-pihanga={cardName}\n type=\"multiple\"\n value={Array.isArray(value) ? value : value ? [value] : []}\n onValueChange={handleMultipleChange}\n variant={resolvedVariant}\n size={size}\n spacing={spacing}\n disabled={disabled}\n className={resolvedClassName}\n >\n {items.map((item) => (\n <ToggleGroupItem\n key={item.value}\n value={item.value}\n disabled={item.disabled}\n className={isPill ? \"!rounded-full\" : undefined}\n >\n {item.label}\n </ToggleGroupItem>\n ))}\n </ToggleGroupUI>\n );\n};\n"],"mappings":";;;;;;AAeA,IAAM,IACJ,+NAKW,KACX,MACoB;CACpB,IAAM,EACJ,SACA,WAAQ,CAAC,GACT,UAAO,UACP,OAAO,GACP,iBAAc,IACd,aAAU,WACV,UAAO,WACP,aAAU,GACV,aACA,cACA,aACA,iBACE,GAIE,IAAS,MAAY,QACrB,IAAkB,IAAS,YAAY,GACvC,IACJ,CAAC,IAAS,IAAa,KAAA,GAAW,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KACrE,KAAA,GAGI,IAAO,EAAe,GACtB,IAAc,EAAK,YAAY,EAAQ,GAMvC,IACJ,MAAS,aACL,MAAM,QAAQ,CAAS,IACrB,IACA,KAAa,OAEX,CAAC,IADD,CAAC,OAAO,CAAS,CAAC,IAEtB,OAAO,KAAc,WACnB,IACA,IAEF,CAAC,GAAe,KAAoB,EAAM,SAC9C,CACF,GAQM,IAAe,IAAc,EAAK,SAAS,KAAS,KAAA,GACpD,IAA2B,IAC7B,MAAS,aACP,MAAM,QAAQ,CAAY,IACvB,IACD,KAAgB,OAEd,CAAC,IADD,CAAC,OAAO,CAAY,CAAC,IAEzB,KAAgB,OAEd,KADA,OAAO,CAAY,IAEvB,IACE,IACC,MAAc,MAAS,aAAa,CAAC,IAAI;CAEhD,SAAS,EAAmB,GAAkB;EAC5C,AAAI,IACF,EAAK,aAAa,GAAO,CAAQ,KAE7B,KACF,EAAiB,CAAQ,GAE3B,EAAU;GAAC,MAAM,KAAQ;GAAU,OAAO;EAAQ,CAAC;CAEvD;CAEA,SAAS,EAAqB,GAAoB;EAChD,AAAI,IACF,EAAK,aAAa,GAAO,CAAQ,KAE7B,KACF,EAAiB,CAAQ,GAE3B,EAAU;GAAC,MAAM,KAAQ;GAAU,OAAO;EAAQ,CAAC;CAEvD;CA+BA,OA5BI,MAAS,WAET,kBAAC,GAAD;EACE,gBAAc;EACd,MAAK;EACL,OAAO,OAAO,KAAU,WAAW,IAAQ;EAC3C,eAAe;EACf,SAAS;EACH;EACG;EACC;EACV,WAAW;YAEV,EAAM,KAAK,MACV,kBAAC,GAAD;GAEE,OAAO,EAAK;GACZ,UAAU,EAAK;GACf,WAAW,IAAS,kBAAkB,KAAA;aAErC,EAAK;EACS,GANV,EAAK,KAMK,CAClB;CACY,CAAA,IAMjB,kBAAC,GAAD;EACE,gBAAc;EACd,MAAK;EACL,OAAO,MAAM,QAAQ,CAAK,IAAI,IAAQ,IAAQ,CAAC,CAAK,IAAI,CAAC;EACzD,eAAe;EACf,SAAS;EACH;EACG;EACC;EACV,WAAW;YAEV,EAAM,KAAK,MACV,kBAAC,GAAD;GAEE,OAAO,EAAK;GACZ,UAAU,EAAK;GACf,WAAW,IAAS,kBAAkB,KAAA;aAErC,EAAK;EACS,GANV,EAAK,KAMK,CAClB;CACY,CAAA;AAEnB"}
@@ -63,10 +63,12 @@ export type PiToggleGroupProps = {
63
63
  * Visual style variant of the toggle buttons.
64
64
  * - `"default"` — transparent background, highlights on active.
65
65
  * - `"outline"` — bordered buttons, grouped or spaced.
66
+ * - `"pill"` — segmented-control style: muted rounded-full container with a
67
+ * filled foreground pill on the active item.
66
68
  *
67
69
  * @default "default"
68
70
  */
69
- variant?: "default" | "outline";
71
+ variant?: "default" | "outline" | "pill";
70
72
  /**
71
73
  * Size of each toggle button.
72
74
  * @default "default"
@@ -1 +1 @@
1
- {"version":3,"file":"toggleGroup.types.js","names":[],"sources":["../../../src/cards/toggleGroup/toggleGroup.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_TOGGLE_GROUP_CARD = \"pi/toggle-group\";\n\nexport const ToggleGroup = createCardDeclaration<\n PiToggleGroupProps,\n PiToggleGroupEvents\n>(PI_TOGGLE_GROUP_CARD);\n\nexport const PI_TOGGLE_GROUP_ACTION = registerActions(PI_TOGGLE_GROUP_CARD, [\n \"changed\",\n]);\n\nexport const onToggleGroupChanged = createOnAction<PiToggleGroupChangedEvent>(\n PI_TOGGLE_GROUP_ACTION.CHANGED,\n);\n\n/** @deprecated Use `onToggleGroupChanged` instead. */\nexport const onPiToggleGroupChanged = onToggleGroupChanged;\n\n// ---------------------------------------------------------------------------\n// Item type\n// ---------------------------------------------------------------------------\n\nexport type PiToggleGroupItem = {\n /** The value associated with this toggle item. */\n value: string;\n /** Human-readable label rendered inside the toggle button. */\n label: string;\n /** When true, this individual item is disabled. */\n disabled?: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Props & Events\n// ---------------------------------------------------------------------------\n\nexport type PiToggleGroupProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its selected value from form data and\n * writes back via form.handleChange.\n */\n name?: string;\n\n /**\n * The list of toggle items to display.\n */\n items: PiToggleGroupItem[];\n\n /**\n * Selection mode.\n * - `\"single\"` — at most one item can be active at a time.\n * - `\"multiple\"` — any number of items can be active simultaneously.\n *\n * @default \"single\"\n */\n type?: \"single\" | \"multiple\";\n\n /**\n * Controlled selected value(s).\n * - In `\"single\"` mode: a `string` (or `undefined` for nothing selected).\n * - In `\"multiple\"` mode: an array of `string`.\n *\n * Ignored when `name` is set and the component is inside a pi/form.\n * Also ignored when `selfManaged` is `true`.\n */\n value?: string | string[];\n\n /**\n * When `true`, the component manages its own selection state internally\n * using `React.useState`. The `value` prop is used only as the initial\n * value (i.e. `defaultValue`) and subsequent changes do not require an\n * external state update. `onChanged` is still fired so callers can\n * observe changes without owning the state.\n *\n * Has no effect when inside a pi/form (form context always takes\n * precedence).\n *\n * @default false\n */\n selfManaged?: boolean;\n\n /**\n * Visual style variant of the toggle buttons.\n * - `\"default\"` — transparent background, highlights on active.\n * - `\"outline\"` — bordered buttons, grouped or spaced.\n *\n * @default \"default\"\n */\n variant?: \"default\" | \"outline\";\n\n /**\n * Size of each toggle button.\n * @default \"default\"\n */\n size?: \"default\" | \"sm\" | \"lg\";\n\n /**\n * Gap between toggle buttons (in Tailwind spacing units).\n * `0` = buttons are joined (no gap, rounded ends only).\n * Positive values add space between each button.\n *\n * @default 0\n */\n spacing?: number;\n\n /** When true, the entire group is disabled and non-interactive. */\n disabled?: boolean;\n\n /** Extra Tailwind / CSS classes forwarded to the root element. */\n className?: string;\n};\n\nexport type PiToggleGroupChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /**\n * New selected value(s).\n * - In `\"single\"` mode: a `string` (empty string when nothing selected).\n * - In `\"multiple\"` mode: an array of `string`.\n */\n value: string | string[];\n};\n\nexport type PiToggleGroupEvents = {\n onChanged: PiToggleGroupChangedEvent;\n};\n"],"mappings":";;AAMA,IAAa,IAAuB,mBAEvB,IAAc,EAGzB,CAAoB,GAET,IAAyB,EAAgB,GAAsB,CAC1E,SACF,CAAC,GAEY,IAAuB,EAClC,EAAuB,OACzB,GAGa,IAAyB"}
1
+ {"version":3,"file":"toggleGroup.types.js","names":[],"sources":["../../../src/cards/toggleGroup/toggleGroup.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_TOGGLE_GROUP_CARD = \"pi/toggle-group\";\n\nexport const ToggleGroup = createCardDeclaration<\n PiToggleGroupProps,\n PiToggleGroupEvents\n>(PI_TOGGLE_GROUP_CARD);\n\nexport const PI_TOGGLE_GROUP_ACTION = registerActions(PI_TOGGLE_GROUP_CARD, [\n \"changed\",\n]);\n\nexport const onToggleGroupChanged = createOnAction<PiToggleGroupChangedEvent>(\n PI_TOGGLE_GROUP_ACTION.CHANGED,\n);\n\n/** @deprecated Use `onToggleGroupChanged` instead. */\nexport const onPiToggleGroupChanged = onToggleGroupChanged;\n\n// ---------------------------------------------------------------------------\n// Item type\n// ---------------------------------------------------------------------------\n\nexport type PiToggleGroupItem = {\n /** The value associated with this toggle item. */\n value: string;\n /** Human-readable label rendered inside the toggle button. */\n label: string;\n /** When true, this individual item is disabled. */\n disabled?: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Props & Events\n// ---------------------------------------------------------------------------\n\nexport type PiToggleGroupProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its selected value from form data and\n * writes back via form.handleChange.\n */\n name?: string;\n\n /**\n * The list of toggle items to display.\n */\n items: PiToggleGroupItem[];\n\n /**\n * Selection mode.\n * - `\"single\"` — at most one item can be active at a time.\n * - `\"multiple\"` — any number of items can be active simultaneously.\n *\n * @default \"single\"\n */\n type?: \"single\" | \"multiple\";\n\n /**\n * Controlled selected value(s).\n * - In `\"single\"` mode: a `string` (or `undefined` for nothing selected).\n * - In `\"multiple\"` mode: an array of `string`.\n *\n * Ignored when `name` is set and the component is inside a pi/form.\n * Also ignored when `selfManaged` is `true`.\n */\n value?: string | string[];\n\n /**\n * When `true`, the component manages its own selection state internally\n * using `React.useState`. The `value` prop is used only as the initial\n * value (i.e. `defaultValue`) and subsequent changes do not require an\n * external state update. `onChanged` is still fired so callers can\n * observe changes without owning the state.\n *\n * Has no effect when inside a pi/form (form context always takes\n * precedence).\n *\n * @default false\n */\n selfManaged?: boolean;\n\n /**\n * Visual style variant of the toggle buttons.\n * - `\"default\"` — transparent background, highlights on active.\n * - `\"outline\"` — bordered buttons, grouped or spaced.\n * - `\"pill\"` — segmented-control style: muted rounded-full container with a\n * filled foreground pill on the active item.\n *\n * @default \"default\"\n */\n variant?: \"default\" | \"outline\" | \"pill\";\n\n /**\n * Size of each toggle button.\n * @default \"default\"\n */\n size?: \"default\" | \"sm\" | \"lg\";\n\n /**\n * Gap between toggle buttons (in Tailwind spacing units).\n * `0` = buttons are joined (no gap, rounded ends only).\n * Positive values add space between each button.\n *\n * @default 0\n */\n spacing?: number;\n\n /** When true, the entire group is disabled and non-interactive. */\n disabled?: boolean;\n\n /** Extra Tailwind / CSS classes forwarded to the root element. */\n className?: string;\n};\n\nexport type PiToggleGroupChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /**\n * New selected value(s).\n * - In `\"single\"` mode: a `string` (empty string when nothing selected).\n * - In `\"multiple\"` mode: an array of `string`.\n */\n value: string | string[];\n};\n\nexport type PiToggleGroupEvents = {\n onChanged: PiToggleGroupChangedEvent;\n};\n"],"mappings":";;AAMA,IAAa,IAAuB,mBAEvB,IAAc,EAGzB,CAAoB,GAET,IAAyB,EAAgB,GAAsB,CAC1E,SACF,CAAC,GAEY,IAAuB,EAClC,EAAuB,OACzB,GAGa,IAAyB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pihanga2/shadcn",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
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": {