Quantity Picker
정수 단위의 수량을 늘리거나 줄일 때 사용하는 컴포넌트입니다.
import { QuantityPicker } from "seed-design/ui/quantity-picker";
export default function QuantityPickerPreview() {
return <QuantityPicker min={1} max={99} defaultValue={1} aria-label="상품 수량" />;
}Installation
npx @seed-design/cli@latest add ui:quantity-pickerpnpm dlx @seed-design/cli@latest add ui:quantity-pickeryarn dlx @seed-design/cli@latest add ui:quantity-pickerbun x @seed-design/cli@latest add ui:quantity-picker의존성 설치
npm install @karrotmarket/react-monochrome-icon @seed-design/reactyarn add @karrotmarket/react-monochrome-icon @seed-design/reactpnpm add @karrotmarket/react-monochrome-icon @seed-design/reactbun add @karrotmarket/react-monochrome-icon @seed-design/react아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:quantity-picker
* @requires @seed-design/react@^2.1.0
* @requires @seed-design/css@^2.3.0
**/
"use client";
import { IconMinusLine, IconPlusLine, IconTrashcanLine } from "@karrotmarket/react-monochrome-icon";
import { QuantityPicker as SeedQuantityPicker } from "@seed-design/react";
import * as React from "react";
import { ProgressCircle } from "./progress-circle";
export interface QuantityPickerProps
extends Omit<SeedQuantityPicker.RootProps, "children" | "removeAriaLabel"> {
/**
* Remove 버튼의 접근성 이름입니다.
* @default "상품 삭제"
*/
removeAriaLabel?: SeedQuantityPicker.RootProps["removeAriaLabel"];
/**
* Decrement 버튼의 접근성 이름입니다.
* @default "수량 줄이기"
*/
decrementAriaLabel?: string;
/**
* Increment 버튼의 접근성 이름입니다.
* @default "수량 늘리기"
*/
incrementAriaLabel?: string;
/**
* Decrement 버튼에 표시할 아이콘입니다.
* @default <IconMinusLine />
*/
decrementIcon?: React.ReactNode;
/**
* Increment 버튼에 표시할 아이콘입니다.
* @default <IconPlusLine />
*/
incrementIcon?: React.ReactNode;
/**
* Remove 버튼에 표시할 아이콘입니다.
* @default <IconTrashcanLine />
*/
removeIcon?: React.ReactNode;
/**
* loading 상태일 때 버튼에 표시할 요소입니다.
* @default <ProgressCircle size="inherit" tone="inherit" />
*/
loadingIndicator?: React.ReactNode;
/** 현재 수량을 form으로 제출할 때 hidden input에 전달할 속성입니다. */
inputProps?: SeedQuantityPicker.HiddenInputProps;
}
/**
* @see https://seed-design.io/react/components/quantity-picker
*/
export const QuantityPicker = React.forwardRef<
React.ElementRef<typeof SeedQuantityPicker.Root>,
QuantityPickerProps
>(
(
{
decrementAriaLabel = "수량 줄이기",
incrementAriaLabel = "수량 늘리기",
removeAriaLabel = "상품 삭제",
decrementIcon = <IconMinusLine />,
incrementIcon = <IconPlusLine />,
removeIcon = <IconTrashcanLine />,
loadingIndicator = <ProgressCircle size="inherit" tone="inherit" />,
inputProps,
...rootProps
},
ref,
) => {
return (
<SeedQuantityPicker.Root ref={ref} removeAriaLabel={removeAriaLabel} {...rootProps}>
<SeedQuantityPicker.DecrementButton
aria-label={decrementAriaLabel}
icon={decrementIcon}
loadingIndicator={loadingIndicator}
removeIcon={removeIcon}
/>
<SeedQuantityPicker.ValueDisplay />
<SeedQuantityPicker.IncrementButton
aria-label={incrementAriaLabel}
icon={incrementIcon}
loadingIndicator={loadingIndicator}
/>
<SeedQuantityPicker.HiddenInput {...inputProps} />
</SeedQuantityPicker.Root>
);
},
);
QuantityPicker.displayName = "QuantityPicker";
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
Props
Prop
Type
Examples
Layout
layout="hug"는 콘텐츠에 맞는 기존 너비를 유지합니다. 부모가 Flex 레이아웃이고 남은 공간을 채워야 한다면 layout="fill"을 사용하세요. 이때 양쪽 버튼 크기는 유지되고 Value Display 영역만 늘어납니다.
import { HStack, Text, VStack } from "@seed-design/react";
import { QuantityPicker } from "seed-design/ui/quantity-picker";
export default function QuantityPickerLayout() {
return (
<VStack width="360px" gap="x4" align="stretch">
<VStack gap="x2" align="stretch">
<Text>Hug (기본)</Text>
<HStack width="full">
<QuantityPicker
layout="hug"
min={1}
max={99}
defaultValue={1}
aria-label="Hug 상품 수량"
/>
</HStack>
</VStack>
<VStack gap="x2" align="stretch">
<Text>Fill</Text>
<HStack width="full">
<QuantityPicker
layout="fill"
min={1}
max={99}
defaultValue={1}
aria-label="Fill 상품 수량"
/>
</HStack>
</VStack>
</VStack>
);
}Value Text
getValueText를 사용하면 표시되는 수량에 단위나 보조 설명을 덧붙일 수 있습니다.
import { QuantityPicker } from "seed-design/ui/quantity-picker";
export default function QuantityPickerValueText() {
return (
<QuantityPicker
min={1}
max={99}
defaultValue={1}
aria-label="상품 수량"
getValueText={(valueText: string) => `${valueText}개`}
/>
);
}Controlled
value와 onValueChange를 사용해 수량 상태를 외부에서 제어할 수 있습니다.
import { Text, VStack } from "@seed-design/react";
import { useState } from "react";
import { QuantityPicker } from "seed-design/ui/quantity-picker";
export default function QuantityPickerControlled() {
const [quantity, setQuantity] = useState(2);
return (
<VStack gap="x3" align="center">
<QuantityPicker
min={1}
max={99}
value={quantity}
onValueChange={setQuantity}
aria-label="상품 수량"
/>
<Text textStyle="t4Regular">현재 수량: {quantity}개</Text>
</VStack>
);
}Removable
removable을 사용하면 값이 min에 도달했을 때 Decrement 버튼이 Remove 버튼으로 전환됩니다. onRemove에서 제거 동작을 처리하세요.
import { Text, VStack } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import { QuantityPicker } from "seed-design/ui/quantity-picker";
export default function QuantityPickerRemovable() {
const [removed, setRemoved] = useState(false);
if (removed) {
return (
<VStack gap="x3" align="center">
<Text textStyle="t4Regular">상품을 삭제했습니다.</Text>
<ActionButton variant="neutralWeak" onClick={() => setRemoved(false)}>
되돌리기
</ActionButton>
</VStack>
);
}
return (
<VStack gap="x3" align="center">
<QuantityPicker
min={1}
max={99}
defaultValue={1}
removable
aria-label="상품 수량"
removeAriaLabel="상품 삭제"
onRemove={() => setRemoved(true)}
/>
<Text textStyle="t4Regular">최소 수량에서 Decrement 버튼이 Remove 버튼으로 전환됩니다.</Text>
</VStack>
);
}Loading
loading으로 모든 action 또는 특정 action의 실행을 일시적으로 막고 loading indicator를 표시할 수 있습니다.
import { HStack, VStack } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import { QuantityPicker } from "seed-design/ui/quantity-picker";
export default function QuantityPickerLoading() {
const [decrementLoading, setDecrementLoading] = useState(false);
const [incrementLoading, setIncrementLoading] = useState(false);
const allLoading = decrementLoading && incrementLoading;
function toggleAllLoading() {
const nextLoading = !allLoading;
setDecrementLoading(nextLoading);
setIncrementLoading(nextLoading);
}
return (
<VStack gap="x3" align="center">
<QuantityPicker
min={1}
max={99}
defaultValue={2}
loading={
allLoading
? true
: {
decrement: decrementLoading,
increment: incrementLoading,
}
}
aria-label="상품 수량"
/>
<HStack gap="x2">
<ActionButton variant="neutralWeak" aria-pressed={allLoading} onClick={toggleAllLoading}>
전체
</ActionButton>
<ActionButton
variant="neutralWeak"
aria-pressed={decrementLoading}
onClick={() => setDecrementLoading((current) => !current)}
>
Decrement
</ActionButton>
<ActionButton
variant="neutralWeak"
aria-pressed={incrementLoading}
onClick={() => setIncrementLoading((current) => !current)}
>
Increment
</ActionButton>
</HStack>
</VStack>
);
}Form
inputProps에 name을 전달하면 현재 수량이 hidden input으로 제출됩니다.
import { VStack } from "@seed-design/react";
import type { FormEvent } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import { QuantityPicker } from "seed-design/ui/quantity-picker";
export default function QuantityPickerForm() {
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
window.alert(`제출한 수량: ${formData.get("quantity")}개`);
}
return (
<VStack asChild gap="x3" align="center">
<form onSubmit={handleSubmit}>
<QuantityPicker
min={1}
max={99}
defaultValue={1}
aria-label="상품 수량"
inputProps={{ name: "quantity" }}
/>
<ActionButton type="submit" variant="neutralSolid">
제출
</ActionButton>
</form>
</VStack>
);
}Accessibility
Root에는 Quantity Picker의 용도를 설명하는 aria-label 또는 aria-labelledby를 제공하세요. 필요하면 decrementAriaLabel, incrementAriaLabel, removeAriaLabel로 각 action의 접근성 이름을 맥락에 맞게 변경할 수 있습니다.
getValueText를 사용하면 ValueDisplay의 숫자를 사람이 읽기 쉬운 텍스트로 제공할 수 있습니다. 이 값은 hidden input에 제출되는 raw integer 값에 영향을 주지 않습니다.
Last updated on