@loword/loword-design-system 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ButtonBase.d.ts","sourceRoot":"","sources":["../../../src/components/LDButton/ButtonBase.tsx"],"names":[],"mappings":"AA0BA,eAAO,MAAM,UAAU;UACP,KAAK,CAAC,WAAW;CAChC,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"ButtonBase.d.ts","sourceRoot":"","sources":["../../../src/components/LDButton/ButtonBase.tsx"],"names":[],"mappings":"AAsCA,eAAO,MAAM,UAAU;UACP,KAAK,CAAC,WAAW;CAChC,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import cn from '../../utils/cn.js';
3
- const Root = ({ as, className, children, ref, ...props }) => {
4
- const Component = as ?? 'button';
5
- return (_jsx(Component, { ref: ref, className: cn('relative inline-flex flex-row items-center justify-center', className), ...props, children: children }));
3
+ const Root = ({ as, className, children, ref, href, disabled, ...props }) => {
4
+ // as가 href를 가질 때 (a, NextLink 등) disabled=true일 경우 href를 제거하고 span으로 렌더링
5
+ const isDisabledLink = href && disabled;
6
+ const Component = isDisabledLink ? 'span' : (as ?? 'button');
7
+ return (_jsx(Component, { ref: ref, className: cn('relative inline-flex flex-row items-center justify-center', className), "aria-disabled": disabled || undefined, ...(!isDisabledLink && { href, disabled }), ...props, children: children }));
6
8
  };
7
9
  export const ButtonBase = {
8
10
  Root: Root,
@@ -1 +1 @@
1
- {"version":3,"file":"LDButton.d.ts","sourceRoot":"","sources":["../../../src/components/LDButton/LDButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAQpE,KAAK,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5D,KAAK,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAClD,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,OAAO,GACP,QAAQ,CAAC;AACb,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AACzC,KAAK,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AA6E/C,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAOtD,CAAC;AAcF,KAAK,YAAY,GAAG;IAClB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,WAAW,IAAI,yBAAyB,CAC1E,CAAC,EACD,YAAY,CACb,CAAC;AAEF,KAAK,eAAe,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,WAAW,GAAG,QAAQ,EAC5D,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,KACpB,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;AAE/B,QAAA,MAAM,QAAQ,EAAE,eAoEf,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"LDButton.d.ts","sourceRoot":"","sources":["../../../src/components/LDButton/LDButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAQpE,KAAK,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5D,KAAK,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAClD,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,OAAO,GACP,QAAQ,CAAC;AACb,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AACzC,KAAK,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AA+E/C,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAOtD,CAAC;AAcF,KAAK,YAAY,GAAG;IAClB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,WAAW,IAAI,yBAAyB,CAC1E,CAAC,EACD,YAAY,CACb,CAAC;AAEF,KAAK,eAAe,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,WAAW,GAAG,QAAQ,EAC5D,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,KACpB,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;AAE/B,QAAA,MAAM,QAAQ,EAAE,eAoEf,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import cn from '../../utils/cn.js';
3
- import ButtonBase from './ButtonBase.js';
4
3
  import { renderIcon } from '../../utils/renderIcon.js';
4
+ import ButtonBase from './ButtonBase.js';
5
5
  import ButtonSpinner from './ButtonSpinner.js';
6
6
  // ── 스타일 매핑 (static) ──
7
7
  const variantBaseClasses = {
@@ -11,9 +11,9 @@ const variantBaseClasses = {
11
11
  danger: 'bg-danger500 text-white',
12
12
  },
13
13
  outline: {
14
- neutral: 'bg-white outline outline-neutral100 text-neutral900',
15
- point: 'bg-white outline outline-lb500 text-lb500',
16
- danger: 'bg-white outline outline-danger500 text-danger500',
14
+ neutral: 'bg-white outline -outline-offset-1 outline-neutral100 text-neutral900',
15
+ point: 'bg-white outline -outline-offset-1 outline-lb500 text-lb500',
16
+ danger: 'bg-white outline -outline-offset-1 outline-danger500 text-danger500',
17
17
  },
18
18
  ghost: {
19
19
  neutral: 'bg-transparent text-neutral900',
@@ -50,7 +50,7 @@ const variantHoverClasses = {
50
50
  };
51
51
  const variantDisabledClasses = {
52
52
  solid: 'bg-mono300 text-white',
53
- outline: 'text-mono300 outline outline-neutral100',
53
+ outline: 'text-mono300 outline -outline-offset-1 outline-neutral100',
54
54
  ghost: 'text-mono300',
55
55
  link: 'text-mono300 underline underline-offset-[4px]',
56
56
  };
@@ -0,0 +1,10 @@
1
+ import { type RegisterOptions } from 'react-hook-form';
2
+ import { type LDRateProps } from './LDRate.js';
3
+ export interface LDFormRateProps extends Omit<LDRateProps, 'value' | 'name' | 'onChange'> {
4
+ name: string;
5
+ options?: RegisterOptions;
6
+ customErrorMessage?: string;
7
+ }
8
+ declare const LDFormRate: ({ name, options, customErrorMessage, className, itemClassName, ...props }: LDFormRateProps) => import("react/jsx-runtime").JSX.Element;
9
+ export default LDFormRate;
10
+ //# sourceMappingURL=LDFormRate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LDFormRate.d.ts","sourceRoot":"","sources":["../../../src/components/LDRate/LDFormRate.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAEpD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,WAAW,EACX,OAAO,GAAG,MAAM,GAAG,UAAU,CAC9B;IACC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,QAAA,MAAM,UAAU,GAAI,2EAOjB,eAAe,4CAuBjB,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -0,0 +1,9 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import cn from '../../utils/cn.js';
4
+ import FormField from '../LDForm/LDFormField.js';
5
+ import LDRate from './LDRate.js';
6
+ const LDFormRate = ({ name, options, customErrorMessage = '', className, itemClassName, ...props }) => {
7
+ return (_jsx(FormField, { name: name, options: options, customErrorMessage: customErrorMessage, children: ({ field, fieldState }) => (_jsx(LDRate, { ...props, value: field.value ?? 0, onChange: field.onChange, onBlur: field.onBlur, className: className, "aria-invalid": field['aria-invalid'], itemClassName: cn(fieldState.hasError && 'text-danger600', itemClassName) })) }));
8
+ };
9
+ export default LDFormRate;
@@ -0,0 +1,45 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export type LDRateSize = 'small' | 'medium' | 'large' | 'xlarge' | '2xlarge';
3
+ export interface LDRateProps {
4
+ /** controlled 값 */
5
+ value?: number;
6
+ /** 초기 값 (uncontrolled) */
7
+ defaultValue?: number;
8
+ /** 값 확정 콜백 */
9
+ onChange?: (value: number) => void;
10
+ /** 호버 값 변경 콜백 (마우스가 벗어나면 0으로 호출) */
11
+ onHoverChange?: (value: number) => void;
12
+ /** 별 개수 (기본 5) */
13
+ count?: number;
14
+ /** 읽기 전용 여부 (평균 평점 등 표시 용도, 상호작용 차단 및 스크린리더에 값 하나로 안내, 기본 false) */
15
+ readOnly?: boolean;
16
+ /** 반개 클릭 허용 여부 */
17
+ allowHalf?: boolean;
18
+ /** 현재 값과 동일한 지점을 다시 클릭하면 0으로 초기화 (기본 true) */
19
+ allowClear?: boolean;
20
+ /** 방향키/Home/End/Space/Enter 조작 허용 여부 (기본 true) */
21
+ keyboard?: boolean;
22
+ /** 크기 프리셋 (기본 'medium') */
23
+ size?: LDRateSize;
24
+ /** 별 대신 렌더할 커스텀 아이콘 */
25
+ icon?: ReactNode;
26
+ /** Root 래퍼(role="radiogroup")에 merge할 className */
27
+ className?: string;
28
+ /** 각 별 아이템(role="radio")에 merge할 className. 채운 별 색(text-yellow600)이 여기 붙어 svg로 상속되므로 색 override 시 사용 */
29
+ itemClassName?: string;
30
+ /** Root 래퍼(radiogroup)에 적용할 인라인 스타일 */
31
+ style?: CSSProperties;
32
+ /** 포커스 이벤트 핸들러 */
33
+ onFocus?: React.FocusEventHandler<HTMLDivElement>;
34
+ /** 블러 이벤트 핸들러 */
35
+ onBlur?: React.FocusEventHandler<HTMLDivElement>;
36
+ /** 키보드 이벤트 핸들러 */
37
+ onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
38
+ /** 에러 발생 시 true, 정상 시 false */
39
+ 'aria-invalid'?: boolean | 'true' | 'false';
40
+ /** 에러 메시지 요소의 id와 연결되어 스크린 리더에서 에러를 읽을 수 있게 함 */
41
+ 'aria-describedby'?: string;
42
+ }
43
+ declare const LDRate: ({ value, defaultValue, onChange, onHoverChange, count, readOnly, allowHalf, allowClear, keyboard, size, icon, className, itemClassName, style, onFocus, onBlur, onKeyDown, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, }: LDRateProps) => import("react/jsx-runtime").JSX.Element;
44
+ export default LDRate;
45
+ //# sourceMappingURL=LDRate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LDRate.d.ts","sourceRoot":"","sources":["../../../src/components/LDRate/LDRate.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM3D,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AA6D7E,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,oCAAoC;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,uBAAuB;IACvB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wGAAwG;IACxG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,kBAAkB;IAClB,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAClD,iBAAiB;IACjB,MAAM,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;IACjD,kBAAkB;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC;IACvD,+BAA+B;IAC/B,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,QAAA,MAAM,MAAM,GAAI,gPAoBb,WAAW,4CA8Cb,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Star } from 'lucide-react';
4
+ import cn from '../../utils/cn.js';
5
+ import { renderIcon } from '../../utils/renderIcon.js';
6
+ import RateBase from './RateBase.js';
7
+ const sizeClasses = {
8
+ small: 'display14',
9
+ medium: 'display16',
10
+ large: 'display20',
11
+ xlarge: 'display28',
12
+ '2xlarge': 'display32',
13
+ };
14
+ // 채워진 별 색 (황색 고정)
15
+ const filledColorClass = 'text-yellow600';
16
+ // 빈 별 색
17
+ const emptyColorClass = 'text-neutral100';
18
+ const StarItem = ({ filled, halfFilled, hovered, allowHalf, icon, }) => {
19
+ const iconBaseClassName = cn('size-[1em] shrink-0');
20
+ const renderStar = (isFilled) => {
21
+ const colorClass = isFilled
22
+ ? 'fill-current'
23
+ : cn('fill-current', emptyColorClass);
24
+ return icon ? (renderIcon(icon, cn(iconBaseClassName, colorClass, 'ld-transition'))) : (_jsx(Star, { className: cn(iconBaseClassName, colorClass, 'ld-transition') }));
25
+ };
26
+ return (_jsxs("div", { className: cn('relative ld-transition', hovered && 'scale-110'), children: [renderStar(filled), allowHalf && (_jsx("div", { "aria-hidden": "true", className: "absolute inset-0 w-1/2 overflow-hidden pointer-events-none", children: renderStar(halfFilled) }))] }));
27
+ };
28
+ const LDRate = ({ value, defaultValue = 0, onChange, onHoverChange, count = 5, readOnly = false, allowHalf = false, allowClear = true, keyboard = true, size = 'medium', icon, className, itemClassName, style, onFocus, onBlur, onKeyDown, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedby, }) => {
29
+ const sizeClass = sizeClasses[size];
30
+ const itemWrapperClassName = cn(readOnly ? 'cursor-default' : 'cursor-pointer', 'ld-transition', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-lb500 focus-visible:ring-offset-2 rounded-xs', filledColorClass, itemClassName);
31
+ return (_jsx(RateBase.Root, { value: value, defaultValue: defaultValue, onChange: onChange, onHoverChange: onHoverChange, count: count, readOnly: readOnly, allowHalf: allowHalf, allowClear: allowClear, keyboard: keyboard, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, style: style, className: cn('flex flex-row w-fit shrink-0 items-center gap-2xs', sizeClass, className), itemClassName: itemWrapperClassName, children: ({ filled, halfFilled, hovered }) => (_jsx(StarItem, { filled: filled, halfFilled: halfFilled, hovered: hovered, allowHalf: allowHalf, icon: icon })) }));
32
+ };
33
+ export default LDRate;
@@ -0,0 +1,49 @@
1
+ import { type FocusEventHandler, type KeyboardEventHandler, type ReactNode } from 'react';
2
+ interface RateStarRenderProps {
3
+ /** 0-based 별 인덱스 */
4
+ index: number;
5
+ /** 별 전체가 채워지는지 */
6
+ filled: boolean;
7
+ /** 별의 왼쪽 절반이 채워지는지 */
8
+ halfFilled: boolean;
9
+ /** 이 별이 현재 호버 대상인지 */
10
+ hovered: boolean;
11
+ }
12
+ interface RootProps {
13
+ /** controlled 값 */
14
+ value?: number;
15
+ /** 초기 값 (uncontrolled) */
16
+ defaultValue?: number;
17
+ /** 값 확정 콜백 */
18
+ onChange?: (value: number) => void;
19
+ /** 호버 값 변경 콜백 (마우스가 벗어나면 0으로 호출) */
20
+ onHoverChange?: (value: number) => void;
21
+ /** 별 개수 */
22
+ count?: number;
23
+ /** 읽기 전용 여부 (평균 평점 등 표시 용도, 상호작용 차단) */
24
+ readOnly?: boolean;
25
+ /** 반개 클릭 허용 여부 */
26
+ allowHalf?: boolean;
27
+ /** 현재 값과 동일한 지점을 다시 클릭하면 0으로 초기화 */
28
+ allowClear?: boolean;
29
+ /** 방향키/Home/End/Space/Enter 조작 허용 여부 */
30
+ keyboard?: boolean;
31
+ onFocus?: FocusEventHandler<HTMLDivElement>;
32
+ onBlur?: FocusEventHandler<HTMLDivElement>;
33
+ onKeyDown?: KeyboardEventHandler<HTMLDivElement>;
34
+ className?: string;
35
+ style?: React.CSSProperties;
36
+ /** 별 하나의 시각 표현을 렌더하는 render-prop */
37
+ children: (star: RateStarRenderProps) => ReactNode;
38
+ /** 각 Item(role="radio")에 그대로 전달할 className 패스스루 */
39
+ itemClassName?: string;
40
+ /** 에러 발생 시 true, 정상 시 false */
41
+ 'aria-invalid'?: boolean | 'true' | 'false';
42
+ /** 에러 메시지 요소의 id와 연결되어 스크린 리더에서 에러를 읽을 수 있게 함 */
43
+ 'aria-describedby'?: string;
44
+ }
45
+ export declare const RateBase: {
46
+ Root: ({ value, defaultValue, onChange, onHoverChange, count, readOnly, allowHalf, allowClear, keyboard, onFocus, onBlur, onKeyDown, className, style, children, itemClassName, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, }: RootProps) => import("react/jsx-runtime").JSX.Element;
47
+ };
48
+ export default RateBase;
49
+ //# sourceMappingURL=RateBase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateBase.d.ts","sourceRoot":"","sources":["../../../src/components/LDRate/RateBase.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAGf,UAAU,mBAAmB;IAC3B,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,sBAAsB;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,sBAAsB;IACtB,OAAO,EAAE,OAAO,CAAC;CAClB;AAGD,UAAU,SAAS;IACjB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,oCAAoC;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oCAAoC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAC5C,MAAM,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,oBAAoB,CAAC,cAAc,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,SAAS,CAAC;IACnD,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAsOD,eAAO,MAAM,QAAQ;yPAjNlB,SAAS;CAmNX,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useRef, useState, } from 'react';
4
+ const Root = ({ value, defaultValue = 0, onChange, onHoverChange, count = 5, readOnly = false, allowHalf = false, allowClear = true, keyboard = true, onFocus, onBlur, onKeyDown, className, style, children, itemClassName, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedby, }) => {
5
+ const [internalValue, setInternalValue] = useState(defaultValue);
6
+ const [hoverValue, setHoverValue] = useState(null);
7
+ const isControlled = value !== undefined;
8
+ const currentValue = isControlled ? value : internalValue;
9
+ const displayValue = hoverValue !== null ? hoverValue : currentValue;
10
+ const min = allowHalf ? 0.5 : 1;
11
+ const step = allowHalf ? 0.5 : 1;
12
+ const clamp = (n) => Math.min(Math.max(n, 0), count);
13
+ const selectValue = (candidate) => {
14
+ if (readOnly)
15
+ return;
16
+ const next = allowClear && candidate === currentValue ? 0 : candidate;
17
+ if (!isControlled)
18
+ setInternalValue(next);
19
+ onChange?.(next);
20
+ };
21
+ const updateHover = (candidate) => {
22
+ if (readOnly)
23
+ return;
24
+ setHoverValue(candidate);
25
+ if (candidate !== null)
26
+ onHoverChange?.(candidate);
27
+ };
28
+ const handleMouseLeave = () => {
29
+ if (readOnly)
30
+ return;
31
+ setHoverValue(null);
32
+ onHoverChange?.(0);
33
+ };
34
+ const handleKeyDown = (event, index) => {
35
+ if (!keyboard || readOnly)
36
+ return;
37
+ switch (event.key) {
38
+ case 'ArrowRight':
39
+ case 'ArrowUp':
40
+ event.preventDefault();
41
+ selectValue(clamp(currentValue + step));
42
+ break;
43
+ case 'ArrowLeft':
44
+ case 'ArrowDown':
45
+ event.preventDefault();
46
+ selectValue(clamp(currentValue - step));
47
+ break;
48
+ case 'Home':
49
+ event.preventDefault();
50
+ selectValue(min);
51
+ break;
52
+ case 'End':
53
+ event.preventDefault();
54
+ selectValue(count);
55
+ break;
56
+ case ' ':
57
+ case 'Enter':
58
+ event.preventDefault();
59
+ selectValue(index + 1);
60
+ break;
61
+ default:
62
+ break;
63
+ }
64
+ };
65
+ const rawActiveIndex = currentValue === 0 ? 0 : Math.ceil(currentValue) - 1;
66
+ const activeIndex = Number.isFinite(rawActiveIndex)
67
+ ? Math.min(Math.max(rawActiveIndex, 0), count - 1)
68
+ : 0;
69
+ const raw = Number.isFinite(displayValue) ? displayValue : 0;
70
+ const snapped = allowHalf ? Math.round(raw * 2) / 2 : Math.round(raw);
71
+ if (readOnly) {
72
+ return (_jsx("div", { role: "img", "aria-label": `${count}점 중 ${currentValue}점`, style: style, className: className, children: Array.from({ length: count }, (_, index) => {
73
+ const filled = snapped >= index + 1;
74
+ const halfFilled = snapped >= index + 0.5;
75
+ return (_jsx("div", { "aria-hidden": "true", className: itemClassName, children: children({ index, filled, halfFilled, hovered: false }) }, index));
76
+ }) }));
77
+ }
78
+ return (_jsx("div", { role: "radiogroup", "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, style: style, className: className, onMouseLeave: handleMouseLeave, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown, children: Array.from({ length: count }, (_, index) => {
79
+ const filled = snapped >= index + 1;
80
+ const halfFilled = snapped >= index + 0.5;
81
+ const hovered = hoverValue !== null && Math.ceil(hoverValue) - 1 === index;
82
+ return (_jsx(Item, { index: index, count: count, allowHalf: allowHalf, isTabbable: index === activeIndex, isChecked: currentValue !== 0 && index === activeIndex, label: index === activeIndex && !Number.isInteger(currentValue)
83
+ ? `${currentValue}점`
84
+ : `${index + 1}점`, onSelect: selectValue, onHover: updateHover, onKeyDown: (event) => handleKeyDown(event, index), className: itemClassName, children: children({ index, filled, halfFilled, hovered }) }, index));
85
+ }) }));
86
+ };
87
+ const Item = ({ index, count, allowHalf, isTabbable, isChecked, label, onSelect, onHover, onKeyDown, className, children, }) => {
88
+ const ref = useRef(null);
89
+ const posinset = index + 1;
90
+ const getCandidate = (event) => {
91
+ if (!allowHalf)
92
+ return posinset;
93
+ const rect = ref.current.getBoundingClientRect();
94
+ const isLeftHalf = event.clientX - rect.left < rect.width / 2;
95
+ return isLeftHalf ? index + 0.5 : posinset;
96
+ };
97
+ const handleClick = (event) => {
98
+ onSelect(getCandidate(event));
99
+ };
100
+ const handleMouseMove = (event) => {
101
+ onHover(getCandidate(event));
102
+ };
103
+ return (_jsx("div", { ref: ref, role: "radio", "aria-checked": isChecked, "aria-posinset": posinset, "aria-setsize": count, "aria-label": label, tabIndex: isTabbable ? 0 : -1, className: className, onClick: handleClick, onMouseMove: handleMouseMove, onKeyDown: onKeyDown, children: children }));
104
+ };
105
+ export const RateBase = {
106
+ Root,
107
+ };
108
+ export default RateBase;
@@ -0,0 +1,46 @@
1
+ import * as React from 'react';
2
+ import { type ComponentProps, type ElementType, type ReactNode } from 'react';
3
+ import { type PolymorphicComponentProps } from '../../types/polymorphic.js';
4
+ import TabsBase from './TabsBase.js';
5
+ /** 탭 트리거 시각 스타일 */
6
+ export type LDTabsVariant = 'line' | 'card' | 'button';
7
+ /** 탭 리스트 위치 */
8
+ export type LDTabsPlacement = 'top' | 'left' | 'right';
9
+ /** 탭 트리거 크기 */
10
+ export type LDTabsSize = 'small' | 'medium' | 'large';
11
+ /** 탭 트리거 강조색 (활성·hover). Trigger에 개별 지정한다. mono/neutral 계열은 color와 무관하게 고정 */
12
+ export type LDTabsColor = 'point' | 'danger' | 'success' | 'warning';
13
+ /** LDTabs.Root props */
14
+ export interface LDTabsRootProps extends ComponentProps<typeof TabsBase.Root> {
15
+ /** 탭 트리거 시각 스타일. 기본 'line' */
16
+ variant?: LDTabsVariant;
17
+ /** 탭 리스트 위치. 기본 'top' */
18
+ placement?: LDTabsPlacement;
19
+ /** 탭 트리거 크기. 기본 'medium' */
20
+ size?: LDTabsSize;
21
+ }
22
+ /** LDTabs.List props */
23
+ export type LDTabsListProps = ComponentProps<typeof TabsBase.List>;
24
+ interface LDTabsTriggerOwnProps {
25
+ /** 활성 상태 강조색. 지정한 트리거에만 적용됩니다. 기본 'point' */
26
+ color?: LDTabsColor;
27
+ /** children 왼쪽에 렌더되는 아이콘. 크기는 size에 따라 자동 결정됩니다. */
28
+ icon?: ReactNode;
29
+ /** 탭 값 */
30
+ value: string;
31
+ /** 비활성화 여부 */
32
+ disabled?: boolean;
33
+ }
34
+ /** LDTabs.Trigger props */
35
+ export type LDTabsTriggerProps<T extends ElementType> = PolymorphicComponentProps<T, LDTabsTriggerOwnProps>;
36
+ type TriggerComponent = <T extends ElementType = 'button'>(props: LDTabsTriggerProps<T>) => React.ReactElement | null;
37
+ /** LDTabs.Content props */
38
+ export type LDTabsContentProps = ComponentProps<typeof TabsBase.Content>;
39
+ declare const LDTabs: {
40
+ Root: ({ variant, placement, size, className, ...props }: LDTabsRootProps) => import("react/jsx-runtime").JSX.Element;
41
+ List: ({ className, children, ...props }: LDTabsListProps) => import("react/jsx-runtime").JSX.Element;
42
+ Trigger: TriggerComponent;
43
+ Content: ({ className, children, ...props }: LDTabsContentProps) => import("react/jsx-runtime").JSX.Element;
44
+ };
45
+ export default LDTabs;
46
+ //# sourceMappingURL=LDTabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LDTabs.d.ts","sourceRoot":"","sources":["../../../src/components/LDTabs/LDTabs.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAGzE,OAAO,QAAQ,MAAM,YAAY,CAAC;AAKlC,mBAAmB;AACnB,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AACvD,eAAe;AACf,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AACvD,eAAe;AACf,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AACtD,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAwBrE,wBAAwB;AACxB,MAAM,WAAW,eAAgB,SAAQ,cAAc,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC;IAC3E,8BAA8B;IAC9B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,yBAAyB;IACzB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,4BAA4B;IAC5B,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAwED,wBAAwB;AACxB,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AA+PnE,UAAU,qBAAqB;IAC7B,6CAA6C;IAC7C,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,oDAAoD;IACpD,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,UAAU;IACV,KAAK,EAAE,MAAM,CAAC;IACd,cAAc;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,2BAA2B;AAC3B,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,WAAW,IAClD,yBAAyB,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;AAEtD,KAAK,gBAAgB,GAAG,CAAC,CAAC,SAAS,WAAW,GAAG,QAAQ,EACvD,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,KACzB,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;AAgD/B,2BAA2B;AAC3B,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;AAkBzE,QAAA,MAAM,MAAM;8DApZT,eAAe;8CAmE+B,eAAe;;iDAiUZ,kBAAkB;CAqBrE,CAAC;AAEF,eAAe,MAAM,CAAC"}