diff --git a/docs/public/__registry__/ui/segmented-control.json b/docs/public/__registry__/ui/segmented-control.json index 12b7d9fe0..aa28fc75a 100644 --- a/docs/public/__registry__/ui/segmented-control.json +++ b/docs/public/__registry__/ui/segmented-control.json @@ -7,7 +7,7 @@ { "name": "segmented-control.tsx", "type": "ui", - "content": "\"use client\";\n\nimport \"@seed-design/stylesheet/segmentedControl.css\";\nimport {\n useTabs,\n type TriggerProps,\n type UseTabsProps,\n} from \"@seed-design/react-tabs\";\nimport * as React from \"react\";\nimport clsx from \"clsx\";\nimport {\n segmentedControl,\n type SegmentedControlVariantProps,\n} from \"@seed-design/recipe/segmentedControl\";\nimport type { Assign } from \"../util/types\";\nexport interface SegmentedControlProps extends SegmentedControlVariantProps {}\n\nconst TabsContext = React.createContext<{\n api: ReturnType;\n} | null>(null);\n\nconst useTabsContext = () => {\n const context = React.useContext(TabsContext);\n if (!context)\n throw new Error(\n \"SegmentedControlOption cannot be rendered outside the SegmentedControl\",\n );\n\n return context;\n};\n\nexport interface SegmentedControlProps\n extends SegmentedControlVariantProps,\n Pick {}\n\ntype ReactSegmentedControlProps = SegmentedControlProps &\n Assign, UseTabsProps>;\n\nexport const SegmentedControl = React.forwardRef<\n // HTMLFieldSetElement,\n HTMLDivElement,\n ReactSegmentedControlProps\n>(({ className, children, style, ...otherProps }, ref) => {\n const api = useTabs(otherProps);\n const { tabTriggerListProps, triggerSize, tabIndicatorProps } = api;\n\n const { left, width } = triggerSize;\n\n // TODO: value/defaultvalue 없는 경우 첫 번째 아이템으로 default (tabs 참고)\n\n const classNames = segmentedControl();\n\n return (\n \n {children}\n \n \n );\n});\nSegmentedControl.displayName = \"SegmentedControl\";\n\nexport interface SegmentedControlOptionProps\n extends SegmentedControlVariantProps,\n Omit {}\n\ntype ReactSegmentedControlOptionProps = Assign<\n React.HTMLAttributes,\n SegmentedControlOptionProps\n>;\n\nexport const SegmentedControlOption = React.forwardRef<\n HTMLButtonElement,\n ReactSegmentedControlOptionProps\n>(({ className, children, value, ...otherProps }, ref) => {\n const {\n api: { getTabTriggerProps },\n } = useTabsContext();\n\n const { rootProps, labelProps } = getTabTriggerProps({ value });\n\n const classNames = segmentedControl();\n\n return (\n \n
\n {children}\n
\n
\n {children}\n
\n \n );\n});\n\nSegmentedControlOption.displayName = \"SegmentedControlOption\";\n" + "content": "\"use client\";\n\nimport \"@seed-design/stylesheet/segmentedControl.css\";\nimport {\n useTabs,\n type TriggerProps,\n type UseTabsProps,\n} from \"@seed-design/react-tabs\";\nimport * as React from \"react\";\nimport clsx from \"clsx\";\nimport {\n segmentedControl,\n type SegmentedControlVariantProps,\n} from \"@seed-design/recipe/segmentedControl\";\nimport type { Assign } from \"../util/types\";\nexport interface SegmentedControlProps extends SegmentedControlVariantProps {}\n\nconst TabsContext = React.createContext<{\n api: ReturnType;\n} | null>(null);\n\nconst useTabsContext = () => {\n const context = React.useContext(TabsContext);\n if (!context)\n throw new Error(\n \"SegmentedControlOption cannot be rendered outside the SegmentedControl\",\n );\n\n return context;\n};\n\nexport interface SegmentedControlProps\n extends SegmentedControlVariantProps,\n Pick {}\n\ntype ReactSegmentedControlProps = SegmentedControlProps &\n Assign, UseTabsProps>;\n\nexport const SegmentedControl = React.forwardRef<\n // HTMLFieldSetElement,\n HTMLDivElement,\n ReactSegmentedControlProps\n>(({ className, children, style, ...otherProps }, ref) => {\n const api = useTabs(otherProps);\n const { tabTriggerListProps, triggerSize, tabIndicatorProps } = api;\n\n const { left, width } = triggerSize;\n\n // TODO: value/defaultvalue 없는 경우 첫 번째 아이템으로 default (tabs 참고)\n\n const classNames = segmentedControl();\n\n return (\n \n {children}\n \n \n );\n});\nSegmentedControl.displayName = \"SegmentedControl\";\n\nexport interface SegmentedControlOptionProps\n extends SegmentedControlVariantProps,\n Omit {}\n\ntype ReactSegmentedControlOptionProps = Assign<\n React.HTMLAttributes,\n SegmentedControlOptionProps\n>;\n\nexport const SegmentedControlOption = React.forwardRef<\n HTMLButtonElement,\n ReactSegmentedControlOptionProps\n>(({ className, children, value, ...otherProps }, ref) => {\n const {\n api: { getTabTriggerProps },\n } = useTabsContext();\n\n const { rootProps, labelProps } = getTabTriggerProps({ value });\n\n const classNames = segmentedControl();\n\n return (\n \n
\n {children}\n
\n
\n {children}\n
\n \n );\n});\n\nSegmentedControlOption.displayName = \"SegmentedControlOption\";\n" } ] } \ No newline at end of file diff --git a/docs/registry/ui/segmented-control.tsx b/docs/registry/ui/segmented-control.tsx index e25752dc1..fba9b433f 100644 --- a/docs/registry/ui/segmented-control.tsx +++ b/docs/registry/ui/segmented-control.tsx @@ -65,7 +65,7 @@ export const SegmentedControl = React.forwardRef< {children}
@@ -98,14 +98,14 @@ export const SegmentedControlOption = React.forwardRef< return ( diff --git a/packages/recipe-generator/preset/src/segmented-control.recipe.ts b/packages/recipe-generator/preset/src/segmented-control.recipe.ts index f15be4e01..1e53f6fa1 100644 --- a/packages/recipe-generator/preset/src/segmented-control.recipe.ts +++ b/packages/recipe-generator/preset/src/segmented-control.recipe.ts @@ -4,12 +4,11 @@ import { disabled, active, pseudo, selected } from "./pseudo"; const segmentedControl = defineRecipe({ name: "segmentedControl", - slots: ["root", "option", "optionLabel", "optionLabelPlaceholder", "indicator"], + slots: ["root", "segment", "segmentLabel", "segmentLabelPlaceholder", "selectedIndicator"], base: { root: { display: "grid", - height: vars.base.enabled.root.height, minWidth: "fit-content", maxWidth: "100%", @@ -24,70 +23,72 @@ const segmentedControl = defineRecipe({ // XXX: css reset 생기면 제거 boxSizing: "border-box", }, - option: { + segment: { // XXX: css reset 생기면 제거 border: "none", padding: 0, backgroundColor: "transparent", font: "inherit", + cursor: "pointer", position: "relative", - minWidth: vars.base.enabled.option.minWidth, + minWidth: vars.base.enabled.segment.minWidth, + height: vars.base.enabled.segment.height, zIndex: 10, - borderRadius: vars.base.enabled.option.cornerRadius, + borderRadius: vars.base.enabled.segment.cornerRadius, overflow: "hidden", userSelect: "none", - lineHeight: vars.base.enabled.option.lineHeight, + lineHeight: vars.base.enabled.segment.lineHeight, [pseudo(active)]: { - backgroundColor: vars.base.pressed.option.color, + backgroundColor: vars.base.pressed.segment.color, }, [pseudo(selected, active)]: { - backgroundColor: vars.base.selectedPressed.option.color, + backgroundColor: vars.base.selectedPressed.segment.color, }, }, - optionLabel: { + segmentLabel: { position: "absolute", insetInline: 0, transform: "translateY(-50%)", insetBlockStart: "50%", - paddingInline: `calc(${vars.base.enabled.option.paddingX} - 1px)`, + paddingInline: `calc(${vars.base.enabled.segment.paddingX} - 1px)`, textAlign: "center", - fontWeight: vars.base.enabled.option.fontWeight, - fontSize: vars.base.enabled.option.fontSize, + fontWeight: vars.base.enabled.segment.fontWeight, + fontSize: vars.base.enabled.segment.fontSize, whiteSpace: "nowrap", textOverflow: "ellipsis", overflow: "hidden", - color: vars.base.enabled.option.color, + color: vars.base.enabled.segment.color, [pseudo(selected)]: { - color: vars.base.selected.option.color, + color: vars.base.selected.segment.color, - fontWeight: vars.base.selected.option.fontWeight, + fontWeight: vars.base.selected.segment.fontWeight, }, [pseudo(disabled)]: { - color: vars.base.disabled.option.color, + color: vars.base.disabled.segment.color, }, }, - optionLabelPlaceholder: { - paddingInline: vars.base.enabled.option.paddingX, + segmentLabelPlaceholder: { + paddingInline: vars.base.enabled.segment.paddingX, textAlign: "center", - fontWeight: vars.base.selected.option.fontWeight, - fontSize: vars.base.enabled.option.fontSize, + fontWeight: vars.base.selected.segment.fontWeight, + fontSize: vars.base.enabled.segment.fontSize, textOverflow: "ellipsis", overflow: "hidden", @@ -95,13 +96,13 @@ const segmentedControl = defineRecipe({ opacity: 0, }, - indicator: { + selectedIndicator: { position: "absolute", insetBlock: vars.base.enabled.root.padding, - borderRadius: vars.base.enabled.indicator.cornerRadius, + borderRadius: vars.base.enabled.selectedIndicator.cornerRadius, - backgroundColor: vars.base.enabled.indicator.color, + backgroundColor: vars.base.enabled.selectedIndicator.color, // XXX: token으로 교체 boxShadow: "0 1px 6px rgba(0, 0, 0, 5%)", diff --git a/packages/recipe/lib/segmentedControl.d.ts b/packages/recipe/lib/segmentedControl.d.ts index 6e3996eaf..b39cfc8b7 100644 --- a/packages/recipe/lib/segmentedControl.d.ts +++ b/packages/recipe/lib/segmentedControl.d.ts @@ -8,7 +8,7 @@ type SegmentedControlVariantMap = { export type SegmentedControlVariantProps = Partial; -export type SegmentedControlSlotName = "root" | "option" | "optionLabel" | "optionLabelPlaceholder" | "indicator"; +export type SegmentedControlSlotName = "root" | "segment" | "segmentLabel" | "segmentLabelPlaceholder" | "selectedIndicator"; export const segmentedControlVariantMap: SegmentedControlVariantMap; diff --git a/packages/recipe/lib/segmentedControl.mjs b/packages/recipe/lib/segmentedControl.mjs index f3ecfca6d..411a7c5ef 100644 --- a/packages/recipe/lib/segmentedControl.mjs +++ b/packages/recipe/lib/segmentedControl.mjs @@ -6,20 +6,20 @@ const segmentedControlSlotNames = [ "segmentedControl__root" ], [ - "option", - "segmentedControl__option" + "segment", + "segmentedControl__segment" ], [ - "optionLabel", - "segmentedControl__optionLabel" + "segmentLabel", + "segmentedControl__segmentLabel" ], [ - "optionLabelPlaceholder", - "segmentedControl__optionLabelPlaceholder" + "segmentLabelPlaceholder", + "segmentedControl__segmentLabelPlaceholder" ], [ - "indicator", - "segmentedControl__indicator" + "selectedIndicator", + "segmentedControl__selectedIndicator" ] ]; diff --git a/packages/rootage/artifacts/components/segmented-control.yaml b/packages/rootage/artifacts/components/segmented-control.yaml index ff8321600..a9d659af2 100644 --- a/packages/rootage/artifacts/components/segmented-control.yaml +++ b/packages/rootage/artifacts/components/segmented-control.yaml @@ -7,11 +7,11 @@ data: base: enabled: root: - height: $unit.x10 padding: $unit.x1 cornerRadius: $radius.full color: $color.bg.neutral-weak - option: + segment: + height: $unit.x8 cornerRadius: $radius.full paddingX: $unit.x4 minWidth: 86px @@ -19,19 +19,19 @@ data: lineHeight: $line-height.t5 fontWeight: $font-weight.medium color: $color.fg.neutral-muted - indicator: + selectedIndicator: cornerRadius: $radius.full color: $color.bg.layer-default pressed: - option: + segment: color: $color.bg.neutral-weak-pressed selected: - option: + segment: color: $color.fg.neutral fontWeight: $font-weight.bold selected,pressed: - option: + segment: color: $color.bg.layer-default-pressed disabled: - option: + segment: color: $color.fg.disabled diff --git a/packages/stylesheet/segmentedControl.css b/packages/stylesheet/segmentedControl.css index 2ff5353a5..18193db9c 100644 --- a/packages/stylesheet/segmentedControl.css +++ b/packages/stylesheet/segmentedControl.css @@ -1,6 +1,5 @@ .segmentedControl__root { display: grid; - height: var(--seed-v3-unit-x10); min-width: fit-content; max-width: 100%; padding: var(--seed-v3-unit-x1); @@ -9,26 +8,28 @@ background-color: var(--seed-v3-color-bg-neutral-weak); box-sizing: border-box; } -.segmentedControl__option { +.segmentedControl__segment { border: none; padding: 0; background-color: transparent; font: inherit; + cursor: pointer; position: relative; min-width: 86px; + height: var(--seed-v3-unit-x8); z-index: 10; border-radius: var(--seed-v3-radius-full); overflow: hidden; user-select: none; line-height: var(--seed-v3-line-height-t5); } -.segmentedControl__option:is(:active, [data-active]) { +.segmentedControl__segment:is(:active, [data-active]) { background-color: var(--seed-v3-color-bg-neutral-weak-pressed); } -.segmentedControl__option:is(:selected, [data-selected]):is(:active, [data-active]) { +.segmentedControl__segment:is(:selected, [data-selected]):is(:active, [data-active]) { background-color: var(--seed-v3-color-bg-layer-default-pressed); } -.segmentedControl__optionLabel { +.segmentedControl__segmentLabel { position: absolute; inset-inline: 0; transform: translateY(-50%); @@ -42,14 +43,14 @@ overflow: hidden; color: var(--seed-v3-color-fg-neutral-muted); } -.segmentedControl__optionLabel:is(:selected, [data-selected]) { +.segmentedControl__segmentLabel:is(:selected, [data-selected]) { color: var(--seed-v3-color-fg-neutral); font-weight: var(--seed-v3-font-weight-bold); } -.segmentedControl__optionLabel:is(:disabled, [disabled], [data-disabled]) { +.segmentedControl__segmentLabel:is(:disabled, [disabled], [data-disabled]) { color: var(--seed-v3-color-fg-disabled); } -.segmentedControl__optionLabelPlaceholder { +.segmentedControl__segmentLabelPlaceholder { padding-inline: var(--seed-v3-unit-x4); text-align: center; font-weight: var(--seed-v3-font-weight-bold); @@ -59,7 +60,7 @@ white-space: nowrap; opacity: 0; } -.segmentedControl__indicator { +.segmentedControl__selectedIndicator { position: absolute; inset-block: var(--seed-v3-unit-x1); border-radius: var(--seed-v3-radius-full); diff --git a/packages/vars/src/component/segmented-control.vars.ts b/packages/vars/src/component/segmented-control.vars.ts index ed18941e2..4be94a25b 100644 --- a/packages/vars/src/component/segmented-control.vars.ts +++ b/packages/vars/src/component/segmented-control.vars.ts @@ -2,12 +2,12 @@ export const vars = { "base": { "enabled": { "root": { - "height": "var(--seed-v3-unit-x10)", "padding": "var(--seed-v3-unit-x1)", "cornerRadius": "var(--seed-v3-radius-full)", "color": "var(--seed-v3-color-bg-neutral-weak)" }, - "option": { + "segment": { + "height": "var(--seed-v3-unit-x8)", "cornerRadius": "var(--seed-v3-radius-full)", "paddingX": "var(--seed-v3-unit-x4)", "minWidth": "86px", @@ -16,29 +16,29 @@ export const vars = { "fontWeight": "var(--seed-v3-font-weight-medium)", "color": "var(--seed-v3-color-fg-neutral-muted)" }, - "indicator": { + "selectedIndicator": { "cornerRadius": "var(--seed-v3-radius-full)", "color": "var(--seed-v3-color-bg-layer-default)" } }, "pressed": { - "option": { + "segment": { "color": "var(--seed-v3-color-bg-neutral-weak-pressed)" } }, "selected": { - "option": { + "segment": { "color": "var(--seed-v3-color-fg-neutral)", "fontWeight": "var(--seed-v3-font-weight-bold)" } }, "selectedPressed": { - "option": { + "segment": { "color": "var(--seed-v3-color-bg-layer-default-pressed)" } }, "disabled": { - "option": { + "segment": { "color": "var(--seed-v3-color-fg-disabled)" } }