@spark-ui/components 17.12.0 → 17.13.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.
- package/dist/accordion/Accordion.d.ts +19 -6
- package/dist/accordion/index.js +1 -1
- package/dist/accordion/index.js.map +1 -1
- package/dist/accordion/index.mjs +11 -5
- package/dist/accordion/index.mjs.map +1 -1
- package/dist/segmented-control/SegmentedControl.d.ts +15 -3
- package/dist/segmented-control/SegmentedControlContext.d.ts +4 -0
- package/dist/segmented-control/SegmentedControlItem.d.ts +2 -3
- package/dist/segmented-control/index.js +1 -1
- package/dist/segmented-control/index.js.map +1 -1
- package/dist/segmented-control/index.mjs +124 -52
- package/dist/segmented-control/index.mjs.map +1 -1
- package/dist/segmented-control/useSegmentedControlNavigation.d.ts +14 -0
- package/package.json +5 -5
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Accordion as BaseAccordion } from '@base-ui/react/accordion';
|
|
2
2
|
import { ComponentProps, Ref } from 'react';
|
|
3
|
-
type
|
|
4
|
-
|
|
3
|
+
type BaseAccordionRootProps = ComponentProps<typeof BaseAccordion.Root<string | string[]>>;
|
|
4
|
+
type ExtentedBaseUIInterface = Omit<BaseAccordionRootProps, 'multiple' | 'render' | 'value' | 'defaultValue' | 'onValueChange'>;
|
|
5
|
+
export interface AccordionProps extends ExtentedBaseUIInterface {
|
|
5
6
|
/**
|
|
6
7
|
* Change the default rendered element for the one passed as a child, merging their props and behavior.
|
|
7
8
|
*/
|
|
@@ -16,11 +17,23 @@ export interface AccordionProps extends ExtentedZagInterface {
|
|
|
16
17
|
multiple?: boolean;
|
|
17
18
|
design?: 'filled' | 'outlined';
|
|
18
19
|
ref?: Ref<HTMLDivElement>;
|
|
20
|
+
/**
|
|
21
|
+
* The controlled value (always an array of strings)
|
|
22
|
+
*/
|
|
23
|
+
value?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* The default value (always an array of strings)
|
|
26
|
+
*/
|
|
27
|
+
defaultValue?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Callback when the value changes (always receives an array of strings)
|
|
30
|
+
*/
|
|
31
|
+
onValueChange?: (value: string[]) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function Accordion({ asChild, children, design, hiddenUntilFound, multiple, className, ref, value, defaultValue, onValueChange, ...props }: AccordionProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export declare namespace Accordion {
|
|
35
|
+
var displayName: string;
|
|
19
36
|
}
|
|
20
|
-
export declare const Accordion: {
|
|
21
|
-
({ asChild, children, design, hiddenUntilFound, multiple, className, ref, ...props }: AccordionProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
-
displayName: string;
|
|
23
|
-
};
|
|
24
37
|
export declare const useAccordionContext: () => {
|
|
25
38
|
design: "filled" | "outlined";
|
|
26
39
|
};
|
package/dist/accordion/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`../chunk-C91j1N6u.js`);const e=require(`../slot/index.js`),t=require(`../icon-CRPcdgYp.js`);let n=require(`@base-ui/react/accordion`),r=require(`class-variance-authority`),i=require(`react`),a=require(`react/jsx-runtime`),o=require(`@spark-ui/icons/ArrowHorizontalDown`);function s(t,n){let r=t?e.Slot:n;return t?({...e})=>(0,a.jsx)(r,{...e}):void 0}var c=(0,i.createContext)(null)
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`../chunk-C91j1N6u.js`);const e=require(`../slot/index.js`),t=require(`../icon-CRPcdgYp.js`);let n=require(`@base-ui/react/accordion`),r=require(`class-variance-authority`),i=require(`react`),a=require(`react/jsx-runtime`),o=require(`@spark-ui/icons/ArrowHorizontalDown`);function s(t,n){let r=t?e.Slot:n;return t?({...e})=>(0,a.jsx)(r,{...e}):void 0}var c=(0,i.createContext)(null);function l({asChild:e=!1,children:t,design:i=`outlined`,hiddenUntilFound:o=!0,multiple:l=!1,className:u,ref:d,value:f,defaultValue:p,onValueChange:m,...h}){let g=s(e,`div`),_=m?e=>{m(Array.isArray(e)?e:[e])}:void 0;return(0,a.jsx)(c,{value:{design:i},children:(0,a.jsx)(n.Accordion.Root,{"data-spark-component":`accordion`,ref:d,multiple:l,hiddenUntilFound:o,className:(0,r.cx)(`bg-surface h-fit rounded-lg`,u),render:g,value:f,defaultValue:p,onValueChange:_,...h,children:t})})}l.displayName=`Accordion`;var u=()=>{let e=(0,i.useContext)(c);if(!e)throw Error(`useAccordionContext must be used within a Accordion provider`);return e},d=({asChild:e=!1,className:t,children:i,ref:o,...c})=>{let l=u(),d=s(e,`div`);return(0,a.jsx)(n.Accordion.Item,{ref:o,"data-spark-component":`accordion-item`,render:d,className:(0,r.cx)(`relative first:rounded-t-lg last:rounded-b-lg`,`not-last:border-b-0`,{"border-sm border-outline":l.design===`outlined`},t),...c,children:i})};d.displayName=`Accordion.Item`;var f=({asChild:e=!1,className:t,children:i,ref:o,...c})=>{let l=s(e,`div`);return(0,a.jsx)(n.Accordion.Panel,{ref:o,"data-spark-component":`accordion-item-content`,className:(0,r.cx)(`[&>:first-child]:p-lg overflow-hidden`,`h-[var(--accordion-panel-height)] transition-all duration-200 data-[ending-style]:h-0 data-[starting-style]:h-0`,`text-body-1 text-on-surface`,t),render:l,...c,children:i})};f.displayName=`Accordion.ItemContent`;var p=({asChild:t=!1,children:n,className:i,ref:o})=>(0,a.jsx)(t?e.Slot:`h3`,{ref:o,"data-spark-component":`accordion-item-header`,className:(0,r.cx)(`rounded-[inherit]`,i),children:n});p.displayName=`Accordion.ItemHeader`;var m=({asChild:e=!1,children:i,className:c,ref:l,...u})=>{let d=s(e,`button`);return(0,a.jsxs)(n.Accordion.Trigger,{ref:l,"data-spark-component":`accordion-item-trigger`,render:d,className:(0,r.cx)(`group`,`gap-lg min-h-sz-48 relative flex items-center justify-between`,`px-lg py-md text-headline-2 text-on-surface data-[panel-open]:rounded-b-0 w-full rounded-[inherit] text-left`,`hover:enabled:bg-surface-hovered focus:bg-surface-hovered`,`focus-visible:u-outline focus-visible:z-raised focus-visible:outline-hidden`,`disabled:opacity-dim-3 cursor-pointer disabled:cursor-not-allowed`,c),...u,children:[(0,a.jsx)(`div`,{className:`gap-lg flex grow items-center`,children:i}),(0,a.jsx)(t.t,{intent:`neutral`,className:(0,r.cx)(`shrink-0 rotate-0 duration-100 ease-in motion-reduce:transition-none`,`group-data-[panel-open]:rotate-180`),size:`sm`,children:(0,a.jsx)(o.ArrowHorizontalDown,{})})]})};m.displayName=`Accordion.ItemTrigger`;var h=Object.assign(l,{Item:d,ItemHeader:p,ItemTrigger:m,ItemContent:f});h.displayName=`Accordion`,d.displayName=`Item`,p.displayName=`ItemHeader`,m.displayName=`Accordion.Trigger`,f.displayName=`Accordion.Content`,exports.Accordion=h;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/accordion/useRenderSlot.tsx","../../src/accordion/Accordion.tsx","../../src/accordion/AccordionItem.tsx","../../src/accordion/AccordionItemContent.tsx","../../src/accordion/AccordionItemHeader.tsx","../../src/accordion/AccordionItemTrigger.tsx","../../src/accordion/index.ts"],"sourcesContent":["import { Slot } from '../slot'\n\nexport function useRenderSlot(asChild: boolean, defaultTag: string) {\n const Component = asChild ? Slot : defaultTag\n\n return asChild ? ({ ...props }) => <Component {...props} /> : undefined\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { ComponentProps, createContext, Ref, useContext } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Root>, 'multiple' | 'render'>\n\nexport interface AccordionProps extends ExtentedZagInterface {\n /**\n * Change the default rendered element for the one passed as a child, merging their props and behavior.\n */\n asChild?: boolean\n /**\n * Whether the accordion items are disabled\n */\n disabled?: boolean\n /**\n * Whether multiple items can be open at the same time.\n */\n multiple?: boolean\n design?: 'filled' | 'outlined'\n ref?: Ref<HTMLDivElement>\n}\n\nconst AccordionContext = createContext<{\n design: 'filled' | 'outlined'\n} | null>(null)\n\nexport const Accordion = ({\n asChild = false,\n children,\n design = 'outlined',\n hiddenUntilFound = true,\n multiple = false,\n className,\n ref,\n ...props\n}: AccordionProps) => {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <AccordionContext value={{ design }}>\n <BaseAccordion.Root\n data-spark-component=\"accordion\"\n ref={ref}\n multiple={multiple}\n hiddenUntilFound={hiddenUntilFound}\n className={cx('bg-surface h-fit rounded-lg', className)}\n render={renderSlot}\n {...props}\n >\n {children}\n </BaseAccordion.Root>\n </AccordionContext>\n )\n}\n\nAccordion.displayName = 'Accordion'\n\nexport const useAccordionContext = () => {\n const context = useContext(AccordionContext)\n\n if (!context) {\n throw Error('useAccordionContext must be used within a Accordion provider')\n }\n\n return context\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useAccordionContext } from './Accordion'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Item>, 'render'>\n\nexport interface AccordionItemProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Groups an accordion header with the corresponding panel. Renders a <div> element.\n */\nexport const Item = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemProps) => {\n const accordion = useAccordionContext()\n\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Item\n ref={ref}\n data-spark-component=\"accordion-item\"\n render={renderSlot}\n className={cx(\n 'relative first:rounded-t-lg last:rounded-b-lg',\n 'not-last:border-b-0',\n { 'border-sm border-outline': accordion.design === 'outlined' },\n className\n )}\n {...props}\n >\n {children}\n </BaseAccordion.Item>\n )\n}\n\nItem.displayName = 'Accordion.Item'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Panel>, 'render'>\n\nexport interface AccordionItemContentProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\nexport const ItemContent = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemContentProps) => {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Panel\n ref={ref}\n data-spark-component=\"accordion-item-content\"\n className={cx(\n '[&>:first-child]:p-lg overflow-hidden',\n 'h-[var(--accordion-panel-height)] transition-all duration-200 data-[ending-style]:h-0 data-[starting-style]:h-0',\n 'text-body-1 text-on-surface',\n className\n )}\n render={renderSlot}\n {...props}\n >\n {children}\n </BaseAccordion.Panel>\n )\n}\n\nItemContent.displayName = 'Accordion.ItemContent'\n","import { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Slot } from '../slot'\n\nexport interface AccordionItemHeaderProps extends ComponentProps<'h3'> {\n asChild?: boolean\n ref?: Ref<HTMLHeadingElement>\n}\n\nexport const ItemHeader = ({\n asChild = false,\n children,\n className,\n ref,\n}: AccordionItemHeaderProps) => {\n const Component = asChild ? Slot : 'h3'\n\n return (\n <Component\n ref={ref}\n data-spark-component=\"accordion-item-header\"\n className={cx('rounded-[inherit]', className)}\n >\n {children}\n </Component>\n )\n}\n\nItemHeader.displayName = 'Accordion.ItemHeader'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { ArrowHorizontalDown } from '@spark-ui/icons/ArrowHorizontalDown'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Trigger>, 'render'>\n\nexport interface AccordionItemTriggerProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLButtonElement>\n}\n\nexport const ItemTrigger = ({\n asChild = false,\n children,\n className,\n ref,\n ...props\n}: AccordionItemTriggerProps) => {\n const renderSlot = useRenderSlot(asChild, 'button')\n\n return (\n <BaseAccordion.Trigger\n ref={ref}\n data-spark-component=\"accordion-item-trigger\"\n render={renderSlot}\n className={cx(\n 'group',\n 'gap-lg min-h-sz-48 relative flex items-center justify-between',\n 'px-lg py-md text-headline-2 text-on-surface data-[panel-open]:rounded-b-0 w-full rounded-[inherit] text-left',\n 'hover:enabled:bg-surface-hovered focus:bg-surface-hovered',\n 'focus-visible:u-outline focus-visible:z-raised focus-visible:outline-hidden',\n 'disabled:opacity-dim-3 cursor-pointer disabled:cursor-not-allowed',\n className\n )}\n {...props}\n >\n <div className=\"gap-lg flex grow items-center\">{children}</div>\n <Icon\n intent=\"neutral\"\n className={cx(\n 'shrink-0 rotate-0 duration-100 ease-in motion-reduce:transition-none',\n 'group-data-[panel-open]:rotate-180'\n )}\n size=\"sm\"\n >\n <ArrowHorizontalDown />\n </Icon>\n </BaseAccordion.Trigger>\n )\n}\n\nItemTrigger.displayName = 'Accordion.ItemTrigger'\n","import { Accordion as Root } from './Accordion'\nimport { Item } from './AccordionItem'\nimport { ItemContent } from './AccordionItemContent'\nimport { ItemHeader } from './AccordionItemHeader'\nimport { ItemTrigger } from './AccordionItemTrigger'\n\n/**\n * A vertically stacked set of expandable sections that allow users to show and hide content.\n */\nexport const Accordion: typeof Root & {\n Item: typeof Item\n ItemHeader: typeof ItemHeader\n ItemTrigger: typeof ItemTrigger\n ItemContent: typeof ItemContent\n} = Object.assign(Root, {\n Item,\n ItemHeader,\n ItemTrigger,\n ItemContent,\n})\n\nAccordion.displayName = 'Accordion'\nItem.displayName = 'Item'\nItemHeader.displayName = 'ItemHeader'\nItemTrigger.displayName = 'Accordion.Trigger'\nItemContent.displayName = 'Accordion.Content'\n\nexport { type AccordionProps } from './Accordion'\nexport { type AccordionItemHeaderProps } from './AccordionItemHeader'\nexport { type AccordionItemContentProps } from './AccordionItemContent'\nexport { type AccordionItemTriggerProps } from './AccordionItemTrigger'\n"],"mappings":"2VAEA,SAAgB,EAAc,EAAkB,EAAoB,CAClE,IAAM,EAAY,EAAU,EAAA,KAAO,EAEnC,OAAO,GAAW,CAAE,GAAG,MAAY,EAAA,EAAA,KAAC,EAAD,CAAW,GAAI,EAAS,CAAA,CAAG,IAAA,GCoBhE,IAAM,GAAA,EAAA,EAAA,eAEI,KAAK,CAEF,GAAa,CACxB,UAAU,GACV,WACA,SAAS,WACT,mBAAmB,GACnB,WAAW,GACX,YACA,MACA,GAAG,KACiB,CACpB,IAAM,EAAa,EAAc,EAAS,MAAM,CAEhD,OACE,EAAA,EAAA,KAAC,EAAD,CAAkB,MAAO,CAAE,SAAQ,WACjC,EAAA,EAAA,KAAC,EAAA,UAAc,KAAf,CACE,uBAAqB,YAChB,MACK,WACQ,mBAClB,WAAA,EAAA,EAAA,IAAc,8BAA+B,EAAU,CACvD,OAAQ,EACR,GAAI,EAEH,WACkB,CAAA,CACJ,CAAA,EAIvB,EAAU,YAAc,YAExB,IAAa,MAA4B,CACvC,IAAM,GAAA,EAAA,EAAA,YAAqB,EAAiB,CAE5C,GAAI,CAAC,EACH,MAAM,MAAM,+DAA+D,CAG7E,OAAO,GClDI,GAAQ,CACnB,UAAU,GACV,YACA,WACA,MACA,GAAG,KACqB,CACxB,IAAM,EAAY,GAAqB,CAEjC,EAAa,EAAc,EAAS,MAAM,CAEhD,OACE,EAAA,EAAA,KAAC,EAAA,UAAc,KAAf,CACO,MACL,uBAAqB,iBACrB,OAAQ,EACR,WAAA,EAAA,EAAA,IACE,gDACA,sBACA,CAAE,2BAA4B,EAAU,SAAW,WAAY,CAC/D,EACD,CACD,GAAI,EAEH,WACkB,CAAA,EAIzB,EAAK,YAAc,iBCjCnB,IAAa,GAAe,CAC1B,UAAU,GACV,YACA,WACA,MACA,GAAG,KAC4B,CAC/B,IAAM,EAAa,EAAc,EAAS,MAAM,CAEhD,OACE,EAAA,EAAA,KAAC,EAAA,UAAc,MAAf,CACO,MACL,uBAAqB,yBACrB,WAAA,EAAA,EAAA,IACE,wCACA,kHACA,8BACA,EACD,CACD,OAAQ,EACR,GAAI,EAEH,WACmB,CAAA,EAI1B,EAAY,YAAc,wBC9B1B,IAAa,GAAc,CACzB,UAAU,GACV,WACA,YACA,UAKE,EAAA,EAAA,KAHgB,EAAU,EAAA,KAAO,KAGjC,CACO,MACL,uBAAqB,wBACrB,WAAA,EAAA,EAAA,IAAc,oBAAqB,EAAU,CAE5C,WACS,CAAA,CAIhB,EAAW,YAAc,uBCdzB,IAAa,GAAe,CAC1B,UAAU,GACV,WACA,YACA,MACA,GAAG,KAC4B,CAC/B,IAAM,EAAa,EAAc,EAAS,SAAS,CAEnD,OACE,EAAA,EAAA,MAAC,EAAA,UAAc,QAAf,CACO,MACL,uBAAqB,yBACrB,OAAQ,EACR,WAAA,EAAA,EAAA,IACE,QACA,gEACA,+GACA,4DACA,8EACA,oEACA,EACD,CACD,GAAI,WAbN,EAeE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,gCAAiC,WAAe,CAAA,EAC/D,EAAA,EAAA,KAAC,EAAA,EAAD,CACE,OAAO,UACP,WAAA,EAAA,EAAA,IACE,uEACA,qCACD,CACD,KAAK,eAEL,EAAA,EAAA,KAAC,EAAA,oBAAD,EAAuB,CAAA,CAClB,CAAA,CACe,IAI5B,EAAY,YAAc,wBC9C1B,IAAa,EAKT,OAAO,OAAO,EAAM,CACtB,OACA,aACA,cACA,cACD,CAAC,CAEF,EAAU,YAAc,YACxB,EAAK,YAAc,OACnB,EAAW,YAAc,aACzB,EAAY,YAAc,oBAC1B,EAAY,YAAc"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/accordion/useRenderSlot.tsx","../../src/accordion/Accordion.tsx","../../src/accordion/AccordionItem.tsx","../../src/accordion/AccordionItemContent.tsx","../../src/accordion/AccordionItemHeader.tsx","../../src/accordion/AccordionItemTrigger.tsx","../../src/accordion/index.ts"],"sourcesContent":["import { Slot } from '../slot'\n\nexport function useRenderSlot(asChild: boolean, defaultTag: string) {\n const Component = asChild ? Slot : defaultTag\n\n return asChild ? ({ ...props }) => <Component {...props} /> : undefined\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { ComponentProps, createContext, Ref, useContext } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype BaseAccordionRootProps = ComponentProps<typeof BaseAccordion.Root<string | string[]>>\n\ntype ExtentedBaseUIInterface = Omit<\n BaseAccordionRootProps,\n 'multiple' | 'render' | 'value' | 'defaultValue' | 'onValueChange'\n>\n\nexport interface AccordionProps extends ExtentedBaseUIInterface {\n /**\n * Change the default rendered element for the one passed as a child, merging their props and behavior.\n */\n asChild?: boolean\n /**\n * Whether the accordion items are disabled\n */\n disabled?: boolean\n /**\n * Whether multiple items can be open at the same time.\n */\n multiple?: boolean\n design?: 'filled' | 'outlined'\n ref?: Ref<HTMLDivElement>\n /**\n * The controlled value (always an array of strings)\n */\n value?: string[]\n /**\n * The default value (always an array of strings)\n */\n defaultValue?: string[]\n /**\n * Callback when the value changes (always receives an array of strings)\n */\n onValueChange?: (value: string[]) => void\n}\n\nconst AccordionContext = createContext<{\n design: 'filled' | 'outlined'\n} | null>(null)\n\nexport function Accordion({\n asChild = false,\n children,\n design = 'outlined',\n hiddenUntilFound = true,\n multiple = false,\n className,\n ref,\n value,\n defaultValue,\n onValueChange,\n ...props\n}: AccordionProps) {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n // Wrap the onValueChange to always provide string[]\n const handleValueChange = onValueChange\n ? (newValue: string | string[]) => {\n // Base UI returns string when multiple=false, string[] when multiple=true\n // We normalize to always return string[]\n const normalizedValue = Array.isArray(newValue) ? newValue : [newValue]\n onValueChange(normalizedValue)\n }\n : undefined\n\n return (\n <AccordionContext value={{ design }}>\n <BaseAccordion.Root\n data-spark-component=\"accordion\"\n ref={ref}\n multiple={multiple}\n hiddenUntilFound={hiddenUntilFound}\n className={cx('bg-surface h-fit rounded-lg', className)}\n render={renderSlot}\n value={value as any}\n defaultValue={defaultValue as any}\n onValueChange={handleValueChange as any}\n {...props}\n >\n {children}\n </BaseAccordion.Root>\n </AccordionContext>\n )\n}\n\nAccordion.displayName = 'Accordion'\n\nexport const useAccordionContext = () => {\n const context = useContext(AccordionContext)\n\n if (!context) {\n throw Error('useAccordionContext must be used within a Accordion provider')\n }\n\n return context\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useAccordionContext } from './Accordion'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Item>, 'render'>\n\nexport interface AccordionItemProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Groups an accordion header with the corresponding panel. Renders a <div> element.\n */\nexport const Item = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemProps) => {\n const accordion = useAccordionContext()\n\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Item\n ref={ref}\n data-spark-component=\"accordion-item\"\n render={renderSlot}\n className={cx(\n 'relative first:rounded-t-lg last:rounded-b-lg',\n 'not-last:border-b-0',\n { 'border-sm border-outline': accordion.design === 'outlined' },\n className\n )}\n {...props}\n >\n {children}\n </BaseAccordion.Item>\n )\n}\n\nItem.displayName = 'Accordion.Item'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Panel>, 'render'>\n\nexport interface AccordionItemContentProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\nexport const ItemContent = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemContentProps) => {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Panel\n ref={ref}\n data-spark-component=\"accordion-item-content\"\n className={cx(\n '[&>:first-child]:p-lg overflow-hidden',\n 'h-[var(--accordion-panel-height)] transition-all duration-200 data-[ending-style]:h-0 data-[starting-style]:h-0',\n 'text-body-1 text-on-surface',\n className\n )}\n render={renderSlot}\n {...props}\n >\n {children}\n </BaseAccordion.Panel>\n )\n}\n\nItemContent.displayName = 'Accordion.ItemContent'\n","import { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Slot } from '../slot'\n\nexport interface AccordionItemHeaderProps extends ComponentProps<'h3'> {\n asChild?: boolean\n ref?: Ref<HTMLHeadingElement>\n}\n\nexport const ItemHeader = ({\n asChild = false,\n children,\n className,\n ref,\n}: AccordionItemHeaderProps) => {\n const Component = asChild ? Slot : 'h3'\n\n return (\n <Component\n ref={ref}\n data-spark-component=\"accordion-item-header\"\n className={cx('rounded-[inherit]', className)}\n >\n {children}\n </Component>\n )\n}\n\nItemHeader.displayName = 'Accordion.ItemHeader'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { ArrowHorizontalDown } from '@spark-ui/icons/ArrowHorizontalDown'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Trigger>, 'render'>\n\nexport interface AccordionItemTriggerProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLButtonElement>\n}\n\nexport const ItemTrigger = ({\n asChild = false,\n children,\n className,\n ref,\n ...props\n}: AccordionItemTriggerProps) => {\n const renderSlot = useRenderSlot(asChild, 'button')\n\n return (\n <BaseAccordion.Trigger\n ref={ref}\n data-spark-component=\"accordion-item-trigger\"\n render={renderSlot}\n className={cx(\n 'group',\n 'gap-lg min-h-sz-48 relative flex items-center justify-between',\n 'px-lg py-md text-headline-2 text-on-surface data-[panel-open]:rounded-b-0 w-full rounded-[inherit] text-left',\n 'hover:enabled:bg-surface-hovered focus:bg-surface-hovered',\n 'focus-visible:u-outline focus-visible:z-raised focus-visible:outline-hidden',\n 'disabled:opacity-dim-3 cursor-pointer disabled:cursor-not-allowed',\n className\n )}\n {...props}\n >\n <div className=\"gap-lg flex grow items-center\">{children}</div>\n <Icon\n intent=\"neutral\"\n className={cx(\n 'shrink-0 rotate-0 duration-100 ease-in motion-reduce:transition-none',\n 'group-data-[panel-open]:rotate-180'\n )}\n size=\"sm\"\n >\n <ArrowHorizontalDown />\n </Icon>\n </BaseAccordion.Trigger>\n )\n}\n\nItemTrigger.displayName = 'Accordion.ItemTrigger'\n","import { Accordion as Root } from './Accordion'\nimport { Item } from './AccordionItem'\nimport { ItemContent } from './AccordionItemContent'\nimport { ItemHeader } from './AccordionItemHeader'\nimport { ItemTrigger } from './AccordionItemTrigger'\n\n/**\n * A vertically stacked set of expandable sections that allow users to show and hide content.\n */\nexport const Accordion: typeof Root & {\n Item: typeof Item\n ItemHeader: typeof ItemHeader\n ItemTrigger: typeof ItemTrigger\n ItemContent: typeof ItemContent\n} = Object.assign(Root, {\n Item,\n ItemHeader,\n ItemTrigger,\n ItemContent,\n})\n\nAccordion.displayName = 'Accordion'\nItem.displayName = 'Item'\nItemHeader.displayName = 'ItemHeader'\nItemTrigger.displayName = 'Accordion.Trigger'\nItemContent.displayName = 'Accordion.Content'\n\nexport { type AccordionProps } from './Accordion'\nexport { type AccordionItemHeaderProps } from './AccordionItemHeader'\nexport { type AccordionItemContentProps } from './AccordionItemContent'\nexport { type AccordionItemTriggerProps } from './AccordionItemTrigger'\n"],"mappings":"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"}
|
package/dist/accordion/index.mjs
CHANGED
|
@@ -12,8 +12,11 @@ function l(t, n) {
|
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/accordion/Accordion.tsx
|
|
15
|
-
var u = i(null)
|
|
16
|
-
|
|
15
|
+
var u = i(null);
|
|
16
|
+
function d({ asChild: e = !1, children: t, design: i = "outlined", hiddenUntilFound: a = !0, multiple: s = !1, className: c, ref: d, value: f, defaultValue: p, onValueChange: m, ...h }) {
|
|
17
|
+
let g = l(e, "div"), _ = m ? (e) => {
|
|
18
|
+
m(Array.isArray(e) ? e : [e]);
|
|
19
|
+
} : void 0;
|
|
17
20
|
return /* @__PURE__ */ o(u, {
|
|
18
21
|
value: { design: i },
|
|
19
22
|
children: /* @__PURE__ */ o(n.Root, {
|
|
@@ -22,12 +25,15 @@ var u = i(null), d = ({ asChild: e = !1, children: t, design: i = "outlined", hi
|
|
|
22
25
|
multiple: s,
|
|
23
26
|
hiddenUntilFound: a,
|
|
24
27
|
className: r("bg-surface h-fit rounded-lg", c),
|
|
25
|
-
render:
|
|
26
|
-
|
|
28
|
+
render: g,
|
|
29
|
+
value: f,
|
|
30
|
+
defaultValue: p,
|
|
31
|
+
onValueChange: _,
|
|
32
|
+
...h,
|
|
27
33
|
children: t
|
|
28
34
|
})
|
|
29
35
|
});
|
|
30
|
-
}
|
|
36
|
+
}
|
|
31
37
|
d.displayName = "Accordion";
|
|
32
38
|
var f = () => {
|
|
33
39
|
let e = a(u);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/accordion/useRenderSlot.tsx","../../src/accordion/Accordion.tsx","../../src/accordion/AccordionItem.tsx","../../src/accordion/AccordionItemContent.tsx","../../src/accordion/AccordionItemHeader.tsx","../../src/accordion/AccordionItemTrigger.tsx","../../src/accordion/index.ts"],"sourcesContent":["import { Slot } from '../slot'\n\nexport function useRenderSlot(asChild: boolean, defaultTag: string) {\n const Component = asChild ? Slot : defaultTag\n\n return asChild ? ({ ...props }) => <Component {...props} /> : undefined\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { ComponentProps, createContext, Ref, useContext } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Root>, 'multiple' | 'render'>\n\nexport interface AccordionProps extends ExtentedZagInterface {\n /**\n * Change the default rendered element for the one passed as a child, merging their props and behavior.\n */\n asChild?: boolean\n /**\n * Whether the accordion items are disabled\n */\n disabled?: boolean\n /**\n * Whether multiple items can be open at the same time.\n */\n multiple?: boolean\n design?: 'filled' | 'outlined'\n ref?: Ref<HTMLDivElement>\n}\n\nconst AccordionContext = createContext<{\n design: 'filled' | 'outlined'\n} | null>(null)\n\nexport const Accordion = ({\n asChild = false,\n children,\n design = 'outlined',\n hiddenUntilFound = true,\n multiple = false,\n className,\n ref,\n ...props\n}: AccordionProps) => {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <AccordionContext value={{ design }}>\n <BaseAccordion.Root\n data-spark-component=\"accordion\"\n ref={ref}\n multiple={multiple}\n hiddenUntilFound={hiddenUntilFound}\n className={cx('bg-surface h-fit rounded-lg', className)}\n render={renderSlot}\n {...props}\n >\n {children}\n </BaseAccordion.Root>\n </AccordionContext>\n )\n}\n\nAccordion.displayName = 'Accordion'\n\nexport const useAccordionContext = () => {\n const context = useContext(AccordionContext)\n\n if (!context) {\n throw Error('useAccordionContext must be used within a Accordion provider')\n }\n\n return context\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useAccordionContext } from './Accordion'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Item>, 'render'>\n\nexport interface AccordionItemProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Groups an accordion header with the corresponding panel. Renders a <div> element.\n */\nexport const Item = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemProps) => {\n const accordion = useAccordionContext()\n\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Item\n ref={ref}\n data-spark-component=\"accordion-item\"\n render={renderSlot}\n className={cx(\n 'relative first:rounded-t-lg last:rounded-b-lg',\n 'not-last:border-b-0',\n { 'border-sm border-outline': accordion.design === 'outlined' },\n className\n )}\n {...props}\n >\n {children}\n </BaseAccordion.Item>\n )\n}\n\nItem.displayName = 'Accordion.Item'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Panel>, 'render'>\n\nexport interface AccordionItemContentProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\nexport const ItemContent = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemContentProps) => {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Panel\n ref={ref}\n data-spark-component=\"accordion-item-content\"\n className={cx(\n '[&>:first-child]:p-lg overflow-hidden',\n 'h-[var(--accordion-panel-height)] transition-all duration-200 data-[ending-style]:h-0 data-[starting-style]:h-0',\n 'text-body-1 text-on-surface',\n className\n )}\n render={renderSlot}\n {...props}\n >\n {children}\n </BaseAccordion.Panel>\n )\n}\n\nItemContent.displayName = 'Accordion.ItemContent'\n","import { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Slot } from '../slot'\n\nexport interface AccordionItemHeaderProps extends ComponentProps<'h3'> {\n asChild?: boolean\n ref?: Ref<HTMLHeadingElement>\n}\n\nexport const ItemHeader = ({\n asChild = false,\n children,\n className,\n ref,\n}: AccordionItemHeaderProps) => {\n const Component = asChild ? Slot : 'h3'\n\n return (\n <Component\n ref={ref}\n data-spark-component=\"accordion-item-header\"\n className={cx('rounded-[inherit]', className)}\n >\n {children}\n </Component>\n )\n}\n\nItemHeader.displayName = 'Accordion.ItemHeader'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { ArrowHorizontalDown } from '@spark-ui/icons/ArrowHorizontalDown'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Trigger>, 'render'>\n\nexport interface AccordionItemTriggerProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLButtonElement>\n}\n\nexport const ItemTrigger = ({\n asChild = false,\n children,\n className,\n ref,\n ...props\n}: AccordionItemTriggerProps) => {\n const renderSlot = useRenderSlot(asChild, 'button')\n\n return (\n <BaseAccordion.Trigger\n ref={ref}\n data-spark-component=\"accordion-item-trigger\"\n render={renderSlot}\n className={cx(\n 'group',\n 'gap-lg min-h-sz-48 relative flex items-center justify-between',\n 'px-lg py-md text-headline-2 text-on-surface data-[panel-open]:rounded-b-0 w-full rounded-[inherit] text-left',\n 'hover:enabled:bg-surface-hovered focus:bg-surface-hovered',\n 'focus-visible:u-outline focus-visible:z-raised focus-visible:outline-hidden',\n 'disabled:opacity-dim-3 cursor-pointer disabled:cursor-not-allowed',\n className\n )}\n {...props}\n >\n <div className=\"gap-lg flex grow items-center\">{children}</div>\n <Icon\n intent=\"neutral\"\n className={cx(\n 'shrink-0 rotate-0 duration-100 ease-in motion-reduce:transition-none',\n 'group-data-[panel-open]:rotate-180'\n )}\n size=\"sm\"\n >\n <ArrowHorizontalDown />\n </Icon>\n </BaseAccordion.Trigger>\n )\n}\n\nItemTrigger.displayName = 'Accordion.ItemTrigger'\n","import { Accordion as Root } from './Accordion'\nimport { Item } from './AccordionItem'\nimport { ItemContent } from './AccordionItemContent'\nimport { ItemHeader } from './AccordionItemHeader'\nimport { ItemTrigger } from './AccordionItemTrigger'\n\n/**\n * A vertically stacked set of expandable sections that allow users to show and hide content.\n */\nexport const Accordion: typeof Root & {\n Item: typeof Item\n ItemHeader: typeof ItemHeader\n ItemTrigger: typeof ItemTrigger\n ItemContent: typeof ItemContent\n} = Object.assign(Root, {\n Item,\n ItemHeader,\n ItemTrigger,\n ItemContent,\n})\n\nAccordion.displayName = 'Accordion'\nItem.displayName = 'Item'\nItemHeader.displayName = 'ItemHeader'\nItemTrigger.displayName = 'Accordion.Trigger'\nItemContent.displayName = 'Accordion.Content'\n\nexport { type AccordionProps } from './Accordion'\nexport { type AccordionItemHeaderProps } from './AccordionItemHeader'\nexport { type AccordionItemContentProps } from './AccordionItemContent'\nexport { type AccordionItemTriggerProps } from './AccordionItemTrigger'\n"],"mappings":";;;;;;;;AAEA,SAAgB,EAAc,GAAkB,GAAoB;CAClE,IAAM,IAAY,IAAU,IAAO;AAEnC,QAAO,KAAW,EAAE,GAAG,QAAY,kBAAC,GAAD,EAAW,GAAI,GAAS,CAAA,GAAG,KAAA;;;;ACoBhE,IAAM,IAAmB,EAEf,KAAK,EAEF,KAAa,EACxB,aAAU,IACV,aACA,YAAS,YACT,sBAAmB,IACnB,cAAW,IACX,cACA,QACA,GAAG,QACiB;CACpB,IAAM,IAAa,EAAc,GAAS,MAAM;AAEhD,QACE,kBAAC,GAAD;EAAkB,OAAO,EAAE,WAAQ;YACjC,kBAAC,EAAc,MAAf;GACE,wBAAqB;GAChB;GACK;GACQ;GAClB,WAAW,EAAG,+BAA+B,EAAU;GACvD,QAAQ;GACR,GAAI;GAEH;GACkB,CAAA;EACJ,CAAA;;AAIvB,EAAU,cAAc;AAExB,IAAa,UAA4B;CACvC,IAAM,IAAU,EAAW,EAAiB;AAE5C,KAAI,CAAC,EACH,OAAM,MAAM,+DAA+D;AAG7E,QAAO;GClDI,KAAQ,EACnB,aAAU,IACV,cACA,aACA,QACA,GAAG,QACqB;CACxB,IAAM,IAAY,GAAqB,EAEjC,IAAa,EAAc,GAAS,MAAM;AAEhD,QACE,kBAAC,EAAc,MAAf;EACO;EACL,wBAAqB;EACrB,QAAQ;EACR,WAAW,EACT,iDACA,uBACA,EAAE,4BAA4B,EAAU,WAAW,YAAY,EAC/D,EACD;EACD,GAAI;EAEH;EACkB,CAAA;;AAIzB,EAAK,cAAc;;;ACjCnB,IAAa,KAAe,EAC1B,aAAU,IACV,cACA,aACA,QACA,GAAG,QAC4B;CAC/B,IAAM,IAAa,EAAc,GAAS,MAAM;AAEhD,QACE,kBAAC,EAAc,OAAf;EACO;EACL,wBAAqB;EACrB,WAAW,EACT,yCACA,mHACA,+BACA,EACD;EACD,QAAQ;EACR,GAAI;EAEH;EACmB,CAAA;;AAI1B,EAAY,cAAc;;;AC9B1B,IAAa,KAAc,EACzB,aAAU,IACV,aACA,cACA,aAKE,kBAHgB,IAAU,IAAO,MAGjC;CACO;CACL,wBAAqB;CACrB,WAAW,EAAG,qBAAqB,EAAU;CAE5C;CACS,CAAA;AAIhB,EAAW,cAAc;;;ACdzB,IAAa,KAAe,EAC1B,aAAU,IACV,aACA,cACA,QACA,GAAG,QAC4B;CAC/B,IAAM,IAAa,EAAc,GAAS,SAAS;AAEnD,QACE,kBAAC,EAAc,SAAf;EACO;EACL,wBAAqB;EACrB,QAAQ;EACR,WAAW,EACT,SACA,iEACA,gHACA,6DACA,+EACA,qEACA,EACD;EACD,GAAI;YAbN,CAeE,kBAAC,OAAD;GAAK,WAAU;GAAiC;GAAe,CAAA,EAC/D,kBAAC,GAAD;GACE,QAAO;GACP,WAAW,EACT,wEACA,qCACD;GACD,MAAK;aAEL,kBAAC,GAAD,EAAuB,CAAA;GAClB,CAAA,CACe;;;AAI5B,EAAY,cAAc;;;AC9C1B,IAAa,IAKT,OAAO,OAAO,GAAM;CACtB;CACA;CACA;CACA;CACD,CAAC;AAEF,EAAU,cAAc,aACxB,EAAK,cAAc,QACnB,EAAW,cAAc,cACzB,EAAY,cAAc,qBAC1B,EAAY,cAAc"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/accordion/useRenderSlot.tsx","../../src/accordion/Accordion.tsx","../../src/accordion/AccordionItem.tsx","../../src/accordion/AccordionItemContent.tsx","../../src/accordion/AccordionItemHeader.tsx","../../src/accordion/AccordionItemTrigger.tsx","../../src/accordion/index.ts"],"sourcesContent":["import { Slot } from '../slot'\n\nexport function useRenderSlot(asChild: boolean, defaultTag: string) {\n const Component = asChild ? Slot : defaultTag\n\n return asChild ? ({ ...props }) => <Component {...props} /> : undefined\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { ComponentProps, createContext, Ref, useContext } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype BaseAccordionRootProps = ComponentProps<typeof BaseAccordion.Root<string | string[]>>\n\ntype ExtentedBaseUIInterface = Omit<\n BaseAccordionRootProps,\n 'multiple' | 'render' | 'value' | 'defaultValue' | 'onValueChange'\n>\n\nexport interface AccordionProps extends ExtentedBaseUIInterface {\n /**\n * Change the default rendered element for the one passed as a child, merging their props and behavior.\n */\n asChild?: boolean\n /**\n * Whether the accordion items are disabled\n */\n disabled?: boolean\n /**\n * Whether multiple items can be open at the same time.\n */\n multiple?: boolean\n design?: 'filled' | 'outlined'\n ref?: Ref<HTMLDivElement>\n /**\n * The controlled value (always an array of strings)\n */\n value?: string[]\n /**\n * The default value (always an array of strings)\n */\n defaultValue?: string[]\n /**\n * Callback when the value changes (always receives an array of strings)\n */\n onValueChange?: (value: string[]) => void\n}\n\nconst AccordionContext = createContext<{\n design: 'filled' | 'outlined'\n} | null>(null)\n\nexport function Accordion({\n asChild = false,\n children,\n design = 'outlined',\n hiddenUntilFound = true,\n multiple = false,\n className,\n ref,\n value,\n defaultValue,\n onValueChange,\n ...props\n}: AccordionProps) {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n // Wrap the onValueChange to always provide string[]\n const handleValueChange = onValueChange\n ? (newValue: string | string[]) => {\n // Base UI returns string when multiple=false, string[] when multiple=true\n // We normalize to always return string[]\n const normalizedValue = Array.isArray(newValue) ? newValue : [newValue]\n onValueChange(normalizedValue)\n }\n : undefined\n\n return (\n <AccordionContext value={{ design }}>\n <BaseAccordion.Root\n data-spark-component=\"accordion\"\n ref={ref}\n multiple={multiple}\n hiddenUntilFound={hiddenUntilFound}\n className={cx('bg-surface h-fit rounded-lg', className)}\n render={renderSlot}\n value={value as any}\n defaultValue={defaultValue as any}\n onValueChange={handleValueChange as any}\n {...props}\n >\n {children}\n </BaseAccordion.Root>\n </AccordionContext>\n )\n}\n\nAccordion.displayName = 'Accordion'\n\nexport const useAccordionContext = () => {\n const context = useContext(AccordionContext)\n\n if (!context) {\n throw Error('useAccordionContext must be used within a Accordion provider')\n }\n\n return context\n}\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useAccordionContext } from './Accordion'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Item>, 'render'>\n\nexport interface AccordionItemProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Groups an accordion header with the corresponding panel. Renders a <div> element.\n */\nexport const Item = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemProps) => {\n const accordion = useAccordionContext()\n\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Item\n ref={ref}\n data-spark-component=\"accordion-item\"\n render={renderSlot}\n className={cx(\n 'relative first:rounded-t-lg last:rounded-b-lg',\n 'not-last:border-b-0',\n { 'border-sm border-outline': accordion.design === 'outlined' },\n className\n )}\n {...props}\n >\n {children}\n </BaseAccordion.Item>\n )\n}\n\nItem.displayName = 'Accordion.Item'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Panel>, 'render'>\n\nexport interface AccordionItemContentProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n}\n\nexport const ItemContent = ({\n asChild = false,\n className,\n children,\n ref,\n ...props\n}: AccordionItemContentProps) => {\n const renderSlot = useRenderSlot(asChild, 'div')\n\n return (\n <BaseAccordion.Panel\n ref={ref}\n data-spark-component=\"accordion-item-content\"\n className={cx(\n '[&>:first-child]:p-lg overflow-hidden',\n 'h-[var(--accordion-panel-height)] transition-all duration-200 data-[ending-style]:h-0 data-[starting-style]:h-0',\n 'text-body-1 text-on-surface',\n className\n )}\n render={renderSlot}\n {...props}\n >\n {children}\n </BaseAccordion.Panel>\n )\n}\n\nItemContent.displayName = 'Accordion.ItemContent'\n","import { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Slot } from '../slot'\n\nexport interface AccordionItemHeaderProps extends ComponentProps<'h3'> {\n asChild?: boolean\n ref?: Ref<HTMLHeadingElement>\n}\n\nexport const ItemHeader = ({\n asChild = false,\n children,\n className,\n ref,\n}: AccordionItemHeaderProps) => {\n const Component = asChild ? Slot : 'h3'\n\n return (\n <Component\n ref={ref}\n data-spark-component=\"accordion-item-header\"\n className={cx('rounded-[inherit]', className)}\n >\n {children}\n </Component>\n )\n}\n\nItemHeader.displayName = 'Accordion.ItemHeader'\n","import { Accordion as BaseAccordion } from '@base-ui/react/accordion'\nimport { ArrowHorizontalDown } from '@spark-ui/icons/ArrowHorizontalDown'\nimport { cx } from 'class-variance-authority'\nimport { type ComponentProps, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { useRenderSlot } from './useRenderSlot'\n\ntype ExtentedZagInterface = Omit<ComponentProps<typeof BaseAccordion.Trigger>, 'render'>\n\nexport interface AccordionItemTriggerProps extends ExtentedZagInterface {\n asChild?: boolean\n ref?: Ref<HTMLButtonElement>\n}\n\nexport const ItemTrigger = ({\n asChild = false,\n children,\n className,\n ref,\n ...props\n}: AccordionItemTriggerProps) => {\n const renderSlot = useRenderSlot(asChild, 'button')\n\n return (\n <BaseAccordion.Trigger\n ref={ref}\n data-spark-component=\"accordion-item-trigger\"\n render={renderSlot}\n className={cx(\n 'group',\n 'gap-lg min-h-sz-48 relative flex items-center justify-between',\n 'px-lg py-md text-headline-2 text-on-surface data-[panel-open]:rounded-b-0 w-full rounded-[inherit] text-left',\n 'hover:enabled:bg-surface-hovered focus:bg-surface-hovered',\n 'focus-visible:u-outline focus-visible:z-raised focus-visible:outline-hidden',\n 'disabled:opacity-dim-3 cursor-pointer disabled:cursor-not-allowed',\n className\n )}\n {...props}\n >\n <div className=\"gap-lg flex grow items-center\">{children}</div>\n <Icon\n intent=\"neutral\"\n className={cx(\n 'shrink-0 rotate-0 duration-100 ease-in motion-reduce:transition-none',\n 'group-data-[panel-open]:rotate-180'\n )}\n size=\"sm\"\n >\n <ArrowHorizontalDown />\n </Icon>\n </BaseAccordion.Trigger>\n )\n}\n\nItemTrigger.displayName = 'Accordion.ItemTrigger'\n","import { Accordion as Root } from './Accordion'\nimport { Item } from './AccordionItem'\nimport { ItemContent } from './AccordionItemContent'\nimport { ItemHeader } from './AccordionItemHeader'\nimport { ItemTrigger } from './AccordionItemTrigger'\n\n/**\n * A vertically stacked set of expandable sections that allow users to show and hide content.\n */\nexport const Accordion: typeof Root & {\n Item: typeof Item\n ItemHeader: typeof ItemHeader\n ItemTrigger: typeof ItemTrigger\n ItemContent: typeof ItemContent\n} = Object.assign(Root, {\n Item,\n ItemHeader,\n ItemTrigger,\n ItemContent,\n})\n\nAccordion.displayName = 'Accordion'\nItem.displayName = 'Item'\nItemHeader.displayName = 'ItemHeader'\nItemTrigger.displayName = 'Accordion.Trigger'\nItemContent.displayName = 'Accordion.Content'\n\nexport { type AccordionProps } from './Accordion'\nexport { type AccordionItemHeaderProps } from './AccordionItemHeader'\nexport { type AccordionItemContentProps } from './AccordionItemContent'\nexport { type AccordionItemTriggerProps } from './AccordionItemTrigger'\n"],"mappings":";;;;;;;;AAEA,SAAgB,EAAc,GAAkB,GAAoB;CAClE,IAAM,IAAY,IAAU,IAAO;AAEnC,QAAO,KAAW,EAAE,GAAG,QAAY,kBAAC,GAAD,EAAW,GAAI,GAAS,CAAA,GAAG,KAAA;;;;ACqChE,IAAM,IAAmB,EAEf,KAAK;AAEf,SAAgB,EAAU,EACxB,aAAU,IACV,aACA,YAAS,YACT,sBAAmB,IACnB,cAAW,IACX,cACA,QACA,UACA,iBACA,kBACA,GAAG,KACc;CACjB,IAAM,IAAa,EAAc,GAAS,MAAM,EAG1C,IAAoB,KACrB,MAAgC;AAI/B,IADwB,MAAM,QAAQ,EAAS,GAAG,IAAW,CAAC,EAAS,CACzC;KAEhC,KAAA;AAEJ,QACE,kBAAC,GAAD;EAAkB,OAAO,EAAE,WAAQ;YACjC,kBAAC,EAAc,MAAf;GACE,wBAAqB;GAChB;GACK;GACQ;GAClB,WAAW,EAAG,+BAA+B,EAAU;GACvD,QAAQ;GACD;GACO;GACd,eAAe;GACf,GAAI;GAEH;GACkB,CAAA;EACJ,CAAA;;AAIvB,EAAU,cAAc;AAExB,IAAa,UAA4B;CACvC,IAAM,IAAU,EAAW,EAAiB;AAE5C,KAAI,CAAC,EACH,OAAM,MAAM,+DAA+D;AAG7E,QAAO;GCnFI,KAAQ,EACnB,aAAU,IACV,cACA,aACA,QACA,GAAG,QACqB;CACxB,IAAM,IAAY,GAAqB,EAEjC,IAAa,EAAc,GAAS,MAAM;AAEhD,QACE,kBAAC,EAAc,MAAf;EACO;EACL,wBAAqB;EACrB,QAAQ;EACR,WAAW,EACT,iDACA,uBACA,EAAE,4BAA4B,EAAU,WAAW,YAAY,EAC/D,EACD;EACD,GAAI;EAEH;EACkB,CAAA;;AAIzB,EAAK,cAAc;;;ACjCnB,IAAa,KAAe,EAC1B,aAAU,IACV,cACA,aACA,QACA,GAAG,QAC4B;CAC/B,IAAM,IAAa,EAAc,GAAS,MAAM;AAEhD,QACE,kBAAC,EAAc,OAAf;EACO;EACL,wBAAqB;EACrB,WAAW,EACT,yCACA,mHACA,+BACA,EACD;EACD,QAAQ;EACR,GAAI;EAEH;EACmB,CAAA;;AAI1B,EAAY,cAAc;;;AC9B1B,IAAa,KAAc,EACzB,aAAU,IACV,aACA,cACA,aAKE,kBAHgB,IAAU,IAAO,MAGjC;CACO;CACL,wBAAqB;CACrB,WAAW,EAAG,qBAAqB,EAAU;CAE5C;CACS,CAAA;AAIhB,EAAW,cAAc;;;ACdzB,IAAa,KAAe,EAC1B,aAAU,IACV,aACA,cACA,QACA,GAAG,QAC4B;CAC/B,IAAM,IAAa,EAAc,GAAS,SAAS;AAEnD,QACE,kBAAC,EAAc,SAAf;EACO;EACL,wBAAqB;EACrB,QAAQ;EACR,WAAW,EACT,SACA,iEACA,gHACA,6DACA,+EACA,qEACA,EACD;EACD,GAAI;YAbN,CAeE,kBAAC,OAAD;GAAK,WAAU;GAAiC;GAAe,CAAA,EAC/D,kBAAC,GAAD;GACE,QAAO;GACP,WAAW,EACT,wEACA,qCACD;GACD,MAAK;aAEL,kBAAC,GAAD,EAAuB,CAAA;GAClB,CAAA,CACe;;;AAI5B,EAAY,cAAc;;;AC9C1B,IAAa,IAKT,OAAO,OAAO,GAAM;CACtB;CACA;CACA;CACA;CACD,CAAC;AAEF,EAAU,cAAc,aACxB,EAAK,cAAc,QACnB,EAAW,cAAc,cACzB,EAAY,cAAc,qBAC1B,EAAY,cAAc"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { RadioGroup } from '@base-ui/react/radio-group';
|
|
2
1
|
import { ComponentProps, Ref } from 'react';
|
|
3
2
|
import { SegmentedControlStylesProps } from './SegmentedControl.styles';
|
|
4
|
-
export interface SegmentedControlProps extends Omit<ComponentProps<
|
|
3
|
+
export interface SegmentedControlProps extends Omit<ComponentProps<'div'>, 'onValueChange'>, SegmentedControlStylesProps {
|
|
5
4
|
/**
|
|
6
5
|
* The controlled selected value.
|
|
7
6
|
*/
|
|
@@ -14,9 +13,22 @@ export interface SegmentedControlProps extends Omit<ComponentProps<typeof RadioG
|
|
|
14
13
|
* Callback fired when the selected value changes.
|
|
15
14
|
*/
|
|
16
15
|
onValueChange?: (value: string) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Number of items per row in multi-row layout.
|
|
18
|
+
* When undefined, items display in a single row (default behavior).
|
|
19
|
+
* @default undefined
|
|
20
|
+
* @example
|
|
21
|
+
* // Create 3-column grid with wrapping rows
|
|
22
|
+
* <SegmentedControl rowLength={3}>
|
|
23
|
+
*/
|
|
24
|
+
rowLength?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The name attribute for the radio group (used in form submissions).
|
|
27
|
+
*/
|
|
28
|
+
name?: string;
|
|
17
29
|
ref?: Ref<HTMLDivElement>;
|
|
18
30
|
}
|
|
19
31
|
export declare const SegmentedControl: {
|
|
20
|
-
({ value, defaultValue, onValueChange, className, children, ref, ...rest }: SegmentedControlProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
({ value, defaultValue, onValueChange, className, children, rowLength, name: nameProp, ref, ...rest }: SegmentedControlProps): import("react/jsx-runtime").JSX.Element;
|
|
21
33
|
displayName: string;
|
|
22
34
|
};
|
|
@@ -2,6 +2,10 @@ import { RefObject } from 'react';
|
|
|
2
2
|
export interface SegmentedControlContextInterface {
|
|
3
3
|
checkedValue: string | null;
|
|
4
4
|
containerRef: RefObject<HTMLDivElement | null>;
|
|
5
|
+
onValueChange: (value: string) => void;
|
|
6
|
+
name?: string;
|
|
7
|
+
rowLength?: number;
|
|
8
|
+
itemValues: string[];
|
|
5
9
|
}
|
|
6
10
|
export declare const SegmentedControlContext: import('react').Context<SegmentedControlContextInterface>;
|
|
7
11
|
export declare const useSegmentedControlContext: () => SegmentedControlContextInterface;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Radio } from '@base-ui/react/radio';
|
|
2
1
|
import { ComponentProps, Ref } from 'react';
|
|
3
|
-
export interface SegmentedControlItemProps extends Omit<ComponentProps<
|
|
2
|
+
export interface SegmentedControlItemProps extends Omit<ComponentProps<'button'>, 'value' | 'onClick'> {
|
|
4
3
|
/**
|
|
5
4
|
* A unique value that identifies this item within the segmented control.
|
|
6
5
|
*/
|
|
@@ -10,7 +9,7 @@ export interface SegmentedControlItemProps extends Omit<ComponentProps<typeof Ra
|
|
|
10
9
|
* @default false
|
|
11
10
|
*/
|
|
12
11
|
disabled?: boolean;
|
|
13
|
-
ref?: Ref<
|
|
12
|
+
ref?: Ref<HTMLButtonElement>;
|
|
14
13
|
}
|
|
15
14
|
/** A selectable item in the segmented control. Renders a <button> element. */
|
|
16
15
|
export declare const SegmentedControlItem: {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`../chunk-C91j1N6u.js`);let e=require(`class-variance-authority`),t=require(`react`),n=require(`react/jsx-runtime`),r=require(`@spark-ui/hooks/use-merge-refs`),i=require(`@spark-ui/components/form-field`)
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`../chunk-C91j1N6u.js`);let e=require(`class-variance-authority`),t=require(`react`),n=require(`react/jsx-runtime`),r=require(`@spark-ui/hooks/use-merge-refs`),i=require(`@spark-ui/components/form-field`);var a=(0,e.cva)([`default:self-start`,`group inline-flex flex-wrap`,`relative items-stretch min-w-max`,`rounded-xl p-sm`,`bg-surface border-sm border-outline`]),o=(0,e.cva)([`relative z-raised min-h-sz-44 focus-visible:outline-none`,`flex flex-auto items-center justify-center gap-md`,`default:px-lg default:py-md`,`rounded-[20px]`,`cursor-pointer select-none`,`font-medium`,`transition-colors duration-150`,`outline-none`,`focus-visible:u-outline`,`data-disabled:cursor-not-allowed data-disabled:opacity-dim-3`,`data-checked:text-on-support-container`,`data-checked:[&>[data-spark-segmented-control-text]]:[text-shadow:0.35px_0_currentColor,-0.35px_0_currentColor]`]),s=(0,e.cva)([`absolute z-base`,`rounded-[20px]`,`bg-support-container border-md border-support`,`group-has-focus-visible:border-focus`,`transition-[left,top,width,height] duration-200 ease-in-out`,`pointer-events-none`]),c=(0,t.createContext)({}),l=()=>{let e=(0,t.useContext)(c);if(!e)throw Error(`useSegmentedControlContext must be used within a SegmentedControlContext Provider`);return e},u=({itemValues:e,containerRef:t,onValueChange:n})=>({handleKeyDown:r=>{let i=r.target.getAttribute(`data-value`);if(!i)return;let a=e.indexOf(i);if(a===-1)return;let o=null;if(o=d(r.key,a,e.length),o!==null&&o!==a){r.preventDefault();let i=0,a=e.length;for(;i<a;){let a=e[o];if(!a)return;let s=t.current?.querySelector(`[data-value="${CSS.escape(a)}"]`);if(s&&!s.hasAttribute(`data-disabled`)){s.focus(),n(a);return}let c=r.key===`ArrowRight`||r.key===`ArrowDown`?1:-1;o=(o+c+e.length)%e.length,i++}}}});function d(e,t,n){switch(e){case`ArrowRight`:case`ArrowDown`:return(t+1)%n;case`ArrowLeft`:case`ArrowUp`:return(t-1+n)%n;default:return null}}var f=e=>{let n=null;return t.Children.forEach(e,e=>{n===null&&(0,t.isValidElement)(e)&&typeof e.props.value==`string`&&(n=e.props.value)}),n},p=({value:e,defaultValue:o,onValueChange:s,className:l,children:d,rowLength:p,name:m,ref:h,...g})=>{let _=(0,t.useRef)(null),v=(0,r.useMergeRefs)(_,h),y=f(d),b=e!==void 0,[x,S]=(0,t.useState)(()=>o??y),C=b?e??null:x,w=e=>{b||S(e),s?.(e)},{labelId:T,description:E,isRequired:D,isInvalid:O,name:k}=(0,i.useFormFieldControl)(),A=m??k,j=[];t.Children.forEach(d,e=>{(0,t.isValidElement)(e)&&typeof e.props.value==`string`&&j.push(e.props.value)});let{handleKeyDown:M}=u({itemValues:j,containerRef:_,onValueChange:w}),N=p?{"--segmented-control-cols":p,rowGap:`var(--spacing-md)`}:void 0;return(0,n.jsx)(c,{value:{checkedValue:C,containerRef:_,onValueChange:w,name:A,rowLength:p,itemValues:j},children:(0,n.jsx)(`div`,{ref:v,role:`radiogroup`,"data-spark-component":`segmented-control`,className:a({className:l}),style:N,"aria-labelledby":T,"aria-describedby":E,"aria-required":D||void 0,"aria-invalid":O||void 0,onKeyDown:M,...g,children:d})})};p.displayName=`SegmentedControl`;var m=({className:e,ref:r,...i})=>{let{checkedValue:a,containerRef:o}=l(),[c,u]=(0,t.useState)(null),d=(0,t.useMemo)(()=>a?`[data-value="${CSS.escape(a)}"]`:null,[a]);if((0,t.useEffect)(()=>{let e=o.current;if(!e)return;let t=d?e.querySelector(d):null,n=()=>{let e=o.current;if(!e||!d){u(null);return}let t=e.querySelector(d);if(!t){u(null);return}let n=e.getBoundingClientRect(),r=t.getBoundingClientRect(),i=t.offsetWidth>0?r.width/t.offsetWidth:1,a=t.offsetHeight>0?r.height/t.offsetHeight:1;u({left:(r.left-n.left)/i-e.clientLeft,top:(r.top-n.top)/a-e.clientTop,width:r.width/i,height:r.height/a})};n();let r=typeof ResizeObserver<`u`?new ResizeObserver(()=>{n()}):null;return r?.observe(e),t&&r?.observe(t),window.addEventListener(`resize`,n,{passive:!0}),window.visualViewport?.addEventListener(`resize`,n,{passive:!0}),()=>{r?.disconnect(),window.removeEventListener(`resize`,n),window.visualViewport?.removeEventListener(`resize`,n)}},[o,d]),!c)return null;let f={left:c.left,top:c.top,width:c.width,height:c.height};return(0,n.jsx)(`span`,{ref:r,"data-spark-component":`segmented-control-indicator`,"aria-hidden":!0,className:s({className:e}),style:f,...i})};m.displayName=`SegmentedControl.Indicator`;var h=({value:e,disabled:r=!1,children:i,className:a,ref:s,...c})=>{let{checkedValue:u,onValueChange:d,name:f,rowLength:p,itemValues:m}=l(),h=u===e,g=()=>{r||d(e)},_=t.Children.toArray(i).map((e,t)=>typeof e==`string`||typeof e==`number`?(0,n.jsx)(`span`,{"data-spark-segmented-control-text":!0,children:e},`text-${t}`):e),v=m.indexOf(e),y=p?{flexBasis:`calc(100% / var(--segmented-control-cols))`}:void 0,b=!1;if(p&&p>0&&v!==-1){let e=p,t=v%e;b=Math.floor(v/e)<Math.ceil(m.length/e)-1&&t===0}return(0,n.jsxs)(`button`,{ref:s,type:`button`,role:`radio`,"data-spark-component":`segmented-control-item`,"data-value":e,"aria-checked":h,"data-checked":h||void 0,"data-disabled":r||void 0,disabled:r,tabIndex:h?0:-1,className:o({className:a}),style:y,onClick:g,...c,children:[_,f&&h&&(0,n.jsx)(`input`,{type:`hidden`,name:f,value:e}),b&&p&&(0,n.jsx)(`div`,{className:`bg-outline/dim-3 -mx-sm absolute left-0 h-px`,style:{bottom:`calc(var(--spacing-md) / -2)`,width:`calc(${p*100}% + var(--spacing-sm) * 2)`},"aria-hidden":`true`})]})};h.displayName=`SegmentedControl.Item`;var g=Object.assign(p,{Item:h,Indicator:m});g.displayName=`SegmentedControl`,h.displayName=`SegmentedControl.Item`,m.displayName=`SegmentedControl.Indicator`,exports.SegmentedControl=g;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/segmented-control/SegmentedControl.styles.ts","../../src/segmented-control/SegmentedControlContext.tsx","../../src/segmented-control/SegmentedControl.tsx","../../src/segmented-control/SegmentedControlIndicator.tsx","../../src/segmented-control/SegmentedControlItem.tsx","../../src/segmented-control/index.ts"],"sourcesContent":["import { cva, VariantProps } from 'class-variance-authority'\n\nexport const rootStyles = cva([\n 'default:self-start',\n 'group inline-grid grid-flow-col auto-cols-fr',\n 'relative items-stretch min-w-max',\n 'rounded-xl p-sm',\n 'bg-surface border-sm border-outline',\n])\n\nexport const itemStyles = cva([\n 'relative z-raised min-h-sz-44 focus-visible:outline-none',\n 'flex flex-none items-center justify-center gap-md',\n 'default:px-lg default:py-md',\n 'rounded-[20px]',\n 'cursor-pointer select-none',\n 'font-medium',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:u-outline',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-dim-3',\n 'data-checked:text-on-support-container',\n // Avoid layout shift: simulate \"bold\" without changing font metrics.\n // Apply only to wrapped text nodes (not arbitrary nested JSX like Tag).\n 'data-checked:[&>[data-spark-segmented-control-text]]:[text-shadow:0.35px_0_currentColor,-0.35px_0_currentColor]',\n])\n\nexport const indicatorStyles = cva([\n 'absolute z-base',\n 'rounded-[20px]',\n 'bg-support-container border-md border-support',\n 'group-has-focus-visible:border-focus',\n 'transition-[left,top,width,height] duration-200 ease-in-out',\n 'pointer-events-none',\n])\n\nexport type SegmentedControlStylesProps = VariantProps<typeof itemStyles>\n","import { createContext, RefObject, useContext } from 'react'\n\nexport interface SegmentedControlContextInterface {\n checkedValue: string | null\n containerRef: RefObject<HTMLDivElement | null>\n}\n\nexport const SegmentedControlContext = createContext<SegmentedControlContextInterface>(\n {} as SegmentedControlContextInterface\n)\n\nexport const useSegmentedControlContext = () => {\n const context = useContext(SegmentedControlContext)\n\n if (!context) {\n throw Error('useSegmentedControlContext must be used within a SegmentedControlContext Provider')\n }\n\n return context\n}\n","import { RadioGroup } from '@base-ui/react/radio-group'\nimport { useFormFieldControl } from '@spark-ui/components/form-field'\nimport { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { Children, type ComponentProps, isValidElement, Ref, useRef, useState } from 'react'\n\nimport type { SegmentedControlStylesProps } from './SegmentedControl.styles'\nimport { rootStyles } from './SegmentedControl.styles'\nimport { SegmentedControlContext } from './SegmentedControlContext'\n\nexport interface SegmentedControlProps\n extends\n Omit<ComponentProps<typeof RadioGroup>, 'value' | 'defaultValue' | 'onValueChange'>,\n SegmentedControlStylesProps {\n /**\n * The controlled selected value.\n */\n value?: string\n /**\n * The uncontrolled default selected value.\n */\n defaultValue?: string\n /**\n * Callback fired when the selected value changes.\n */\n onValueChange?: (value: string) => void\n ref?: Ref<HTMLDivElement>\n}\n\nconst getFirstItemValue = (children: React.ReactNode): string | null => {\n let firstValue: string | null = null\n\n Children.forEach(children, child => {\n if (firstValue !== null) return\n if (isValidElement(child) && typeof (child.props as { value?: string }).value === 'string') {\n firstValue = (child.props as { value: string }).value\n }\n })\n\n return firstValue\n}\n\nexport const SegmentedControl = ({\n value,\n defaultValue,\n onValueChange,\n className,\n children,\n ref,\n ...rest\n}: SegmentedControlProps) => {\n const containerRef = useRef<HTMLDivElement | null>(null)\n const mergedRef = useMergeRefs(containerRef, ref)\n\n const firstValue = getFirstItemValue(children)\n\n const isControlled = value !== undefined\n const [internalValue, setInternalValue] = useState<string | null>(\n () => defaultValue ?? firstValue\n )\n const checkedValue = isControlled ? (value ?? null) : internalValue\n\n const handleValueChange = (newValue: unknown) => {\n const next = newValue as string\n\n if (!isControlled) {\n setInternalValue(next)\n }\n\n onValueChange?.(next)\n }\n\n const { labelId, description, isRequired, isInvalid, name } = useFormFieldControl()\n\n return (\n <SegmentedControlContext\n value={{\n checkedValue,\n containerRef,\n }}\n >\n <RadioGroup\n ref={mergedRef}\n value={isControlled ? value : undefined}\n defaultValue={!isControlled ? (defaultValue ?? firstValue ?? undefined) : undefined}\n onValueChange={handleValueChange}\n data-spark-component=\"segmented-control\"\n className={rootStyles({ className })}\n aria-labelledby={labelId}\n aria-describedby={description}\n aria-required={isRequired || undefined}\n aria-invalid={isInvalid || undefined}\n name={name}\n {...rest}\n >\n {children}\n </RadioGroup>\n </SegmentedControlContext>\n )\n}\n\nSegmentedControl.displayName = 'SegmentedControl'\n","import { type ComponentProps, type CSSProperties, Ref, useEffect, useMemo, useState } from 'react'\n\nimport { indicatorStyles } from './SegmentedControl.styles'\nimport { useSegmentedControlContext } from './SegmentedControlContext'\n\ninterface IndicatorRect {\n left: number\n top: number\n width: number\n height: number\n}\n\nexport interface SegmentedControlIndicatorProps extends ComponentProps<'span'> {\n ref?: Ref<HTMLSpanElement>\n}\n\n/** The visual indicator that highlights the selected item. Renders a <span> element. */\nexport const SegmentedControlIndicator = ({\n className,\n ref,\n ...rest\n}: SegmentedControlIndicatorProps) => {\n const { checkedValue, containerRef } = useSegmentedControlContext()\n const [rect, setRect] = useState<IndicatorRect | null>(null)\n\n const selector = useMemo(\n () => (checkedValue ? `[data-value=\"${CSS.escape(checkedValue)}\"]` : null),\n [checkedValue]\n )\n\n useEffect(() => {\n const container = containerRef.current\n\n if (!container) {\n return\n }\n\n const selectedItem = selector ? container.querySelector<HTMLElement>(selector) : null\n\n const update = () => {\n const currentContainer = containerRef.current\n if (!currentContainer || !selector) {\n setRect(null)\n\n return\n }\n\n const currentSelected = currentContainer.querySelector<HTMLElement>(selector)\n if (!currentSelected) {\n setRect(null)\n\n return\n }\n\n const containerRect = currentContainer.getBoundingClientRect()\n const itemRect = currentSelected.getBoundingClientRect()\n\n // Storybook canvas \"zoom\" can be implemented via `transform: scale()`.\n // In that case, `getBoundingClientRect()` returns *scaled* values, but CSS positioning/sizing\n // expects unscaled layout pixels. We infer the scale factor from offset sizes and normalize.\n const scaleX =\n currentSelected.offsetWidth > 0 ? itemRect.width / currentSelected.offsetWidth : 1\n const scaleY =\n currentSelected.offsetHeight > 0 ? itemRect.height / currentSelected.offsetHeight : 1\n\n // `getBoundingClientRect()` is border-box; absolute positioning is relative to the padding box.\n setRect({\n left: (itemRect.left - containerRect.left) / scaleX - currentContainer.clientLeft,\n top: (itemRect.top - containerRect.top) / scaleY - currentContainer.clientTop,\n width: itemRect.width / scaleX,\n height: itemRect.height / scaleY,\n })\n }\n\n update()\n\n const ro =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => {\n update()\n })\n : null\n\n ro?.observe(container)\n if (selectedItem) ro?.observe(selectedItem)\n\n window.addEventListener('resize', update, { passive: true })\n window.visualViewport?.addEventListener('resize', update, { passive: true })\n\n return () => {\n ro?.disconnect()\n window.removeEventListener('resize', update)\n window.visualViewport?.removeEventListener('resize', update)\n }\n }, [containerRef, selector])\n\n if (!rect) return null\n\n const style: CSSProperties = {\n left: rect.left,\n top: rect.top,\n width: rect.width,\n height: rect.height,\n }\n\n return (\n <span\n ref={ref}\n data-spark-component=\"segmented-control-indicator\"\n aria-hidden\n className={indicatorStyles({ className })}\n style={style}\n {...rest}\n />\n )\n}\n\nSegmentedControlIndicator.displayName = 'SegmentedControl.Indicator'\n","import { Radio } from '@base-ui/react/radio'\nimport { Children, type ComponentProps, Ref } from 'react'\n\nimport { itemStyles } from './SegmentedControl.styles'\n\nexport interface SegmentedControlItemProps extends Omit<\n ComponentProps<typeof Radio.Root>,\n 'value'\n> {\n /**\n * A unique value that identifies this item within the segmented control.\n */\n value: string\n /**\n * When true, prevents the user from interacting with this item.\n * @default false\n */\n disabled?: boolean\n ref?: Ref<HTMLElement>\n}\n\n/** A selectable item in the segmented control. Renders a <button> element. */\nexport const SegmentedControlItem = ({\n value,\n disabled = false,\n children,\n className,\n ref,\n ...rest\n}: SegmentedControlItemProps) => {\n const content = Children.toArray(children).map((child, index) => {\n if (typeof child === 'string' || typeof child === 'number') {\n return (\n <span key={`text-${index}`} data-spark-segmented-control-text>\n {child}\n </span>\n )\n }\n\n return child\n })\n\n return (\n <Radio.Root\n ref={ref}\n data-spark-component=\"segmented-control-item\"\n data-value={value}\n value={value}\n disabled={disabled}\n className={itemStyles({ className })}\n {...rest}\n >\n {content}\n </Radio.Root>\n )\n}\n\nSegmentedControlItem.displayName = 'SegmentedControl.Item'\n","import { SegmentedControl as Root } from './SegmentedControl'\nimport { SegmentedControlIndicator as Indicator } from './SegmentedControlIndicator'\nimport { SegmentedControlItem as Item } from './SegmentedControlItem'\n\n/**\n * A set of toggle buttons that allows users to select a single option from a group of related choices.\n */\nexport const SegmentedControl: typeof Root & {\n Item: typeof Item\n Indicator: typeof Indicator\n} = Object.assign(Root, {\n Item,\n Indicator,\n})\n\nSegmentedControl.displayName = 'SegmentedControl'\nItem.displayName = 'SegmentedControl.Item'\nIndicator.displayName = 'SegmentedControl.Indicator'\n\nexport type { SegmentedControlProps } from './SegmentedControl'\nexport type { SegmentedControlItemProps } from './SegmentedControlItem'\nexport type { SegmentedControlIndicatorProps } from './SegmentedControlIndicator'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/segmented-control/SegmentedControl.styles.ts","../../src/segmented-control/SegmentedControlContext.tsx","../../src/segmented-control/useSegmentedControlNavigation.ts","../../src/segmented-control/SegmentedControl.tsx","../../src/segmented-control/SegmentedControlIndicator.tsx","../../src/segmented-control/SegmentedControlItem.tsx","../../src/segmented-control/index.ts"],"sourcesContent":["import { cva, VariantProps } from 'class-variance-authority'\n\nexport const rootStyles = cva([\n 'default:self-start',\n 'group inline-flex flex-wrap',\n 'relative items-stretch min-w-max',\n 'rounded-xl p-sm',\n 'bg-surface border-sm border-outline',\n])\n\nexport const itemStyles = cva([\n 'relative z-raised min-h-sz-44 focus-visible:outline-none',\n 'flex flex-auto items-center justify-center gap-md',\n 'default:px-lg default:py-md',\n 'rounded-[20px]',\n 'cursor-pointer select-none',\n 'font-medium',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:u-outline',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-dim-3',\n 'data-checked:text-on-support-container',\n // Avoid layout shift: simulate \"bold\" without changing font metrics.\n // Apply only to wrapped text nodes (not arbitrary nested JSX like Tag).\n 'data-checked:[&>[data-spark-segmented-control-text]]:[text-shadow:0.35px_0_currentColor,-0.35px_0_currentColor]',\n])\n\nexport const indicatorStyles = cva([\n 'absolute z-base',\n 'rounded-[20px]',\n 'bg-support-container border-md border-support',\n 'group-has-focus-visible:border-focus',\n 'transition-[left,top,width,height] duration-200 ease-in-out',\n 'pointer-events-none',\n])\n\nexport type SegmentedControlStylesProps = VariantProps<typeof itemStyles>\n","import { createContext, RefObject, useContext } from 'react'\n\nexport interface SegmentedControlContextInterface {\n checkedValue: string | null\n containerRef: RefObject<HTMLDivElement | null>\n onValueChange: (value: string) => void\n name?: string\n rowLength?: number\n itemValues: string[]\n}\n\nexport const SegmentedControlContext = createContext<SegmentedControlContextInterface>(\n {} as SegmentedControlContextInterface\n)\n\nexport const useSegmentedControlContext = () => {\n const context = useContext(SegmentedControlContext)\n\n if (!context) {\n throw Error('useSegmentedControlContext must be used within a SegmentedControlContext Provider')\n }\n\n return context\n}\n","import { KeyboardEvent, RefObject } from 'react'\n\ninterface UseSegmentedControlNavigationProps {\n itemValues: string[]\n containerRef: RefObject<HTMLDivElement | null>\n onValueChange: (value: string) => void\n}\n\n/**\n * Custom hook that handles keyboard navigation for SegmentedControl.\n * Uses sequential left/right navigation.\n */\nexport const useSegmentedControlNavigation = ({\n itemValues,\n containerRef,\n onValueChange,\n}: UseSegmentedControlNavigationProps) => {\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n // Find the currently focused item (which may differ from checkedValue during keyboard navigation)\n const focusedElement = e.target as HTMLElement\n const focusedValue = focusedElement.getAttribute('data-value')\n\n if (!focusedValue) return\n\n const currentIndex = itemValues.indexOf(focusedValue)\n if (currentIndex === -1) return\n\n let nextIndex: number | null = null\n\n // Always use 1D sequential navigation (left/right only)\n nextIndex = calculate1DNavigation(e.key, currentIndex, itemValues.length)\n\n if (nextIndex !== null && nextIndex !== currentIndex) {\n e.preventDefault()\n\n // Skip disabled items\n let attempts = 0\n const maxAttempts = itemValues.length\n\n while (attempts < maxAttempts) {\n const nextValue = itemValues[nextIndex]\n if (!nextValue) return\n\n const nextItem = containerRef.current?.querySelector<HTMLElement>(\n `[data-value=\"${CSS.escape(nextValue)}\"]`\n )\n\n // If the item is not disabled, focus it and update the value\n if (nextItem && !nextItem.hasAttribute('data-disabled')) {\n nextItem.focus()\n onValueChange(nextValue)\n return\n }\n\n // If disabled, continue in the same direction\n const direction = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1 : -1\n nextIndex = (nextIndex + direction + itemValues.length) % itemValues.length\n attempts++\n }\n }\n }\n\n return { handleKeyDown }\n}\n\n/**\n * Calculate next index for 1D sequential navigation.\n * Navigation wraps around at the boundaries.\n */\nfunction calculate1DNavigation(\n key: string,\n currentIndex: number,\n totalItems: number\n): number | null {\n switch (key) {\n case 'ArrowRight':\n case 'ArrowDown':\n return (currentIndex + 1) % totalItems\n case 'ArrowLeft':\n case 'ArrowUp':\n return (currentIndex - 1 + totalItems) % totalItems\n default:\n return null\n }\n}\n","import { useFormFieldControl } from '@spark-ui/components/form-field'\nimport { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport {\n Children,\n type ComponentProps,\n CSSProperties,\n isValidElement,\n Ref,\n useRef,\n useState,\n} from 'react'\n\nimport type { SegmentedControlStylesProps } from './SegmentedControl.styles'\nimport { rootStyles } from './SegmentedControl.styles'\nimport { SegmentedControlContext } from './SegmentedControlContext'\nimport { useSegmentedControlNavigation } from './useSegmentedControlNavigation'\n\nexport interface SegmentedControlProps\n extends Omit<ComponentProps<'div'>, 'onValueChange'>, SegmentedControlStylesProps {\n /**\n * The controlled selected value.\n */\n value?: string\n /**\n * The uncontrolled default selected value.\n */\n defaultValue?: string\n /**\n * Callback fired when the selected value changes.\n */\n onValueChange?: (value: string) => void\n /**\n * Number of items per row in multi-row layout.\n * When undefined, items display in a single row (default behavior).\n * @default undefined\n * @example\n * // Create 3-column grid with wrapping rows\n * <SegmentedControl rowLength={3}>\n */\n rowLength?: number\n /**\n * The name attribute for the radio group (used in form submissions).\n */\n name?: string\n ref?: Ref<HTMLDivElement>\n}\n\nconst getFirstItemValue = (children: React.ReactNode): string | null => {\n let firstValue: string | null = null\n\n Children.forEach(children, child => {\n if (firstValue !== null) return\n if (isValidElement(child) && typeof (child.props as { value?: string }).value === 'string') {\n firstValue = (child.props as { value: string }).value\n }\n })\n\n return firstValue\n}\n\nexport const SegmentedControl = ({\n value,\n defaultValue,\n onValueChange,\n className,\n children,\n rowLength,\n name: nameProp,\n ref,\n ...rest\n}: SegmentedControlProps) => {\n const containerRef = useRef<HTMLDivElement | null>(null)\n const mergedRef = useMergeRefs(containerRef, ref)\n\n const firstValue = getFirstItemValue(children)\n\n const isControlled = value !== undefined\n const [internalValue, setInternalValue] = useState<string | null>(\n () => defaultValue ?? firstValue\n )\n const checkedValue = isControlled ? (value ?? null) : internalValue\n\n const handleValueChange = (newValue: string) => {\n if (!isControlled) {\n setInternalValue(newValue)\n }\n\n onValueChange?.(newValue)\n }\n\n const { labelId, description, isRequired, isInvalid, name: nameFromField } = useFormFieldControl()\n const name = nameProp ?? nameFromField\n\n // Get all item values in order\n const itemValues: string[] = []\n Children.forEach(children, child => {\n if (isValidElement(child) && typeof (child.props as { value?: string }).value === 'string') {\n itemValues.push((child.props as { value: string }).value)\n }\n })\n\n // Keyboard navigation (sequential left/right)\n const { handleKeyDown } = useSegmentedControlNavigation({\n itemValues,\n containerRef,\n onValueChange: handleValueChange,\n })\n\n // Compute dynamic flex styles for multi-row layout\n const flexStyles = rowLength\n ? ({\n '--segmented-control-cols': rowLength,\n rowGap: 'var(--spacing-md)',\n } as CSSProperties)\n : undefined\n\n return (\n <SegmentedControlContext\n value={{\n checkedValue,\n containerRef,\n onValueChange: handleValueChange,\n name,\n rowLength,\n itemValues,\n }}\n >\n <div\n ref={mergedRef}\n role=\"radiogroup\"\n data-spark-component=\"segmented-control\"\n className={rootStyles({ className })}\n style={flexStyles}\n aria-labelledby={labelId}\n aria-describedby={description}\n aria-required={isRequired || undefined}\n aria-invalid={isInvalid || undefined}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {children}\n </div>\n </SegmentedControlContext>\n )\n}\n\nSegmentedControl.displayName = 'SegmentedControl'\n","import { type ComponentProps, type CSSProperties, Ref, useEffect, useMemo, useState } from 'react'\n\nimport { indicatorStyles } from './SegmentedControl.styles'\nimport { useSegmentedControlContext } from './SegmentedControlContext'\n\ninterface IndicatorRect {\n left: number\n top: number\n width: number\n height: number\n}\n\nexport interface SegmentedControlIndicatorProps extends ComponentProps<'span'> {\n ref?: Ref<HTMLSpanElement>\n}\n\n/** The visual indicator that highlights the selected item. Renders a <span> element. */\nexport const SegmentedControlIndicator = ({\n className,\n ref,\n ...rest\n}: SegmentedControlIndicatorProps) => {\n const { checkedValue, containerRef } = useSegmentedControlContext()\n const [rect, setRect] = useState<IndicatorRect | null>(null)\n\n const selector = useMemo(\n () => (checkedValue ? `[data-value=\"${CSS.escape(checkedValue)}\"]` : null),\n [checkedValue]\n )\n\n useEffect(() => {\n const container = containerRef.current\n\n if (!container) {\n return\n }\n\n const selectedItem = selector ? container.querySelector<HTMLElement>(selector) : null\n\n const update = () => {\n const currentContainer = containerRef.current\n if (!currentContainer || !selector) {\n setRect(null)\n\n return\n }\n\n const currentSelected = currentContainer.querySelector<HTMLElement>(selector)\n if (!currentSelected) {\n setRect(null)\n\n return\n }\n\n const containerRect = currentContainer.getBoundingClientRect()\n const itemRect = currentSelected.getBoundingClientRect()\n\n // Storybook canvas \"zoom\" can be implemented via `transform: scale()`.\n // In that case, `getBoundingClientRect()` returns *scaled* values, but CSS positioning/sizing\n // expects unscaled layout pixels. We infer the scale factor from offset sizes and normalize.\n const scaleX =\n currentSelected.offsetWidth > 0 ? itemRect.width / currentSelected.offsetWidth : 1\n const scaleY =\n currentSelected.offsetHeight > 0 ? itemRect.height / currentSelected.offsetHeight : 1\n\n // `getBoundingClientRect()` is border-box; absolute positioning is relative to the padding box.\n setRect({\n left: (itemRect.left - containerRect.left) / scaleX - currentContainer.clientLeft,\n top: (itemRect.top - containerRect.top) / scaleY - currentContainer.clientTop,\n width: itemRect.width / scaleX,\n height: itemRect.height / scaleY,\n })\n }\n\n update()\n\n const ro =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => {\n update()\n })\n : null\n\n ro?.observe(container)\n if (selectedItem) ro?.observe(selectedItem)\n\n window.addEventListener('resize', update, { passive: true })\n window.visualViewport?.addEventListener('resize', update, { passive: true })\n\n return () => {\n ro?.disconnect()\n window.removeEventListener('resize', update)\n window.visualViewport?.removeEventListener('resize', update)\n }\n }, [containerRef, selector])\n\n if (!rect) return null\n\n const style: CSSProperties = {\n left: rect.left,\n top: rect.top,\n width: rect.width,\n height: rect.height,\n }\n\n return (\n <span\n ref={ref}\n data-spark-component=\"segmented-control-indicator\"\n aria-hidden\n className={indicatorStyles({ className })}\n style={style}\n {...rest}\n />\n )\n}\n\nSegmentedControlIndicator.displayName = 'SegmentedControl.Indicator'\n","import { Children, type ComponentProps, Ref } from 'react'\n\nimport { itemStyles } from './SegmentedControl.styles'\nimport { useSegmentedControlContext } from './SegmentedControlContext'\n\nexport interface SegmentedControlItemProps extends Omit<\n ComponentProps<'button'>,\n 'value' | 'onClick'\n> {\n /**\n * A unique value that identifies this item within the segmented control.\n */\n value: string\n /**\n * When true, prevents the user from interacting with this item.\n * @default false\n */\n disabled?: boolean\n ref?: Ref<HTMLButtonElement>\n}\n\n/** A selectable item in the segmented control. Renders a <button> element. */\nexport const SegmentedControlItem = ({\n value,\n disabled = false,\n children,\n className,\n ref,\n ...rest\n}: SegmentedControlItemProps) => {\n const { checkedValue, onValueChange, name, rowLength, itemValues } = useSegmentedControlContext()\n\n const isChecked = checkedValue === value\n\n const handleClick = () => {\n if (!disabled) {\n onValueChange(value)\n }\n }\n\n const content = Children.toArray(children).map((child, index) => {\n if (typeof child === 'string' || typeof child === 'number') {\n return (\n <span key={`text-${index}`} data-spark-segmented-control-text>\n {child}\n </span>\n )\n }\n\n return child\n })\n\n // Calculate flex-basis for multi-row layout and horizontal separator visibility\n const itemIndex = itemValues.indexOf(value)\n const itemStyle = rowLength\n ? { flexBasis: `calc(100% / var(--segmented-control-cols))` }\n : undefined\n\n let showHorizontalSeparator = false\n\n if (rowLength && rowLength > 0 && itemIndex !== -1) {\n const cols = rowLength\n const currentCol = itemIndex % cols\n const currentRow = Math.floor(itemIndex / cols)\n const totalRows = Math.ceil(itemValues.length / cols)\n\n // Show horizontal separator only on the first column of each row (except last row)\n showHorizontalSeparator = currentRow < totalRows - 1 && currentCol === 0\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"radio\"\n data-spark-component=\"segmented-control-item\"\n data-value={value}\n aria-checked={isChecked}\n data-checked={isChecked || undefined}\n data-disabled={disabled || undefined}\n disabled={disabled}\n tabIndex={isChecked ? 0 : -1}\n className={itemStyles({ className })}\n style={itemStyle}\n onClick={handleClick}\n {...rest}\n >\n {content}\n {/* Hidden input for form submission */}\n {name && isChecked && <input type=\"hidden\" name={name} value={value} />}\n {/* Horizontal separator between rows (full width across all columns) */}\n {showHorizontalSeparator && rowLength && (\n <div\n className=\"bg-outline/dim-3 -mx-sm absolute left-0 h-px\"\n style={{\n bottom: 'calc(var(--spacing-md) / -2)',\n width: `calc(${rowLength * 100}% + var(--spacing-sm) * 2)`,\n }}\n aria-hidden=\"true\"\n />\n )}\n </button>\n )\n}\n\nSegmentedControlItem.displayName = 'SegmentedControl.Item'\n","import { SegmentedControl as Root } from './SegmentedControl'\nimport { SegmentedControlIndicator as Indicator } from './SegmentedControlIndicator'\nimport { SegmentedControlItem as Item } from './SegmentedControlItem'\n\n/**\n * A set of toggle buttons that allows users to select a single option from a group of related choices.\n */\nexport const SegmentedControl: typeof Root & {\n Item: typeof Item\n Indicator: typeof Indicator\n} = Object.assign(Root, {\n Item,\n Indicator,\n})\n\nSegmentedControl.displayName = 'SegmentedControl'\nItem.displayName = 'SegmentedControl.Item'\nIndicator.displayName = 'SegmentedControl.Indicator'\n\nexport type { SegmentedControlProps } from './SegmentedControl'\nexport type { SegmentedControlItemProps } from './SegmentedControlItem'\nexport type { SegmentedControlIndicatorProps } from './SegmentedControlIndicator'\n"],"mappings":"wRAEA,IAAa,GAAA,EAAA,EAAA,KAAiB,CAC5B,qBACA,8BACA,mCACA,kBACA,sCACD,CAAC,CAEW,GAAA,EAAA,EAAA,KAAiB,CAC5B,2DACA,oDACA,8BACA,iBACA,6BACA,cACA,iCACA,eACA,0BACA,+DACA,yCAGA,kHACD,CAAC,CAEW,GAAA,EAAA,EAAA,KAAsB,CACjC,kBACA,iBACA,gDACA,uCACA,8DACA,sBACD,CAAC,CCvBW,GAAA,EAAA,EAAA,eACX,EAAE,CACH,CAEY,MAAmC,CAC9C,IAAM,GAAA,EAAA,EAAA,YAAqB,EAAwB,CAEnD,GAAI,CAAC,EACH,MAAM,MAAM,oFAAoF,CAGlG,OAAO,GCVI,GAAiC,CAC5C,aACA,eACA,oBA+CO,CAAE,cA7Cc,GAAqC,CAG1D,IAAM,EADiB,EAAE,OACW,aAAa,aAAa,CAE9D,GAAI,CAAC,EAAc,OAEnB,IAAM,EAAe,EAAW,QAAQ,EAAa,CACrD,GAAI,IAAiB,GAAI,OAEzB,IAAI,EAA2B,KAK/B,GAFA,EAAY,EAAsB,EAAE,IAAK,EAAc,EAAW,OAAO,CAErE,IAAc,MAAQ,IAAc,EAAc,CACpD,EAAE,gBAAgB,CAGlB,IAAI,EAAW,EACT,EAAc,EAAW,OAE/B,KAAO,EAAW,GAAa,CAC7B,IAAM,EAAY,EAAW,GAC7B,GAAI,CAAC,EAAW,OAEhB,IAAM,EAAW,EAAa,SAAS,cACrC,gBAAgB,IAAI,OAAO,EAAU,CAAC,IACvC,CAGD,GAAI,GAAY,CAAC,EAAS,aAAa,gBAAgB,CAAE,CACvD,EAAS,OAAO,CAChB,EAAc,EAAU,CACxB,OAIF,IAAM,EAAY,EAAE,MAAQ,cAAgB,EAAE,MAAQ,YAAc,EAAI,GACxE,GAAa,EAAY,EAAY,EAAW,QAAU,EAAW,OACrE,OAKkB,EAO1B,SAAS,EACP,EACA,EACA,EACe,CACf,OAAQ,EAAR,CACE,IAAK,aACL,IAAK,YACH,OAAQ,EAAe,GAAK,EAC9B,IAAK,YACL,IAAK,UACH,OAAQ,EAAe,EAAI,GAAc,EAC3C,QACE,OAAO,MCnCb,IAAM,EAAqB,GAA6C,CACtE,IAAI,EAA4B,KAShC,OAPA,EAAA,SAAS,QAAQ,EAAU,GAAS,CAC9B,IAAe,OACnB,EAAA,EAAA,gBAAmB,EAAM,EAAI,OAAQ,EAAM,MAA6B,OAAU,WAChF,EAAc,EAAM,MAA4B,QAElD,CAEK,GAGI,GAAoB,CAC/B,QACA,eACA,gBACA,YACA,WACA,YACA,KAAM,EACN,MACA,GAAG,KACwB,CAC3B,IAAM,GAAA,EAAA,EAAA,QAA6C,KAAK,CAClD,GAAA,EAAA,EAAA,cAAyB,EAAc,EAAI,CAE3C,EAAa,EAAkB,EAAS,CAExC,EAAe,IAAU,IAAA,GACzB,CAAC,EAAe,IAAA,EAAA,EAAA,cACd,GAAgB,EACvB,CACK,EAAe,EAAgB,GAAS,KAAQ,EAEhD,EAAqB,GAAqB,CACzC,GACH,EAAiB,EAAS,CAG5B,IAAgB,EAAS,EAGrB,CAAE,UAAS,cAAa,aAAY,YAAW,KAAM,IAAA,EAAA,EAAA,sBAAuC,CAC5F,EAAO,GAAY,EAGnB,EAAuB,EAAE,CAC/B,EAAA,SAAS,QAAQ,EAAU,GAAS,EAClC,EAAA,EAAA,gBAAmB,EAAM,EAAI,OAAQ,EAAM,MAA6B,OAAU,UAChF,EAAW,KAAM,EAAM,MAA4B,MAAM,EAE3D,CAGF,GAAM,CAAE,iBAAkB,EAA8B,CACtD,aACA,eACA,cAAe,EAChB,CAAC,CAGI,EAAa,EACd,CACC,2BAA4B,EAC5B,OAAQ,oBACT,CACD,IAAA,GAEJ,OACE,EAAA,EAAA,KAAC,EAAD,CACE,MAAO,CACL,eACA,eACA,cAAe,EACf,OACA,YACA,aACD,WAED,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,KAAK,aACL,uBAAqB,oBACrB,UAAW,EAAW,CAAE,YAAW,CAAC,CACpC,MAAO,EACP,kBAAiB,EACjB,mBAAkB,EAClB,gBAAe,GAAc,IAAA,GAC7B,eAAc,GAAa,IAAA,GAC3B,UAAW,EACX,GAAI,EAEH,WACG,CAAA,CACkB,CAAA,EAI9B,EAAiB,YAAc,mBCjI/B,IAAa,GAA6B,CACxC,YACA,MACA,GAAG,KACiC,CACpC,GAAM,CAAE,eAAc,gBAAiB,GAA4B,CAC7D,CAAC,EAAM,IAAA,EAAA,EAAA,UAA0C,KAAK,CAEtD,GAAA,EAAA,EAAA,aACG,EAAe,gBAAgB,IAAI,OAAO,EAAa,CAAC,IAAM,KACrE,CAAC,EAAa,CACf,CAoED,IAlEA,EAAA,EAAA,eAAgB,CACd,IAAM,EAAY,EAAa,QAE/B,GAAI,CAAC,EACH,OAGF,IAAM,EAAe,EAAW,EAAU,cAA2B,EAAS,CAAG,KAE3E,MAAe,CACnB,IAAM,EAAmB,EAAa,QACtC,GAAI,CAAC,GAAoB,CAAC,EAAU,CAClC,EAAQ,KAAK,CAEb,OAGF,IAAM,EAAkB,EAAiB,cAA2B,EAAS,CAC7E,GAAI,CAAC,EAAiB,CACpB,EAAQ,KAAK,CAEb,OAGF,IAAM,EAAgB,EAAiB,uBAAuB,CACxD,EAAW,EAAgB,uBAAuB,CAKlD,EACJ,EAAgB,YAAc,EAAI,EAAS,MAAQ,EAAgB,YAAc,EAC7E,EACJ,EAAgB,aAAe,EAAI,EAAS,OAAS,EAAgB,aAAe,EAGtF,EAAQ,CACN,MAAO,EAAS,KAAO,EAAc,MAAQ,EAAS,EAAiB,WACvE,KAAM,EAAS,IAAM,EAAc,KAAO,EAAS,EAAiB,UACpE,MAAO,EAAS,MAAQ,EACxB,OAAQ,EAAS,OAAS,EAC3B,CAAC,EAGJ,GAAQ,CAER,IAAM,EACJ,OAAO,eAAmB,IACtB,IAAI,mBAAqB,CACvB,GAAQ,EACR,CACF,KAQN,OANA,GAAI,QAAQ,EAAU,CAClB,GAAc,GAAI,QAAQ,EAAa,CAE3C,OAAO,iBAAiB,SAAU,EAAQ,CAAE,QAAS,GAAM,CAAC,CAC5D,OAAO,gBAAgB,iBAAiB,SAAU,EAAQ,CAAE,QAAS,GAAM,CAAC,KAE/D,CACX,GAAI,YAAY,CAChB,OAAO,oBAAoB,SAAU,EAAO,CAC5C,OAAO,gBAAgB,oBAAoB,SAAU,EAAO,GAE7D,CAAC,EAAc,EAAS,CAAC,CAExB,CAAC,EAAM,OAAO,KAElB,IAAM,EAAuB,CAC3B,KAAM,EAAK,KACX,IAAK,EAAK,IACV,MAAO,EAAK,MACZ,OAAQ,EAAK,OACd,CAED,OACE,EAAA,EAAA,KAAC,OAAD,CACO,MACL,uBAAqB,8BACrB,cAAA,GACA,UAAW,EAAgB,CAAE,YAAW,CAAC,CAClC,QACP,GAAI,EACJ,CAAA,EAIN,EAA0B,YAAc,6BC/FxC,IAAa,GAAwB,CACnC,QACA,WAAW,GACX,WACA,YACA,MACA,GAAG,KAC4B,CAC/B,GAAM,CAAE,eAAc,gBAAe,OAAM,YAAW,cAAe,GAA4B,CAE3F,EAAY,IAAiB,EAE7B,MAAoB,CACnB,GACH,EAAc,EAAM,EAIlB,EAAU,EAAA,SAAS,QAAQ,EAAS,CAAC,KAAK,EAAO,IACjD,OAAO,GAAU,UAAY,OAAO,GAAU,UAE9C,EAAA,EAAA,KAAC,OAAD,CAA4B,oCAAA,YACzB,EACI,CAFI,QAAQ,IAEZ,CAIJ,EACP,CAGI,EAAY,EAAW,QAAQ,EAAM,CACrC,EAAY,EACd,CAAE,UAAW,6CAA8C,CAC3D,IAAA,GAEA,EAA0B,GAE9B,GAAI,GAAa,EAAY,GAAK,IAAc,GAAI,CAClD,IAAM,EAAO,EACP,EAAa,EAAY,EAK/B,EAJmB,KAAK,MAAM,EAAY,EAAK,CAC7B,KAAK,KAAK,EAAW,OAAS,EAAK,CAGF,GAAK,IAAe,EAGzE,OACE,EAAA,EAAA,MAAC,SAAD,CACO,MACL,KAAK,SACL,KAAK,QACL,uBAAqB,yBACrB,aAAY,EACZ,eAAc,EACd,eAAc,GAAa,IAAA,GAC3B,gBAAe,GAAY,IAAA,GACjB,WACV,SAAU,EAAY,EAAI,GAC1B,UAAW,EAAW,CAAE,YAAW,CAAC,CACpC,MAAO,EACP,QAAS,EACT,GAAI,WAdN,CAgBG,EAEA,GAAQ,IAAa,EAAA,EAAA,KAAC,QAAD,CAAO,KAAK,SAAe,OAAa,QAAS,CAAA,CAEtE,GAA2B,IAC1B,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,+CACV,MAAO,CACL,OAAQ,+BACR,MAAO,QAAQ,EAAY,IAAI,4BAChC,CACD,cAAY,OACZ,CAAA,CAEG,IAIb,EAAqB,YAAc,wBClGnC,IAAa,EAGT,OAAO,OAAO,EAAM,CACtB,KAAA,EACA,UAAA,EACD,CAAC,CAEF,EAAiB,YAAc,mBAC/B,EAAK,YAAc,wBACnB,EAAU,YAAc"}
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { cva as e } from "class-variance-authority";
|
|
2
2
|
import { Children as t, createContext as n, isValidElement as r, useContext as i, useEffect as a, useMemo as o, useRef as s, useState as c } from "react";
|
|
3
|
-
import { jsx as l } from "react/jsx-runtime";
|
|
4
|
-
import { useMergeRefs as
|
|
5
|
-
import { useFormFieldControl as
|
|
6
|
-
import { RadioGroup as f } from "@base-ui/react/radio-group";
|
|
7
|
-
import { Radio as p } from "@base-ui/react/radio";
|
|
3
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
4
|
+
import { useMergeRefs as d } from "@spark-ui/hooks/use-merge-refs";
|
|
5
|
+
import { useFormFieldControl as f } from "@spark-ui/components/form-field";
|
|
8
6
|
//#region src/segmented-control/SegmentedControl.styles.ts
|
|
9
|
-
var
|
|
7
|
+
var p = e([
|
|
10
8
|
"default:self-start",
|
|
11
|
-
"group inline-
|
|
9
|
+
"group inline-flex flex-wrap",
|
|
12
10
|
"relative items-stretch min-w-max",
|
|
13
11
|
"rounded-xl p-sm",
|
|
14
12
|
"bg-surface border-sm border-outline"
|
|
15
|
-
]),
|
|
13
|
+
]), m = e([
|
|
16
14
|
"relative z-raised min-h-sz-44 focus-visible:outline-none",
|
|
17
|
-
"flex flex-
|
|
15
|
+
"flex flex-auto items-center justify-center gap-md",
|
|
18
16
|
"default:px-lg default:py-md",
|
|
19
17
|
"rounded-[20px]",
|
|
20
18
|
"cursor-pointer select-none",
|
|
@@ -25,54 +23,100 @@ var m = e([
|
|
|
25
23
|
"data-disabled:cursor-not-allowed data-disabled:opacity-dim-3",
|
|
26
24
|
"data-checked:text-on-support-container",
|
|
27
25
|
"data-checked:[&>[data-spark-segmented-control-text]]:[text-shadow:0.35px_0_currentColor,-0.35px_0_currentColor]"
|
|
28
|
-
]),
|
|
26
|
+
]), h = e([
|
|
29
27
|
"absolute z-base",
|
|
30
28
|
"rounded-[20px]",
|
|
31
29
|
"bg-support-container border-md border-support",
|
|
32
30
|
"group-has-focus-visible:border-focus",
|
|
33
31
|
"transition-[left,top,width,height] duration-200 ease-in-out",
|
|
34
32
|
"pointer-events-none"
|
|
35
|
-
]),
|
|
36
|
-
let e = i(
|
|
33
|
+
]), g = n({}), _ = () => {
|
|
34
|
+
let e = i(g);
|
|
37
35
|
if (!e) throw Error("useSegmentedControlContext must be used within a SegmentedControlContext Provider");
|
|
38
36
|
return e;
|
|
39
|
-
},
|
|
37
|
+
}, v = ({ itemValues: e, containerRef: t, onValueChange: n }) => ({ handleKeyDown: (r) => {
|
|
38
|
+
let i = r.target.getAttribute("data-value");
|
|
39
|
+
if (!i) return;
|
|
40
|
+
let a = e.indexOf(i);
|
|
41
|
+
if (a === -1) return;
|
|
42
|
+
let o = null;
|
|
43
|
+
if (o = y(r.key, a, e.length), o !== null && o !== a) {
|
|
44
|
+
r.preventDefault();
|
|
45
|
+
let i = 0, a = e.length;
|
|
46
|
+
for (; i < a;) {
|
|
47
|
+
let a = e[o];
|
|
48
|
+
if (!a) return;
|
|
49
|
+
let s = t.current?.querySelector(`[data-value="${CSS.escape(a)}"]`);
|
|
50
|
+
if (s && !s.hasAttribute("data-disabled")) {
|
|
51
|
+
s.focus(), n(a);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let c = r.key === "ArrowRight" || r.key === "ArrowDown" ? 1 : -1;
|
|
55
|
+
o = (o + c + e.length) % e.length, i++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} });
|
|
59
|
+
function y(e, t, n) {
|
|
60
|
+
switch (e) {
|
|
61
|
+
case "ArrowRight":
|
|
62
|
+
case "ArrowDown": return (t + 1) % n;
|
|
63
|
+
case "ArrowLeft":
|
|
64
|
+
case "ArrowUp": return (t - 1 + n) % n;
|
|
65
|
+
default: return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/segmented-control/SegmentedControl.tsx
|
|
70
|
+
var b = (e) => {
|
|
40
71
|
let n = null;
|
|
41
72
|
return t.forEach(e, (e) => {
|
|
42
73
|
n === null && r(e) && typeof e.props.value == "string" && (n = e.props.value);
|
|
43
74
|
}), n;
|
|
44
|
-
},
|
|
45
|
-
let
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
75
|
+
}, x = ({ value: e, defaultValue: n, onValueChange: i, className: a, children: o, rowLength: u, name: m, ref: h, ..._ }) => {
|
|
76
|
+
let y = s(null), x = d(y, h), S = b(o), C = e !== void 0, [w, T] = c(() => n ?? S), E = C ? e ?? null : w, D = (e) => {
|
|
77
|
+
C || T(e), i?.(e);
|
|
78
|
+
}, { labelId: O, description: k, isRequired: A, isInvalid: j, name: M } = f(), N = m ?? M, P = [];
|
|
79
|
+
t.forEach(o, (e) => {
|
|
80
|
+
r(e) && typeof e.props.value == "string" && P.push(e.props.value);
|
|
81
|
+
});
|
|
82
|
+
let { handleKeyDown: F } = v({
|
|
83
|
+
itemValues: P,
|
|
84
|
+
containerRef: y,
|
|
85
|
+
onValueChange: D
|
|
86
|
+
}), I = u ? {
|
|
87
|
+
"--segmented-control-cols": u,
|
|
88
|
+
rowGap: "var(--spacing-md)"
|
|
89
|
+
} : void 0;
|
|
90
|
+
return /* @__PURE__ */ l(g, {
|
|
50
91
|
value: {
|
|
51
|
-
checkedValue:
|
|
52
|
-
containerRef:
|
|
92
|
+
checkedValue: E,
|
|
93
|
+
containerRef: y,
|
|
94
|
+
onValueChange: D,
|
|
95
|
+
name: N,
|
|
96
|
+
rowLength: u,
|
|
97
|
+
itemValues: P
|
|
53
98
|
},
|
|
54
|
-
children: /* @__PURE__ */ l(
|
|
55
|
-
ref:
|
|
56
|
-
|
|
57
|
-
defaultValue: v ? void 0 : t ?? g ?? void 0,
|
|
58
|
-
onValueChange: C,
|
|
99
|
+
children: /* @__PURE__ */ l("div", {
|
|
100
|
+
ref: x,
|
|
101
|
+
role: "radiogroup",
|
|
59
102
|
"data-spark-component": "segmented-control",
|
|
60
|
-
className:
|
|
61
|
-
|
|
62
|
-
"aria-
|
|
63
|
-
"aria-
|
|
64
|
-
"aria-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
103
|
+
className: p({ className: a }),
|
|
104
|
+
style: I,
|
|
105
|
+
"aria-labelledby": O,
|
|
106
|
+
"aria-describedby": k,
|
|
107
|
+
"aria-required": A || void 0,
|
|
108
|
+
"aria-invalid": j || void 0,
|
|
109
|
+
onKeyDown: F,
|
|
110
|
+
..._,
|
|
111
|
+
children: o
|
|
68
112
|
})
|
|
69
113
|
});
|
|
70
114
|
};
|
|
71
|
-
|
|
115
|
+
x.displayName = "SegmentedControl";
|
|
72
116
|
//#endregion
|
|
73
117
|
//#region src/segmented-control/SegmentedControlIndicator.tsx
|
|
74
|
-
var
|
|
75
|
-
let { checkedValue: r, containerRef: i } =
|
|
118
|
+
var S = ({ className: e, ref: t, ...n }) => {
|
|
119
|
+
let { checkedValue: r, containerRef: i } = _(), [s, u] = c(null), d = o(() => r ? `[data-value="${CSS.escape(r)}"]` : null, [r]);
|
|
76
120
|
if (a(() => {
|
|
77
121
|
let e = i.current;
|
|
78
122
|
if (!e) return;
|
|
@@ -113,39 +157,67 @@ var x = ({ className: e, ref: t, ...n }) => {
|
|
|
113
157
|
ref: t,
|
|
114
158
|
"data-spark-component": "segmented-control-indicator",
|
|
115
159
|
"aria-hidden": !0,
|
|
116
|
-
className:
|
|
160
|
+
className: h({ className: e }),
|
|
117
161
|
style: f,
|
|
118
162
|
...n
|
|
119
163
|
});
|
|
120
164
|
};
|
|
121
|
-
|
|
165
|
+
S.displayName = "SegmentedControl.Indicator";
|
|
122
166
|
//#endregion
|
|
123
167
|
//#region src/segmented-control/SegmentedControlItem.tsx
|
|
124
|
-
var
|
|
125
|
-
let s
|
|
168
|
+
var C = ({ value: e, disabled: n = !1, children: r, className: i, ref: a, ...o }) => {
|
|
169
|
+
let { checkedValue: s, onValueChange: c, name: d, rowLength: f, itemValues: p } = _(), h = s === e, g = () => {
|
|
170
|
+
n || c(e);
|
|
171
|
+
}, v = t.toArray(r).map((e, t) => typeof e == "string" || typeof e == "number" ? /* @__PURE__ */ l("span", {
|
|
126
172
|
"data-spark-segmented-control-text": !0,
|
|
127
173
|
children: e
|
|
128
|
-
}, `text-${t}`) : e);
|
|
129
|
-
|
|
174
|
+
}, `text-${t}`) : e), y = p.indexOf(e), b = f ? { flexBasis: "calc(100% / var(--segmented-control-cols))" } : void 0, x = !1;
|
|
175
|
+
if (f && f > 0 && y !== -1) {
|
|
176
|
+
let e = f, t = y % e;
|
|
177
|
+
x = Math.floor(y / e) < Math.ceil(p.length / e) - 1 && t === 0;
|
|
178
|
+
}
|
|
179
|
+
return /* @__PURE__ */ u("button", {
|
|
130
180
|
ref: a,
|
|
181
|
+
type: "button",
|
|
182
|
+
role: "radio",
|
|
131
183
|
"data-spark-component": "segmented-control-item",
|
|
132
184
|
"data-value": e,
|
|
133
|
-
|
|
185
|
+
"aria-checked": h,
|
|
186
|
+
"data-checked": h || void 0,
|
|
187
|
+
"data-disabled": n || void 0,
|
|
134
188
|
disabled: n,
|
|
135
|
-
|
|
189
|
+
tabIndex: h ? 0 : -1,
|
|
190
|
+
className: m({ className: i }),
|
|
191
|
+
style: b,
|
|
192
|
+
onClick: g,
|
|
136
193
|
...o,
|
|
137
|
-
children:
|
|
194
|
+
children: [
|
|
195
|
+
v,
|
|
196
|
+
d && h && /* @__PURE__ */ l("input", {
|
|
197
|
+
type: "hidden",
|
|
198
|
+
name: d,
|
|
199
|
+
value: e
|
|
200
|
+
}),
|
|
201
|
+
x && f && /* @__PURE__ */ l("div", {
|
|
202
|
+
className: "bg-outline/dim-3 -mx-sm absolute left-0 h-px",
|
|
203
|
+
style: {
|
|
204
|
+
bottom: "calc(var(--spacing-md) / -2)",
|
|
205
|
+
width: `calc(${f * 100}% + var(--spacing-sm) * 2)`
|
|
206
|
+
},
|
|
207
|
+
"aria-hidden": "true"
|
|
208
|
+
})
|
|
209
|
+
]
|
|
138
210
|
});
|
|
139
211
|
};
|
|
140
|
-
|
|
212
|
+
C.displayName = "SegmentedControl.Item";
|
|
141
213
|
//#endregion
|
|
142
214
|
//#region src/segmented-control/index.ts
|
|
143
|
-
var
|
|
144
|
-
Item:
|
|
145
|
-
Indicator:
|
|
215
|
+
var w = Object.assign(x, {
|
|
216
|
+
Item: C,
|
|
217
|
+
Indicator: S
|
|
146
218
|
});
|
|
147
|
-
|
|
219
|
+
w.displayName = "SegmentedControl", C.displayName = "SegmentedControl.Item", S.displayName = "SegmentedControl.Indicator";
|
|
148
220
|
//#endregion
|
|
149
|
-
export {
|
|
221
|
+
export { w as SegmentedControl };
|
|
150
222
|
|
|
151
223
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/segmented-control/SegmentedControl.styles.ts","../../src/segmented-control/SegmentedControlContext.tsx","../../src/segmented-control/SegmentedControl.tsx","../../src/segmented-control/SegmentedControlIndicator.tsx","../../src/segmented-control/SegmentedControlItem.tsx","../../src/segmented-control/index.ts"],"sourcesContent":["import { cva, VariantProps } from 'class-variance-authority'\n\nexport const rootStyles = cva([\n 'default:self-start',\n 'group inline-grid grid-flow-col auto-cols-fr',\n 'relative items-stretch min-w-max',\n 'rounded-xl p-sm',\n 'bg-surface border-sm border-outline',\n])\n\nexport const itemStyles = cva([\n 'relative z-raised min-h-sz-44 focus-visible:outline-none',\n 'flex flex-none items-center justify-center gap-md',\n 'default:px-lg default:py-md',\n 'rounded-[20px]',\n 'cursor-pointer select-none',\n 'font-medium',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:u-outline',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-dim-3',\n 'data-checked:text-on-support-container',\n // Avoid layout shift: simulate \"bold\" without changing font metrics.\n // Apply only to wrapped text nodes (not arbitrary nested JSX like Tag).\n 'data-checked:[&>[data-spark-segmented-control-text]]:[text-shadow:0.35px_0_currentColor,-0.35px_0_currentColor]',\n])\n\nexport const indicatorStyles = cva([\n 'absolute z-base',\n 'rounded-[20px]',\n 'bg-support-container border-md border-support',\n 'group-has-focus-visible:border-focus',\n 'transition-[left,top,width,height] duration-200 ease-in-out',\n 'pointer-events-none',\n])\n\nexport type SegmentedControlStylesProps = VariantProps<typeof itemStyles>\n","import { createContext, RefObject, useContext } from 'react'\n\nexport interface SegmentedControlContextInterface {\n checkedValue: string | null\n containerRef: RefObject<HTMLDivElement | null>\n}\n\nexport const SegmentedControlContext = createContext<SegmentedControlContextInterface>(\n {} as SegmentedControlContextInterface\n)\n\nexport const useSegmentedControlContext = () => {\n const context = useContext(SegmentedControlContext)\n\n if (!context) {\n throw Error('useSegmentedControlContext must be used within a SegmentedControlContext Provider')\n }\n\n return context\n}\n","import { RadioGroup } from '@base-ui/react/radio-group'\nimport { useFormFieldControl } from '@spark-ui/components/form-field'\nimport { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { Children, type ComponentProps, isValidElement, Ref, useRef, useState } from 'react'\n\nimport type { SegmentedControlStylesProps } from './SegmentedControl.styles'\nimport { rootStyles } from './SegmentedControl.styles'\nimport { SegmentedControlContext } from './SegmentedControlContext'\n\nexport interface SegmentedControlProps\n extends\n Omit<ComponentProps<typeof RadioGroup>, 'value' | 'defaultValue' | 'onValueChange'>,\n SegmentedControlStylesProps {\n /**\n * The controlled selected value.\n */\n value?: string\n /**\n * The uncontrolled default selected value.\n */\n defaultValue?: string\n /**\n * Callback fired when the selected value changes.\n */\n onValueChange?: (value: string) => void\n ref?: Ref<HTMLDivElement>\n}\n\nconst getFirstItemValue = (children: React.ReactNode): string | null => {\n let firstValue: string | null = null\n\n Children.forEach(children, child => {\n if (firstValue !== null) return\n if (isValidElement(child) && typeof (child.props as { value?: string }).value === 'string') {\n firstValue = (child.props as { value: string }).value\n }\n })\n\n return firstValue\n}\n\nexport const SegmentedControl = ({\n value,\n defaultValue,\n onValueChange,\n className,\n children,\n ref,\n ...rest\n}: SegmentedControlProps) => {\n const containerRef = useRef<HTMLDivElement | null>(null)\n const mergedRef = useMergeRefs(containerRef, ref)\n\n const firstValue = getFirstItemValue(children)\n\n const isControlled = value !== undefined\n const [internalValue, setInternalValue] = useState<string | null>(\n () => defaultValue ?? firstValue\n )\n const checkedValue = isControlled ? (value ?? null) : internalValue\n\n const handleValueChange = (newValue: unknown) => {\n const next = newValue as string\n\n if (!isControlled) {\n setInternalValue(next)\n }\n\n onValueChange?.(next)\n }\n\n const { labelId, description, isRequired, isInvalid, name } = useFormFieldControl()\n\n return (\n <SegmentedControlContext\n value={{\n checkedValue,\n containerRef,\n }}\n >\n <RadioGroup\n ref={mergedRef}\n value={isControlled ? value : undefined}\n defaultValue={!isControlled ? (defaultValue ?? firstValue ?? undefined) : undefined}\n onValueChange={handleValueChange}\n data-spark-component=\"segmented-control\"\n className={rootStyles({ className })}\n aria-labelledby={labelId}\n aria-describedby={description}\n aria-required={isRequired || undefined}\n aria-invalid={isInvalid || undefined}\n name={name}\n {...rest}\n >\n {children}\n </RadioGroup>\n </SegmentedControlContext>\n )\n}\n\nSegmentedControl.displayName = 'SegmentedControl'\n","import { type ComponentProps, type CSSProperties, Ref, useEffect, useMemo, useState } from 'react'\n\nimport { indicatorStyles } from './SegmentedControl.styles'\nimport { useSegmentedControlContext } from './SegmentedControlContext'\n\ninterface IndicatorRect {\n left: number\n top: number\n width: number\n height: number\n}\n\nexport interface SegmentedControlIndicatorProps extends ComponentProps<'span'> {\n ref?: Ref<HTMLSpanElement>\n}\n\n/** The visual indicator that highlights the selected item. Renders a <span> element. */\nexport const SegmentedControlIndicator = ({\n className,\n ref,\n ...rest\n}: SegmentedControlIndicatorProps) => {\n const { checkedValue, containerRef } = useSegmentedControlContext()\n const [rect, setRect] = useState<IndicatorRect | null>(null)\n\n const selector = useMemo(\n () => (checkedValue ? `[data-value=\"${CSS.escape(checkedValue)}\"]` : null),\n [checkedValue]\n )\n\n useEffect(() => {\n const container = containerRef.current\n\n if (!container) {\n return\n }\n\n const selectedItem = selector ? container.querySelector<HTMLElement>(selector) : null\n\n const update = () => {\n const currentContainer = containerRef.current\n if (!currentContainer || !selector) {\n setRect(null)\n\n return\n }\n\n const currentSelected = currentContainer.querySelector<HTMLElement>(selector)\n if (!currentSelected) {\n setRect(null)\n\n return\n }\n\n const containerRect = currentContainer.getBoundingClientRect()\n const itemRect = currentSelected.getBoundingClientRect()\n\n // Storybook canvas \"zoom\" can be implemented via `transform: scale()`.\n // In that case, `getBoundingClientRect()` returns *scaled* values, but CSS positioning/sizing\n // expects unscaled layout pixels. We infer the scale factor from offset sizes and normalize.\n const scaleX =\n currentSelected.offsetWidth > 0 ? itemRect.width / currentSelected.offsetWidth : 1\n const scaleY =\n currentSelected.offsetHeight > 0 ? itemRect.height / currentSelected.offsetHeight : 1\n\n // `getBoundingClientRect()` is border-box; absolute positioning is relative to the padding box.\n setRect({\n left: (itemRect.left - containerRect.left) / scaleX - currentContainer.clientLeft,\n top: (itemRect.top - containerRect.top) / scaleY - currentContainer.clientTop,\n width: itemRect.width / scaleX,\n height: itemRect.height / scaleY,\n })\n }\n\n update()\n\n const ro =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => {\n update()\n })\n : null\n\n ro?.observe(container)\n if (selectedItem) ro?.observe(selectedItem)\n\n window.addEventListener('resize', update, { passive: true })\n window.visualViewport?.addEventListener('resize', update, { passive: true })\n\n return () => {\n ro?.disconnect()\n window.removeEventListener('resize', update)\n window.visualViewport?.removeEventListener('resize', update)\n }\n }, [containerRef, selector])\n\n if (!rect) return null\n\n const style: CSSProperties = {\n left: rect.left,\n top: rect.top,\n width: rect.width,\n height: rect.height,\n }\n\n return (\n <span\n ref={ref}\n data-spark-component=\"segmented-control-indicator\"\n aria-hidden\n className={indicatorStyles({ className })}\n style={style}\n {...rest}\n />\n )\n}\n\nSegmentedControlIndicator.displayName = 'SegmentedControl.Indicator'\n","import { Radio } from '@base-ui/react/radio'\nimport { Children, type ComponentProps, Ref } from 'react'\n\nimport { itemStyles } from './SegmentedControl.styles'\n\nexport interface SegmentedControlItemProps extends Omit<\n ComponentProps<typeof Radio.Root>,\n 'value'\n> {\n /**\n * A unique value that identifies this item within the segmented control.\n */\n value: string\n /**\n * When true, prevents the user from interacting with this item.\n * @default false\n */\n disabled?: boolean\n ref?: Ref<HTMLElement>\n}\n\n/** A selectable item in the segmented control. Renders a <button> element. */\nexport const SegmentedControlItem = ({\n value,\n disabled = false,\n children,\n className,\n ref,\n ...rest\n}: SegmentedControlItemProps) => {\n const content = Children.toArray(children).map((child, index) => {\n if (typeof child === 'string' || typeof child === 'number') {\n return (\n <span key={`text-${index}`} data-spark-segmented-control-text>\n {child}\n </span>\n )\n }\n\n return child\n })\n\n return (\n <Radio.Root\n ref={ref}\n data-spark-component=\"segmented-control-item\"\n data-value={value}\n value={value}\n disabled={disabled}\n className={itemStyles({ className })}\n {...rest}\n >\n {content}\n </Radio.Root>\n )\n}\n\nSegmentedControlItem.displayName = 'SegmentedControl.Item'\n","import { SegmentedControl as Root } from './SegmentedControl'\nimport { SegmentedControlIndicator as Indicator } from './SegmentedControlIndicator'\nimport { SegmentedControlItem as Item } from './SegmentedControlItem'\n\n/**\n * A set of toggle buttons that allows users to select a single option from a group of related choices.\n */\nexport const SegmentedControl: typeof Root & {\n Item: typeof Item\n Indicator: typeof Indicator\n} = Object.assign(Root, {\n Item,\n Indicator,\n})\n\nSegmentedControl.displayName = 'SegmentedControl'\nItem.displayName = 'SegmentedControl.Item'\nIndicator.displayName = 'SegmentedControl.Indicator'\n\nexport type { SegmentedControlProps } from './SegmentedControl'\nexport type { SegmentedControlItemProps } from './SegmentedControlItem'\nexport type { SegmentedControlIndicatorProps } from './SegmentedControlIndicator'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/segmented-control/SegmentedControl.styles.ts","../../src/segmented-control/SegmentedControlContext.tsx","../../src/segmented-control/useSegmentedControlNavigation.ts","../../src/segmented-control/SegmentedControl.tsx","../../src/segmented-control/SegmentedControlIndicator.tsx","../../src/segmented-control/SegmentedControlItem.tsx","../../src/segmented-control/index.ts"],"sourcesContent":["import { cva, VariantProps } from 'class-variance-authority'\n\nexport const rootStyles = cva([\n 'default:self-start',\n 'group inline-flex flex-wrap',\n 'relative items-stretch min-w-max',\n 'rounded-xl p-sm',\n 'bg-surface border-sm border-outline',\n])\n\nexport const itemStyles = cva([\n 'relative z-raised min-h-sz-44 focus-visible:outline-none',\n 'flex flex-auto items-center justify-center gap-md',\n 'default:px-lg default:py-md',\n 'rounded-[20px]',\n 'cursor-pointer select-none',\n 'font-medium',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:u-outline',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-dim-3',\n 'data-checked:text-on-support-container',\n // Avoid layout shift: simulate \"bold\" without changing font metrics.\n // Apply only to wrapped text nodes (not arbitrary nested JSX like Tag).\n 'data-checked:[&>[data-spark-segmented-control-text]]:[text-shadow:0.35px_0_currentColor,-0.35px_0_currentColor]',\n])\n\nexport const indicatorStyles = cva([\n 'absolute z-base',\n 'rounded-[20px]',\n 'bg-support-container border-md border-support',\n 'group-has-focus-visible:border-focus',\n 'transition-[left,top,width,height] duration-200 ease-in-out',\n 'pointer-events-none',\n])\n\nexport type SegmentedControlStylesProps = VariantProps<typeof itemStyles>\n","import { createContext, RefObject, useContext } from 'react'\n\nexport interface SegmentedControlContextInterface {\n checkedValue: string | null\n containerRef: RefObject<HTMLDivElement | null>\n onValueChange: (value: string) => void\n name?: string\n rowLength?: number\n itemValues: string[]\n}\n\nexport const SegmentedControlContext = createContext<SegmentedControlContextInterface>(\n {} as SegmentedControlContextInterface\n)\n\nexport const useSegmentedControlContext = () => {\n const context = useContext(SegmentedControlContext)\n\n if (!context) {\n throw Error('useSegmentedControlContext must be used within a SegmentedControlContext Provider')\n }\n\n return context\n}\n","import { KeyboardEvent, RefObject } from 'react'\n\ninterface UseSegmentedControlNavigationProps {\n itemValues: string[]\n containerRef: RefObject<HTMLDivElement | null>\n onValueChange: (value: string) => void\n}\n\n/**\n * Custom hook that handles keyboard navigation for SegmentedControl.\n * Uses sequential left/right navigation.\n */\nexport const useSegmentedControlNavigation = ({\n itemValues,\n containerRef,\n onValueChange,\n}: UseSegmentedControlNavigationProps) => {\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n // Find the currently focused item (which may differ from checkedValue during keyboard navigation)\n const focusedElement = e.target as HTMLElement\n const focusedValue = focusedElement.getAttribute('data-value')\n\n if (!focusedValue) return\n\n const currentIndex = itemValues.indexOf(focusedValue)\n if (currentIndex === -1) return\n\n let nextIndex: number | null = null\n\n // Always use 1D sequential navigation (left/right only)\n nextIndex = calculate1DNavigation(e.key, currentIndex, itemValues.length)\n\n if (nextIndex !== null && nextIndex !== currentIndex) {\n e.preventDefault()\n\n // Skip disabled items\n let attempts = 0\n const maxAttempts = itemValues.length\n\n while (attempts < maxAttempts) {\n const nextValue = itemValues[nextIndex]\n if (!nextValue) return\n\n const nextItem = containerRef.current?.querySelector<HTMLElement>(\n `[data-value=\"${CSS.escape(nextValue)}\"]`\n )\n\n // If the item is not disabled, focus it and update the value\n if (nextItem && !nextItem.hasAttribute('data-disabled')) {\n nextItem.focus()\n onValueChange(nextValue)\n return\n }\n\n // If disabled, continue in the same direction\n const direction = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1 : -1\n nextIndex = (nextIndex + direction + itemValues.length) % itemValues.length\n attempts++\n }\n }\n }\n\n return { handleKeyDown }\n}\n\n/**\n * Calculate next index for 1D sequential navigation.\n * Navigation wraps around at the boundaries.\n */\nfunction calculate1DNavigation(\n key: string,\n currentIndex: number,\n totalItems: number\n): number | null {\n switch (key) {\n case 'ArrowRight':\n case 'ArrowDown':\n return (currentIndex + 1) % totalItems\n case 'ArrowLeft':\n case 'ArrowUp':\n return (currentIndex - 1 + totalItems) % totalItems\n default:\n return null\n }\n}\n","import { useFormFieldControl } from '@spark-ui/components/form-field'\nimport { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport {\n Children,\n type ComponentProps,\n CSSProperties,\n isValidElement,\n Ref,\n useRef,\n useState,\n} from 'react'\n\nimport type { SegmentedControlStylesProps } from './SegmentedControl.styles'\nimport { rootStyles } from './SegmentedControl.styles'\nimport { SegmentedControlContext } from './SegmentedControlContext'\nimport { useSegmentedControlNavigation } from './useSegmentedControlNavigation'\n\nexport interface SegmentedControlProps\n extends Omit<ComponentProps<'div'>, 'onValueChange'>, SegmentedControlStylesProps {\n /**\n * The controlled selected value.\n */\n value?: string\n /**\n * The uncontrolled default selected value.\n */\n defaultValue?: string\n /**\n * Callback fired when the selected value changes.\n */\n onValueChange?: (value: string) => void\n /**\n * Number of items per row in multi-row layout.\n * When undefined, items display in a single row (default behavior).\n * @default undefined\n * @example\n * // Create 3-column grid with wrapping rows\n * <SegmentedControl rowLength={3}>\n */\n rowLength?: number\n /**\n * The name attribute for the radio group (used in form submissions).\n */\n name?: string\n ref?: Ref<HTMLDivElement>\n}\n\nconst getFirstItemValue = (children: React.ReactNode): string | null => {\n let firstValue: string | null = null\n\n Children.forEach(children, child => {\n if (firstValue !== null) return\n if (isValidElement(child) && typeof (child.props as { value?: string }).value === 'string') {\n firstValue = (child.props as { value: string }).value\n }\n })\n\n return firstValue\n}\n\nexport const SegmentedControl = ({\n value,\n defaultValue,\n onValueChange,\n className,\n children,\n rowLength,\n name: nameProp,\n ref,\n ...rest\n}: SegmentedControlProps) => {\n const containerRef = useRef<HTMLDivElement | null>(null)\n const mergedRef = useMergeRefs(containerRef, ref)\n\n const firstValue = getFirstItemValue(children)\n\n const isControlled = value !== undefined\n const [internalValue, setInternalValue] = useState<string | null>(\n () => defaultValue ?? firstValue\n )\n const checkedValue = isControlled ? (value ?? null) : internalValue\n\n const handleValueChange = (newValue: string) => {\n if (!isControlled) {\n setInternalValue(newValue)\n }\n\n onValueChange?.(newValue)\n }\n\n const { labelId, description, isRequired, isInvalid, name: nameFromField } = useFormFieldControl()\n const name = nameProp ?? nameFromField\n\n // Get all item values in order\n const itemValues: string[] = []\n Children.forEach(children, child => {\n if (isValidElement(child) && typeof (child.props as { value?: string }).value === 'string') {\n itemValues.push((child.props as { value: string }).value)\n }\n })\n\n // Keyboard navigation (sequential left/right)\n const { handleKeyDown } = useSegmentedControlNavigation({\n itemValues,\n containerRef,\n onValueChange: handleValueChange,\n })\n\n // Compute dynamic flex styles for multi-row layout\n const flexStyles = rowLength\n ? ({\n '--segmented-control-cols': rowLength,\n rowGap: 'var(--spacing-md)',\n } as CSSProperties)\n : undefined\n\n return (\n <SegmentedControlContext\n value={{\n checkedValue,\n containerRef,\n onValueChange: handleValueChange,\n name,\n rowLength,\n itemValues,\n }}\n >\n <div\n ref={mergedRef}\n role=\"radiogroup\"\n data-spark-component=\"segmented-control\"\n className={rootStyles({ className })}\n style={flexStyles}\n aria-labelledby={labelId}\n aria-describedby={description}\n aria-required={isRequired || undefined}\n aria-invalid={isInvalid || undefined}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {children}\n </div>\n </SegmentedControlContext>\n )\n}\n\nSegmentedControl.displayName = 'SegmentedControl'\n","import { type ComponentProps, type CSSProperties, Ref, useEffect, useMemo, useState } from 'react'\n\nimport { indicatorStyles } from './SegmentedControl.styles'\nimport { useSegmentedControlContext } from './SegmentedControlContext'\n\ninterface IndicatorRect {\n left: number\n top: number\n width: number\n height: number\n}\n\nexport interface SegmentedControlIndicatorProps extends ComponentProps<'span'> {\n ref?: Ref<HTMLSpanElement>\n}\n\n/** The visual indicator that highlights the selected item. Renders a <span> element. */\nexport const SegmentedControlIndicator = ({\n className,\n ref,\n ...rest\n}: SegmentedControlIndicatorProps) => {\n const { checkedValue, containerRef } = useSegmentedControlContext()\n const [rect, setRect] = useState<IndicatorRect | null>(null)\n\n const selector = useMemo(\n () => (checkedValue ? `[data-value=\"${CSS.escape(checkedValue)}\"]` : null),\n [checkedValue]\n )\n\n useEffect(() => {\n const container = containerRef.current\n\n if (!container) {\n return\n }\n\n const selectedItem = selector ? container.querySelector<HTMLElement>(selector) : null\n\n const update = () => {\n const currentContainer = containerRef.current\n if (!currentContainer || !selector) {\n setRect(null)\n\n return\n }\n\n const currentSelected = currentContainer.querySelector<HTMLElement>(selector)\n if (!currentSelected) {\n setRect(null)\n\n return\n }\n\n const containerRect = currentContainer.getBoundingClientRect()\n const itemRect = currentSelected.getBoundingClientRect()\n\n // Storybook canvas \"zoom\" can be implemented via `transform: scale()`.\n // In that case, `getBoundingClientRect()` returns *scaled* values, but CSS positioning/sizing\n // expects unscaled layout pixels. We infer the scale factor from offset sizes and normalize.\n const scaleX =\n currentSelected.offsetWidth > 0 ? itemRect.width / currentSelected.offsetWidth : 1\n const scaleY =\n currentSelected.offsetHeight > 0 ? itemRect.height / currentSelected.offsetHeight : 1\n\n // `getBoundingClientRect()` is border-box; absolute positioning is relative to the padding box.\n setRect({\n left: (itemRect.left - containerRect.left) / scaleX - currentContainer.clientLeft,\n top: (itemRect.top - containerRect.top) / scaleY - currentContainer.clientTop,\n width: itemRect.width / scaleX,\n height: itemRect.height / scaleY,\n })\n }\n\n update()\n\n const ro =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => {\n update()\n })\n : null\n\n ro?.observe(container)\n if (selectedItem) ro?.observe(selectedItem)\n\n window.addEventListener('resize', update, { passive: true })\n window.visualViewport?.addEventListener('resize', update, { passive: true })\n\n return () => {\n ro?.disconnect()\n window.removeEventListener('resize', update)\n window.visualViewport?.removeEventListener('resize', update)\n }\n }, [containerRef, selector])\n\n if (!rect) return null\n\n const style: CSSProperties = {\n left: rect.left,\n top: rect.top,\n width: rect.width,\n height: rect.height,\n }\n\n return (\n <span\n ref={ref}\n data-spark-component=\"segmented-control-indicator\"\n aria-hidden\n className={indicatorStyles({ className })}\n style={style}\n {...rest}\n />\n )\n}\n\nSegmentedControlIndicator.displayName = 'SegmentedControl.Indicator'\n","import { Children, type ComponentProps, Ref } from 'react'\n\nimport { itemStyles } from './SegmentedControl.styles'\nimport { useSegmentedControlContext } from './SegmentedControlContext'\n\nexport interface SegmentedControlItemProps extends Omit<\n ComponentProps<'button'>,\n 'value' | 'onClick'\n> {\n /**\n * A unique value that identifies this item within the segmented control.\n */\n value: string\n /**\n * When true, prevents the user from interacting with this item.\n * @default false\n */\n disabled?: boolean\n ref?: Ref<HTMLButtonElement>\n}\n\n/** A selectable item in the segmented control. Renders a <button> element. */\nexport const SegmentedControlItem = ({\n value,\n disabled = false,\n children,\n className,\n ref,\n ...rest\n}: SegmentedControlItemProps) => {\n const { checkedValue, onValueChange, name, rowLength, itemValues } = useSegmentedControlContext()\n\n const isChecked = checkedValue === value\n\n const handleClick = () => {\n if (!disabled) {\n onValueChange(value)\n }\n }\n\n const content = Children.toArray(children).map((child, index) => {\n if (typeof child === 'string' || typeof child === 'number') {\n return (\n <span key={`text-${index}`} data-spark-segmented-control-text>\n {child}\n </span>\n )\n }\n\n return child\n })\n\n // Calculate flex-basis for multi-row layout and horizontal separator visibility\n const itemIndex = itemValues.indexOf(value)\n const itemStyle = rowLength\n ? { flexBasis: `calc(100% / var(--segmented-control-cols))` }\n : undefined\n\n let showHorizontalSeparator = false\n\n if (rowLength && rowLength > 0 && itemIndex !== -1) {\n const cols = rowLength\n const currentCol = itemIndex % cols\n const currentRow = Math.floor(itemIndex / cols)\n const totalRows = Math.ceil(itemValues.length / cols)\n\n // Show horizontal separator only on the first column of each row (except last row)\n showHorizontalSeparator = currentRow < totalRows - 1 && currentCol === 0\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"radio\"\n data-spark-component=\"segmented-control-item\"\n data-value={value}\n aria-checked={isChecked}\n data-checked={isChecked || undefined}\n data-disabled={disabled || undefined}\n disabled={disabled}\n tabIndex={isChecked ? 0 : -1}\n className={itemStyles({ className })}\n style={itemStyle}\n onClick={handleClick}\n {...rest}\n >\n {content}\n {/* Hidden input for form submission */}\n {name && isChecked && <input type=\"hidden\" name={name} value={value} />}\n {/* Horizontal separator between rows (full width across all columns) */}\n {showHorizontalSeparator && rowLength && (\n <div\n className=\"bg-outline/dim-3 -mx-sm absolute left-0 h-px\"\n style={{\n bottom: 'calc(var(--spacing-md) / -2)',\n width: `calc(${rowLength * 100}% + var(--spacing-sm) * 2)`,\n }}\n aria-hidden=\"true\"\n />\n )}\n </button>\n )\n}\n\nSegmentedControlItem.displayName = 'SegmentedControl.Item'\n","import { SegmentedControl as Root } from './SegmentedControl'\nimport { SegmentedControlIndicator as Indicator } from './SegmentedControlIndicator'\nimport { SegmentedControlItem as Item } from './SegmentedControlItem'\n\n/**\n * A set of toggle buttons that allows users to select a single option from a group of related choices.\n */\nexport const SegmentedControl: typeof Root & {\n Item: typeof Item\n Indicator: typeof Indicator\n} = Object.assign(Root, {\n Item,\n Indicator,\n})\n\nSegmentedControl.displayName = 'SegmentedControl'\nItem.displayName = 'SegmentedControl.Item'\nIndicator.displayName = 'SegmentedControl.Indicator'\n\nexport type { SegmentedControlProps } from './SegmentedControl'\nexport type { SegmentedControlItemProps } from './SegmentedControlItem'\nexport type { SegmentedControlIndicatorProps } from './SegmentedControlIndicator'\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { KeyboardEvent, RefObject } from 'react';
|
|
2
|
+
interface UseSegmentedControlNavigationProps {
|
|
3
|
+
itemValues: string[];
|
|
4
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
5
|
+
onValueChange: (value: string) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Custom hook that handles keyboard navigation for SegmentedControl.
|
|
9
|
+
* Uses sequential left/right navigation.
|
|
10
|
+
*/
|
|
11
|
+
export declare const useSegmentedControlNavigation: ({ itemValues, containerRef, onValueChange, }: UseSegmentedControlNavigationProps) => {
|
|
12
|
+
handleKeyDown: (e: KeyboardEvent<HTMLDivElement>) => void;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spark-ui/components",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.13.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Spark (Leboncoin design system) components.",
|
|
6
6
|
"exports": {
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@base-ui/react": "^1.5.0",
|
|
51
|
-
"@spark-ui/hooks": "17.
|
|
52
|
-
"@spark-ui/icons": "17.
|
|
53
|
-
"@spark-ui/internal-utils": "17.
|
|
51
|
+
"@spark-ui/hooks": "17.13.1",
|
|
52
|
+
"@spark-ui/icons": "17.13.1",
|
|
53
|
+
"@spark-ui/internal-utils": "17.13.1",
|
|
54
54
|
"@zag-js/pagination": "1.30.0",
|
|
55
55
|
"@zag-js/react": "1.30.0",
|
|
56
56
|
"class-variance-authority": "0.7.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"react-snap-carousel": "0.5.1"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@spark-ui/theme-utils": "17.
|
|
65
|
+
"@spark-ui/theme-utils": "17.13.1",
|
|
66
66
|
"react": "19.2.4",
|
|
67
67
|
"react-dom": "19.2.4",
|
|
68
68
|
"tailwindcss": "4.1.18"
|