@highpixel-co/palda-design-system 0.6.0 → 0.8.0
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/{chunk-QW2SD3XV.js → chunk-3OXUONDZ.js} +22 -6
- package/dist/{chunk-6JZPK2W5.js → chunk-PWAQFOM3.js} +105 -42
- package/dist/guide/README.md +3 -3
- package/dist/guide/catalog/components.yml +71 -4
- package/dist/guide/catalog/drafts.yml +46 -0
- package/dist/guide/catalog/patterns.yml +11 -4
- package/dist/guide/docs/AI_UI_DESIGNER_HANDOFF.md +3 -3
- package/dist/guide/docs/COMPONENT_POLICY.md +2 -2
- package/dist/guide/docs/CONTENT.md +1 -1
- package/dist/guide/docs/DESIGN_GRAMMAR.md +126 -26
- package/dist/guide/docs/DESIGN_PRINCIPLES.md +10 -10
- package/dist/guide/docs/FIGMA_ALIGNMENT_DELTA.md +4 -4
- package/dist/guide/docs/FIGMA_NAME_MAPPING.md +4 -4
- package/dist/guide/docs/FIGMA_WORKFLOW.md +3 -3
- package/dist/guide/docs/ICON_POLICY.md +3 -3
- package/dist/guide/docs/LAYOUT.md +17 -11
- package/dist/guide/docs/TOKEN_POLICY.md +5 -5
- package/dist/harness/check.mjs +13 -7
- package/dist/harness/metadata.json +36 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -2
- package/dist/patterns.d.ts +12 -1
- package/dist/patterns.js +2 -2
- package/dist/scripts/check-examples.mjs +253 -10
- package/dist/styles.css +100 -41
- package/dist/ui.d.ts +67 -5
- package/dist/ui.js +5 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Button
|
|
3
|
-
|
|
2
|
+
Button,
|
|
3
|
+
Skeleton
|
|
4
|
+
} from "./chunk-PWAQFOM3.js";
|
|
4
5
|
import {
|
|
5
6
|
ChevronRightIcon
|
|
6
7
|
} from "./chunk-TWYJ2UTC.js";
|
|
@@ -91,8 +92,15 @@ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
|
91
92
|
var TITLE_TYPOGRAPHY_CLASS3 = "palda-text-body1-regular-semibold";
|
|
92
93
|
var DESCRIPTION_TYPOGRAPHY_CLASS3 = "palda-text-body2-reading-medium";
|
|
93
94
|
var ERROR_TYPOGRAPHY_CLASS = "palda-text-label-reading-medium";
|
|
94
|
-
function FormSection({
|
|
95
|
-
|
|
95
|
+
function FormSection({
|
|
96
|
+
children,
|
|
97
|
+
description,
|
|
98
|
+
error,
|
|
99
|
+
footer,
|
|
100
|
+
title,
|
|
101
|
+
tone = "plain"
|
|
102
|
+
}) {
|
|
103
|
+
return /* @__PURE__ */ jsxs5("section", { className: `palda-form-section palda-form-section--${tone}`, children: [
|
|
96
104
|
/* @__PURE__ */ jsxs5("header", { className: "palda-form-section__header", children: [
|
|
97
105
|
/* @__PURE__ */ jsx5("h2", { className: TITLE_TYPOGRAPHY_CLASS3, children: title }),
|
|
98
106
|
description ? /* @__PURE__ */ jsx5("p", { className: DESCRIPTION_TYPOGRAPHY_CLASS3, children: description }) : null
|
|
@@ -113,6 +121,14 @@ var DESCRIPTION_TYPOGRAPHY_CLASS4 = {
|
|
|
113
121
|
default: "palda-text-label-reading-medium",
|
|
114
122
|
strong: "palda-text-body2-reading-medium"
|
|
115
123
|
};
|
|
124
|
+
var SKELETON_TITLE_TEXT = {
|
|
125
|
+
default: "body2",
|
|
126
|
+
strong: "body1"
|
|
127
|
+
};
|
|
128
|
+
var SKELETON_DESCRIPTION_TEXT = {
|
|
129
|
+
default: "label",
|
|
130
|
+
strong: "body2"
|
|
131
|
+
};
|
|
116
132
|
function ListRow({
|
|
117
133
|
chevron,
|
|
118
134
|
children,
|
|
@@ -135,8 +151,8 @@ function ListRow({
|
|
|
135
151
|
].filter(Boolean).join(" ");
|
|
136
152
|
if (loading) {
|
|
137
153
|
return /* @__PURE__ */ jsxs6("div", { "aria-hidden": "true", className: classes, children: [
|
|
138
|
-
/* @__PURE__ */ jsx6(
|
|
139
|
-
/* @__PURE__ */ jsx6(
|
|
154
|
+
/* @__PURE__ */ jsx6(Skeleton, { text: SKELETON_TITLE_TEXT[emphasis], width: "narrow" }),
|
|
155
|
+
/* @__PURE__ */ jsx6(Skeleton, { text: SKELETON_DESCRIPTION_TEXT[emphasis], width: "wide" })
|
|
140
156
|
] });
|
|
141
157
|
}
|
|
142
158
|
const showChevron = chevron ?? Boolean(onClick);
|
|
@@ -485,31 +485,57 @@ function Modal({
|
|
|
485
485
|
}
|
|
486
486
|
|
|
487
487
|
// ../../components/src/NavigationButton/NavigationButton.tsx
|
|
488
|
-
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}) {
|
|
496
|
-
const classes = [
|
|
488
|
+
import { Fragment, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
489
|
+
var TYPOGRAPHY_CLASS6 = {
|
|
490
|
+
md: "palda-text-body2-regular-semibold",
|
|
491
|
+
sm: "palda-text-body2-regular-medium"
|
|
492
|
+
};
|
|
493
|
+
function classesFor(size, selected, className) {
|
|
494
|
+
return [
|
|
497
495
|
"palda-navigation-button",
|
|
496
|
+
`palda-navigation-button--${size}`,
|
|
498
497
|
selected ? "palda-navigation-button--selected" : "",
|
|
499
|
-
|
|
498
|
+
TYPOGRAPHY_CLASS6[size],
|
|
500
499
|
className
|
|
501
500
|
].filter(Boolean).join(" ");
|
|
502
|
-
|
|
501
|
+
}
|
|
502
|
+
function contentFor(icon, children) {
|
|
503
|
+
return /* @__PURE__ */ jsxs9(Fragment, { children: [
|
|
504
|
+
icon ? /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", className: "palda-navigation-button__icon", children: icon }) : null,
|
|
505
|
+
/* @__PURE__ */ jsx10("span", { className: "palda-navigation-button__label", children })
|
|
506
|
+
] });
|
|
507
|
+
}
|
|
508
|
+
function NavigationButton(props) {
|
|
509
|
+
if (props.href !== void 0) {
|
|
510
|
+
const {
|
|
511
|
+
children: children2,
|
|
512
|
+
className: className2 = "",
|
|
513
|
+
href,
|
|
514
|
+
icon: icon2,
|
|
515
|
+
selected: selected2 = false,
|
|
516
|
+
size: size2 = "md",
|
|
517
|
+
...anchorProps
|
|
518
|
+
} = props;
|
|
519
|
+
return /* @__PURE__ */ jsx10(
|
|
520
|
+
"a",
|
|
521
|
+
{
|
|
522
|
+
...anchorProps,
|
|
523
|
+
"aria-current": selected2 ? "page" : void 0,
|
|
524
|
+
className: classesFor(size2, selected2, className2),
|
|
525
|
+
href,
|
|
526
|
+
children: contentFor(icon2, children2)
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const { children, className = "", icon, selected = false, size = "md", ...buttonProps } = props;
|
|
531
|
+
return /* @__PURE__ */ jsx10(
|
|
503
532
|
"button",
|
|
504
533
|
{
|
|
505
|
-
...
|
|
534
|
+
...buttonProps,
|
|
506
535
|
"aria-current": selected ? "page" : void 0,
|
|
507
|
-
className:
|
|
536
|
+
className: classesFor(size, selected, className),
|
|
508
537
|
type: "button",
|
|
509
|
-
children:
|
|
510
|
-
icon ? /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", className: "palda-navigation-button__icon", children: icon }) : null,
|
|
511
|
-
/* @__PURE__ */ jsx10("span", { className: "palda-navigation-button__label", children })
|
|
512
|
-
]
|
|
538
|
+
children: contentFor(icon, children)
|
|
513
539
|
}
|
|
514
540
|
);
|
|
515
541
|
}
|
|
@@ -546,8 +572,27 @@ function Radio({ children, className = "", disabled, ...props }) {
|
|
|
546
572
|
] });
|
|
547
573
|
}
|
|
548
574
|
|
|
575
|
+
// ../../components/src/Skeleton/Skeleton.tsx
|
|
576
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
577
|
+
function Skeleton({
|
|
578
|
+
className = "",
|
|
579
|
+
shape = "text",
|
|
580
|
+
text = "body2",
|
|
581
|
+
width = "full",
|
|
582
|
+
...props
|
|
583
|
+
}) {
|
|
584
|
+
const classes = [
|
|
585
|
+
"palda-skeleton",
|
|
586
|
+
`palda-skeleton--${shape}`,
|
|
587
|
+
shape === "text" ? `palda-skeleton--${text}` : "",
|
|
588
|
+
shape === "text" ? `palda-skeleton--${width}` : "",
|
|
589
|
+
className
|
|
590
|
+
].filter(Boolean).join(" ");
|
|
591
|
+
return /* @__PURE__ */ jsx13("span", { ...props, "aria-hidden": "true", className: classes });
|
|
592
|
+
}
|
|
593
|
+
|
|
549
594
|
// ../../components/src/Switch/Switch.tsx
|
|
550
|
-
import { jsx as
|
|
595
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
551
596
|
function Switch({ className = "", disabled, size = "md", ...props }) {
|
|
552
597
|
const classes = [
|
|
553
598
|
"palda-switch",
|
|
@@ -556,7 +601,7 @@ function Switch({ className = "", disabled, size = "md", ...props }) {
|
|
|
556
601
|
className
|
|
557
602
|
].filter(Boolean).join(" ");
|
|
558
603
|
return /* @__PURE__ */ jsxs11("label", { className: classes, children: [
|
|
559
|
-
/* @__PURE__ */
|
|
604
|
+
/* @__PURE__ */ jsx14(
|
|
560
605
|
"input",
|
|
561
606
|
{
|
|
562
607
|
...props,
|
|
@@ -566,12 +611,12 @@ function Switch({ className = "", disabled, size = "md", ...props }) {
|
|
|
566
611
|
type: "checkbox"
|
|
567
612
|
}
|
|
568
613
|
),
|
|
569
|
-
/* @__PURE__ */
|
|
614
|
+
/* @__PURE__ */ jsx14("span", { "aria-hidden": "true", className: "palda-switch__track", children: /* @__PURE__ */ jsx14("span", { className: "palda-switch__knob" }) })
|
|
570
615
|
] });
|
|
571
616
|
}
|
|
572
617
|
|
|
573
618
|
// ../../components/src/Tab/Tab.tsx
|
|
574
|
-
import { jsx as
|
|
619
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
575
620
|
function Tab({
|
|
576
621
|
children,
|
|
577
622
|
className = "",
|
|
@@ -587,15 +632,15 @@ function Tab({
|
|
|
587
632
|
className
|
|
588
633
|
].filter(Boolean).join(" ");
|
|
589
634
|
return /* @__PURE__ */ jsxs12("button", { ...props, "aria-selected": selected, className: classes, role: "tab", type: "button", children: [
|
|
590
|
-
icon ? /* @__PURE__ */
|
|
591
|
-
/* @__PURE__ */
|
|
592
|
-
badge ? /* @__PURE__ */
|
|
635
|
+
icon ? /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", className: "palda-tab__icon", children: icon }) : null,
|
|
636
|
+
/* @__PURE__ */ jsx15("span", { className: "palda-tab__label", children }),
|
|
637
|
+
badge ? /* @__PURE__ */ jsx15("span", { className: "palda-tab__badge", children: badge }) : null
|
|
593
638
|
] });
|
|
594
639
|
}
|
|
595
640
|
|
|
596
641
|
// ../../components/src/TextField/TextField.tsx
|
|
597
642
|
import { useId as useId3, useState as useState2 } from "react";
|
|
598
|
-
import { jsx as
|
|
643
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
599
644
|
var LABEL_TYPOGRAPHY_CLASS3 = "palda-text-body2-regular-semibold";
|
|
600
645
|
var FOOTER_TYPOGRAPHY_CLASS = "palda-text-label-regular-semibold";
|
|
601
646
|
var CONTROL_TYPOGRAPHY_CLASS = {
|
|
@@ -657,17 +702,17 @@ function TextField({
|
|
|
657
702
|
return /* @__PURE__ */ jsxs13("div", { className: classes, children: [
|
|
658
703
|
label === void 0 || label === null || label === "" ? null : /* @__PURE__ */ jsxs13("label", { className: `palda-text-field__label ${LABEL_TYPOGRAPHY_CLASS3}`, htmlFor: controlId, children: [
|
|
659
704
|
label,
|
|
660
|
-
required ? /* @__PURE__ */
|
|
705
|
+
required ? /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "palda-text-field__required", children: "*" }) : null
|
|
661
706
|
] }),
|
|
662
707
|
/* @__PURE__ */ jsxs13("div", { className: "palda-text-field__field", children: [
|
|
663
708
|
size === "md" ? (
|
|
664
709
|
// input과 textarea는 같은 prop을 받지만 이벤트 타입만 다르다.
|
|
665
|
-
/* @__PURE__ */
|
|
666
|
-
) : /* @__PURE__ */
|
|
667
|
-
icon ? /* @__PURE__ */
|
|
710
|
+
/* @__PURE__ */ jsx16("textarea", { ...controlProps })
|
|
711
|
+
) : /* @__PURE__ */ jsx16("input", { ...controlProps }),
|
|
712
|
+
icon ? /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "palda-text-field__icon", children: icon }) : null
|
|
668
713
|
] }),
|
|
669
714
|
description || showCount ? /* @__PURE__ */ jsxs13("div", { className: `palda-text-field__footer ${FOOTER_TYPOGRAPHY_CLASS}`, children: [
|
|
670
|
-
/* @__PURE__ */
|
|
715
|
+
/* @__PURE__ */ jsx16("span", { className: "palda-text-field__description", id: descriptionId, children: description }),
|
|
671
716
|
showCount ? /* @__PURE__ */ jsxs13("span", { className: "palda-text-field__count", id: countId, children: [
|
|
672
717
|
length,
|
|
673
718
|
"/",
|
|
@@ -677,6 +722,22 @@ function TextField({
|
|
|
677
722
|
] });
|
|
678
723
|
}
|
|
679
724
|
|
|
725
|
+
// ../../components/src/Thumbnail/Thumbnail.tsx
|
|
726
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
727
|
+
function Thumbnail({
|
|
728
|
+
alt,
|
|
729
|
+
className = "",
|
|
730
|
+
// 상품 목록은 수백 개까지 가므로 기본을 lazy로 둔다. 첫 화면에 보이는 자리는 쓰는 쪽이
|
|
731
|
+
// `loading="eager"`로 되돌린다.
|
|
732
|
+
loading = "lazy",
|
|
733
|
+
size = "md",
|
|
734
|
+
src,
|
|
735
|
+
...props
|
|
736
|
+
}) {
|
|
737
|
+
const classes = ["palda-thumbnail", `palda-thumbnail--${size}`, className].filter(Boolean).join(" ");
|
|
738
|
+
return /* @__PURE__ */ jsx17("img", { ...props, alt, className: classes, loading, src });
|
|
739
|
+
}
|
|
740
|
+
|
|
680
741
|
// ../../components/src/Toast/toastStore.ts
|
|
681
742
|
var queue = [];
|
|
682
743
|
var lastId = 0;
|
|
@@ -711,8 +772,8 @@ import { useSyncExternalStore } from "react";
|
|
|
711
772
|
|
|
712
773
|
// ../../components/src/Toast/Toast.tsx
|
|
713
774
|
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
714
|
-
import { jsx as
|
|
715
|
-
var
|
|
775
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
776
|
+
var TYPOGRAPHY_CLASS7 = "palda-text-body2-reading-medium";
|
|
716
777
|
var DEFAULT_DURATION_MS = 5e3;
|
|
717
778
|
function Toast({
|
|
718
779
|
actionLabel,
|
|
@@ -741,7 +802,7 @@ function Toast({
|
|
|
741
802
|
remainingRef.current = Math.max(remainingRef.current - (Date.now() - startedAt), 0);
|
|
742
803
|
};
|
|
743
804
|
}, [duration, onClose, paused]);
|
|
744
|
-
const classes = ["palda-toast", `palda-toast--${variant}`,
|
|
805
|
+
const classes = ["palda-toast", `palda-toast--${variant}`, TYPOGRAPHY_CLASS7, className].filter(Boolean).join(" ");
|
|
745
806
|
return /* @__PURE__ */ jsxs14(
|
|
746
807
|
"div",
|
|
747
808
|
{
|
|
@@ -752,17 +813,17 @@ function Toast({
|
|
|
752
813
|
onMouseLeave: () => setPaused(false),
|
|
753
814
|
role: variant === "danger" ? "alert" : "status",
|
|
754
815
|
children: [
|
|
755
|
-
icon ? /* @__PURE__ */
|
|
756
|
-
/* @__PURE__ */
|
|
757
|
-
actionLabel ? /* @__PURE__ */
|
|
758
|
-
onClose ? /* @__PURE__ */
|
|
816
|
+
icon ? /* @__PURE__ */ jsx18("span", { "aria-hidden": "true", className: "palda-toast__icon", children: icon }) : null,
|
|
817
|
+
/* @__PURE__ */ jsx18("span", { className: "palda-toast__content", children }),
|
|
818
|
+
actionLabel ? /* @__PURE__ */ jsx18("button", { className: "palda-toast__action", onClick: onAction, type: "button", children: actionLabel }) : null,
|
|
819
|
+
onClose ? /* @__PURE__ */ jsx18(
|
|
759
820
|
"button",
|
|
760
821
|
{
|
|
761
822
|
"aria-label": closeLabel,
|
|
762
823
|
className: "palda-toast__close",
|
|
763
824
|
onClick: onClose,
|
|
764
825
|
type: "button",
|
|
765
|
-
children: /* @__PURE__ */
|
|
826
|
+
children: /* @__PURE__ */ jsx18(CloseIcon, {})
|
|
766
827
|
}
|
|
767
828
|
) : null
|
|
768
829
|
]
|
|
@@ -771,12 +832,12 @@ function Toast({
|
|
|
771
832
|
}
|
|
772
833
|
|
|
773
834
|
// ../../components/src/Toast/Toaster.tsx
|
|
774
|
-
import { jsx as
|
|
835
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
775
836
|
import { createElement } from "react";
|
|
776
837
|
function Toaster({ className = "", placement = "bottom-center" }) {
|
|
777
838
|
const toasts = useSyncExternalStore(subscribeToToasts, getToasts, getToasts);
|
|
778
839
|
const classes = ["palda-toaster", `palda-toaster--${placement}`, className].filter(Boolean).join(" ");
|
|
779
|
-
return /* @__PURE__ */
|
|
840
|
+
return /* @__PURE__ */ jsx19("div", { className: classes, children: toasts.map(({ id, message, onAction, ...options }) => /* @__PURE__ */ createElement(
|
|
780
841
|
Toast,
|
|
781
842
|
{
|
|
782
843
|
...options,
|
|
@@ -802,7 +863,7 @@ import {
|
|
|
802
863
|
useRef as useRef4,
|
|
803
864
|
useState as useState4
|
|
804
865
|
} from "react";
|
|
805
|
-
import { jsx as
|
|
866
|
+
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
806
867
|
var OPPOSITE = {
|
|
807
868
|
bottom: "top",
|
|
808
869
|
left: "right",
|
|
@@ -952,7 +1013,7 @@ function Tooltip({ children, className = "", label, placement = "top" }) {
|
|
|
952
1013
|
),
|
|
953
1014
|
children: [
|
|
954
1015
|
label,
|
|
955
|
-
/* @__PURE__ */
|
|
1016
|
+
/* @__PURE__ */ jsx20("span", { "aria-hidden": "true", className: "palda-tooltip__arrow" })
|
|
956
1017
|
]
|
|
957
1018
|
}
|
|
958
1019
|
) : null
|
|
@@ -975,9 +1036,11 @@ export {
|
|
|
975
1036
|
NavigationButton,
|
|
976
1037
|
ProgressBar,
|
|
977
1038
|
Radio,
|
|
1039
|
+
Skeleton,
|
|
978
1040
|
Switch,
|
|
979
1041
|
Tab,
|
|
980
1042
|
TextField,
|
|
1043
|
+
Thumbnail,
|
|
981
1044
|
toast,
|
|
982
1045
|
Toaster,
|
|
983
1046
|
Tooltip
|
package/dist/guide/README.md
CHANGED
|
@@ -35,13 +35,13 @@ import { Button } from '@highpixel-co/palda-design-system/ui';
|
|
|
35
35
|
`styles.css`를 함께 불러오지 않으면 토큰이 없어 색이 나오지 않습니다.
|
|
36
36
|
|
|
37
37
|
`tokens/`, `icons/`, `components/`, `patterns/`는 저장소 내부 모듈이며 개별 배포하지 않습니다.
|
|
38
|
-
배포와 소비 설정은 [`packages/palda-design-system/README.md`](https://github.com/highpixel-co/palda-design-system/blob/
|
|
38
|
+
배포와 소비 설정은 [`packages/palda-design-system/README.md`](https://github.com/highpixel-co/palda-design-system/blob/abee826d0afda16bd3776f015931e0a820a8876e/packages/palda-design-system/README.md)를
|
|
39
39
|
따릅니다.
|
|
40
40
|
|
|
41
|
-
- 디자이너: [`docs/REPOSITORY_GUIDE.md`](https://github.com/highpixel-co/palda-design-system/blob/
|
|
41
|
+
- 디자이너: [`docs/REPOSITORY_GUIDE.md`](https://github.com/highpixel-co/palda-design-system/blob/abee826d0afda16bd3776f015931e0a820a8876e/docs/REPOSITORY_GUIDE.md)부터 읽습니다.
|
|
42
42
|
- 디자인 원칙: [`docs/DESIGN_PRINCIPLES.md`](docs/DESIGN_PRINCIPLES.md)
|
|
43
43
|
- Figma 운영: [`docs/FIGMA_WORKFLOW.md`](docs/FIGMA_WORKFLOW.md)
|
|
44
|
-
- 기여 절차: [`docs/CONTRIBUTING.md`](https://github.com/highpixel-co/palda-design-system/blob/
|
|
44
|
+
- 기여 절차: [`docs/CONTRIBUTING.md`](https://github.com/highpixel-co/palda-design-system/blob/abee826d0afda16bd3776f015931e0a820a8876e/docs/CONTRIBUTING.md)
|
|
45
45
|
|
|
46
46
|
현재 토큰과 컴포넌트 값은 구조를 보여주기 위한 예시이며, 디자이너 검토 후 확정해야 합니다.
|
|
47
47
|
|
|
@@ -98,7 +98,15 @@ dropdown-button:
|
|
|
98
98
|
outline: 테두리가 있는 기본 트리거
|
|
99
99
|
subtle: 테두리 없는 저강도 트리거. 툴바와 인라인
|
|
100
100
|
note: 트리거는 시안 확정. 메뉴 패널·align·chevron 아이콘은 확정 전이다.
|
|
101
|
-
planned:
|
|
101
|
+
planned: >-
|
|
102
|
+
다중 선택은 아직 없다. 들일 때 선택된 항목은 체크 아이콘(`CheckIcon`)으로 표시한다(2026-07-31
|
|
103
|
+
확정). 아이콘은 2026-07-31에 추가했다. 남은 것은 `DropdownButtonItem`에 선택 상태를 어떻게
|
|
104
|
+
두고 `onSelect`가 무엇을 돌려줄지 정하는 것이며, 공개 API가 바뀌므로 결정 기록을 함께 남긴다.
|
|
105
|
+
상단 행의 스토어 스위처를 여기로 흡수하는 것은 메뉴 패널이 확정된 뒤로 미뤘다(2026-09-05).
|
|
106
|
+
하려는 일은 같지만(단일 선택) 트리거가 셋 다르다 — 라벨 앞 아이콘 슬롯이 없고, 라벨이
|
|
107
|
+
body1/semibold가 아니라 body2/medium 고정이고, chevron이 border/strong이 아니라
|
|
108
|
+
primary/default다. 축 이름부터 걸린다: 둘 다 높이 36px이라 `size`가 아니고 `variant`는 이미
|
|
109
|
+
보더 유무다. 그때까지 스위처는 preview 시안의 손 CSS로 남는다.
|
|
102
110
|
|
|
103
111
|
icon-button:
|
|
104
112
|
status: stable
|
|
@@ -156,13 +164,28 @@ navigation-button:
|
|
|
156
164
|
story: components/src/NavigationButton/NavigationButton.stories.tsx
|
|
157
165
|
figma: TODO
|
|
158
166
|
use_for:
|
|
159
|
-
- 사이드바·내비게이션의 화면 이동 항목
|
|
167
|
+
- 사이드바·내비게이션의 화면 이동 항목 (size="md")
|
|
168
|
+
- 상단 행의 마이페이지 진입점 — 내 아이디가 든다 (size="sm")
|
|
160
169
|
- 지금 보고 있는 화면을 목록에서 표시하기
|
|
161
170
|
avoid_for:
|
|
162
171
|
- 화면을 옮기지 않는 액션 (Button을 쓴다)
|
|
163
172
|
- 목록을 좁히는 필터 토글 (Chip을 쓴다)
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
- 문장 안이나 보조 안내 줄의 이동 (Link를 쓴다 — 아이콘 슬롯도 hover 채움도 없는 자리다)
|
|
174
|
+
size:
|
|
175
|
+
md: 사이드바 항목. 48px · 아이콘 24 · semibold · 부모 폭을 채운다. 기본값이다
|
|
176
|
+
sm: 상단 행 항목. 36px · 아이콘 20 · medium · 내용만큼
|
|
177
|
+
note: >-
|
|
178
|
+
현재 위치는 색만으로 전달하지 않도록 `aria-current="page"`로 함께 노출한다. 아이콘은 라벨
|
|
179
|
+
앞에 오고 라벨은 넘치면 잘린다. `href`를 주면 `<a>`로 그려진다 — 화면을 옮기는 항목은
|
|
180
|
+
링크여야 새 탭·주소 복사·가운데 클릭이 동작한다(Link와 같은 근거). 링크에는 `disabled`가
|
|
181
|
+
없다. 갈 수 없는 링크는 링크로 두지 않는다.
|
|
182
|
+
size는 크기만 가르지 않고 색도 함께 간다 — md는 회색 사이드바 위 목록이라 안 고른 항목이
|
|
183
|
+
흐리고(text/disabled · 아이콘 border/strong), sm은 흰 상단 행의 단독 항목이라 흐리지
|
|
184
|
+
않다(text/primary · 아이콘 primary/default). 두 조합은 각각 시안에 있고, 축을 색과 크기로
|
|
185
|
+
쪼개면 시안에 없는 조합이 열린다(0003).
|
|
186
|
+
selected 채움은 md에만 있다. 선택 표시는 형제들 사이에서만 뜻이 있는데 상단 행의 항목은
|
|
187
|
+
하나뿐이라 대조할 형제가 없다 — sm은 aria-current만으로 현재 위치를 전한다(2026-09-05 확정).
|
|
188
|
+
planned: 시안에 hover/pressed가 없어 default 위에 `state/hover-pressed` 채움만 얹는 것으로 정의했고 확정 전이다. md에서 선택 안 된 항목의 텍스트는 시안대로 `text/disabled`라 흰 배경에서 대비가 2.3:1로 미달이다 — sm에 물려주지 않았고, md 쪽은 그대로 남아 있다.
|
|
166
189
|
|
|
167
190
|
progress-bar:
|
|
168
191
|
status: draft
|
|
@@ -196,6 +219,30 @@ radio:
|
|
|
196
219
|
- 선택지를 펼쳐 놓을 자리가 좁을 때 (DropdownButton을 쓴다)
|
|
197
220
|
note: 라벨을 넘기지 않으면 접근 가능한 이름을 `aria-label`로 준다.
|
|
198
221
|
|
|
222
|
+
skeleton:
|
|
223
|
+
status: draft
|
|
224
|
+
source: components/src/Skeleton/Skeleton.tsx
|
|
225
|
+
story: components/src/Skeleton/Skeleton.stories.tsx
|
|
226
|
+
figma: TODO
|
|
227
|
+
use_for:
|
|
228
|
+
- 값이 오기 전 그 자리를 잡아 두기
|
|
229
|
+
- 판정이 안 끝난 화면에서 본문 자리 세우기 (게이팅·잔액 등)
|
|
230
|
+
avoid_for:
|
|
231
|
+
- 얼마나 걸릴지 모르는 대기 (자리를 잡을 것이 없다)
|
|
232
|
+
- 버튼을 누른 뒤의 진행 표시 (Button의 `disabled`를 쓴다)
|
|
233
|
+
- 끝이 정해진 작업의 진행률 (ProgressBar를 쓴다)
|
|
234
|
+
- 값이 비어 있는 것 (EmptyState를 쓴다 — 비었다는 것은 이미 도착한 답이다)
|
|
235
|
+
note: >-
|
|
236
|
+
글자 자리(`shape="text"`)의 높이는 대신할 텍스트 스타일의 글자 크기다. 갈리면 값이 도착할 때
|
|
237
|
+
자리가 튄다. 면 자리(`shape="block"`)는 크기를 스스로 갖지 않고 놓인 자리를 채운다. 폭은
|
|
238
|
+
`full`·`wide`·`narrow` 셋뿐이고 값으로 받지 않는다 — 글자 폭에 정확한 값이 없고, 자리마다
|
|
239
|
+
다른 값이 들어가면 같은 목록의 뼈대가 갈린다. 여러 줄은 화면이 조립한다. 늘 `aria-hidden`이라
|
|
240
|
+
진행 중이라는 사실은 이것을 놓은 자리가 알린다. 결정 근거는 0038이다.
|
|
241
|
+
planned: >-
|
|
242
|
+
시안 없이 만든 초안. 박동은 opacity 1↔0.4이고 `duration/normal`을 쓴다 — ListRow의 뼈대가
|
|
243
|
+
쓰던 값 그대로다. shimmer(쓸고 지나가는 빛)는 두지 않았다. size가 없다 — 높이는 대신할 글자가
|
|
244
|
+
정하고 면은 자리가 정해서, 고를 축이 아니다.
|
|
245
|
+
|
|
199
246
|
switch:
|
|
200
247
|
status: stable
|
|
201
248
|
source: components/src/Switch/Switch.tsx
|
|
@@ -242,6 +289,26 @@ textfield:
|
|
|
242
289
|
subtle: 배경과 보더 없음. 인라인과 저강도
|
|
243
290
|
note: 크기가 렌더 요소를 정한다. `md`는 120px textarea, `sm`은 40px input이다. 글자 수 표시는 `maxLength`로 켠다.
|
|
244
291
|
|
|
292
|
+
thumbnail:
|
|
293
|
+
status: draft
|
|
294
|
+
source: components/src/Thumbnail/Thumbnail.tsx
|
|
295
|
+
story: components/src/Thumbnail/Thumbnail.stories.tsx
|
|
296
|
+
figma: TODO
|
|
297
|
+
use_for:
|
|
298
|
+
- 목록 행이나 카드 안에서 항목을 알아보게 하는 작은 사각 이미지
|
|
299
|
+
- 고른 상품처럼 여러 개를 나란히 늘어놓는 자리
|
|
300
|
+
avoid_for:
|
|
301
|
+
- 사람을 나타내는 원형 아바타 (다른 것이다. 지금 시스템에 없다)
|
|
302
|
+
- 읽어야 하는 크기의 이미지 (템플릿 미리보기는 화면이 직접 그린다)
|
|
303
|
+
note: >-
|
|
304
|
+
`alt`가 필수다. 장식이면 빈 문자열을 명시적으로 넘긴다 — 빠뜨린 것과 장식인 것이 코드에서
|
|
305
|
+
갈려야 한다. 목록이 길어질 수 있어 `loading`의 기본이 `lazy`이며, 첫 화면에 보이는 자리는
|
|
306
|
+
쓰는 쪽이 `eager`로 되돌린다.
|
|
307
|
+
planned: >-
|
|
308
|
+
md 40 · sm 32는 2026-09-04에 고른 값이지 실측이 아니다. Figma에 대응하는 컴포넌트가 없어
|
|
309
|
+
대조할 것이 없었고, 시안이 다른 값을 요구하면 다시 본다. 이미지가 없거나 깨졌을 때는 회색
|
|
310
|
+
면만 남긴다 — 대체 아이콘을 둘지와 어떤 아이콘인지는 확정 전이다.
|
|
311
|
+
|
|
245
312
|
toast:
|
|
246
313
|
status: draft
|
|
247
314
|
source: components/src/Toast/Toaster.tsx
|
|
@@ -151,6 +151,32 @@ progress-bar:
|
|
|
151
151
|
- 높이 8px은 명세 실측값이고 대응 토큰이 없다. 반복이 확인되면 토큰으로 올릴 것인가
|
|
152
152
|
- 색으로 상태를 가르는 variant가 필요한가 — 지금은 채움이 accent가 아니라 primary 하나다
|
|
153
153
|
|
|
154
|
+
skeleton:
|
|
155
|
+
catalog: components
|
|
156
|
+
entry: skeleton
|
|
157
|
+
based_on_none_reason: 값이 오기 전 자리를 잡아 두는 Stable 요소가 없다 — ListRow가 뼈대를 자기 안에 감춰 두고 있었다
|
|
158
|
+
reused:
|
|
159
|
+
- ListRow 뼈대가 쓰던 색·라운드·박동 값 그대로 (surface/sunken, radius/control-sm, duration/normal, opacity 1↔0.4)
|
|
160
|
+
- 폭 40%·70% — ListRow 뼈대의 제목·설명 폭이 narrow·wide가 됐다
|
|
161
|
+
- 높이를 글자 크기로 잡는 규칙 — ListRow가 emphasis에 따라 뼈대를 같이 올리던 것과 같다
|
|
162
|
+
gap: 잔액·게이팅 판정을 기다리는 동안 화면이 세울 것이 없어, 없는 값으로 본문을 그렸다가 지웠다
|
|
163
|
+
rejected_alternatives:
|
|
164
|
+
- 판정 전까지 아무것도 안 그리기 — 흰 화면이 남고 머리만 뜬 채로 기다린다
|
|
165
|
+
- ‘불러오는 중’ 글자 — 자리를 잡지 못해 값이 오면 그대로 튄다
|
|
166
|
+
- ProgressBar — 끝이 정해진 값의 진행률이라 얼마나 걸릴지 모르는 대기에 못 쓴다
|
|
167
|
+
- FormSection에 loading prop — 틀은 상태를 갖지 않는다 (DESIGN_GRAMMAR §상태는 누가 갖나)
|
|
168
|
+
- shimmer(쓸고 지나가는 빛) — 아직 오지 않은 값이 움직이는 것처럼 보인다
|
|
169
|
+
added:
|
|
170
|
+
- shape text·block 둘과 text body1·body2·label 셋, width full·wide·narrow 셋
|
|
171
|
+
- 면 자리(block) — ListRow 뼈대에는 글자 줄만 있었다. 카드·썸네일 자리를 이것이 받는다
|
|
172
|
+
unchanged:
|
|
173
|
+
- ListRow의 loading prop과 그 마크업 구조. 안에 든 뼈대만 이 컴포넌트로 갈아 끼운다
|
|
174
|
+
- 로딩을 컨트롤의 별도 상태로 두지 않는 규칙 (COMPONENT_POLICY §로딩) — Button은 그대로 disabled다
|
|
175
|
+
feedback_questions:
|
|
176
|
+
- 폭 셋(100·70·40%)으로 충분한가 — 목록 뼈대에서 줄마다 길이를 다르게 주고 싶은 자리가 나오는가
|
|
177
|
+
- 박동 주기 180ms가 맞는가 — ListRow에서 물려받은 값이고 뼈대 여럿이 함께 뛰면 산만할 수 있다
|
|
178
|
+
- text에 headline을 더할 자리가 있는가 — 지금은 머리가 늘 즉시 그려져서 안 뒀다
|
|
179
|
+
|
|
154
180
|
tab:
|
|
155
181
|
catalog: components
|
|
156
182
|
entry: tab
|
|
@@ -331,3 +357,23 @@ empty-state:
|
|
|
331
357
|
- Button action contract와 semantic text hierarchy
|
|
332
358
|
feedback_questions:
|
|
333
359
|
- 최대 폭과 vertical spacing과 action 강조가 적절한가
|
|
360
|
+
|
|
361
|
+
thumbnail:
|
|
362
|
+
catalog: components
|
|
363
|
+
entry: thumbnail
|
|
364
|
+
based_on_none_reason: 이미지를 담는 Stable 컴포넌트가 없다. Card는 면이고 Badge는 라벨이라 둘 다 이미지 자리가 아니다
|
|
365
|
+
reused:
|
|
366
|
+
- radius/control과 surface/raised — 목록 행의 채운 면과 같은 눈금이다
|
|
367
|
+
- size를 md·sm 두 단계로 두는 기존 컴포넌트 규칙
|
|
368
|
+
gap: 상품처럼 사진으로 알아보는 항목을 목록과 카드에 놓을 자리가 없다
|
|
369
|
+
rejected_alternatives:
|
|
370
|
+
- 화면마다 img에 클래스를 직접 주는 방식은 라운드와 크기가 화면마다 갈린다
|
|
371
|
+
- 원형 아바타로 겸용하는 것은 사람과 사물을 한 컴포넌트에 넣는 일이라 뜻이 흐려진다
|
|
372
|
+
- 대체 아이콘을 얹는 것은 어떤 아이콘인지 근거가 없어 자산을 지어내게 된다
|
|
373
|
+
added:
|
|
374
|
+
- md 40 · sm 32 두 단계의 사각 썸네일과 필수 alt
|
|
375
|
+
unchanged:
|
|
376
|
+
- 채운 면의 회색은 한 종류라는 배경 위계와 기존 radius 스케일
|
|
377
|
+
feedback_questions:
|
|
378
|
+
- md 40 · sm 32가 맞는 눈금인가 — 실측이 아니라 고른 값이다
|
|
379
|
+
- 이미지가 없거나 깨졌을 때 회색 면만 남기면 되는가, 대체 아이콘이 필요한가
|
|
@@ -87,11 +87,18 @@ form-section:
|
|
|
87
87
|
- 설정·정보 섹션을 제목 아래로 묶기 — 0011 이후 섹션의 기본 단위다
|
|
88
88
|
- 입력 필드를 제목 아래로 묶는 폼의 한 덩어리
|
|
89
89
|
- 섹션 단위 오류 표시
|
|
90
|
+
tone:
|
|
91
|
+
plain: 면이 없다. 0011의 기본값이고, 제목과 여백만으로 갈린다
|
|
92
|
+
filled: >-
|
|
93
|
+
내용만 옅은 회색 필(surface/raised)에 담고 제목은 캔버스에 남긴다. 섹션이 여럿 쌓여 제목과
|
|
94
|
+
여백만으로 묶음이 안 갈릴 때만 쓴다. 면 위에서 TextField는 흰 면으로 한 단계 밝아진다
|
|
90
95
|
note: >-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
섹션을 Card로 감싸지 않고 이것을 쓴다. 섹션 사이 여백은 AppShell이 --space-8(32)로 준다.
|
|
97
|
+
라벨↔컨트롤 간격은 TextField가 갖고 있어 여기서 다시 주지 않는다. 섹션 오류는 role="alert"이고,
|
|
98
|
+
필드 하나의 오류는 TextField의 error를 쓴다. 폼이 아닌 섹션에도 쓰이므로 이름이 좁다 —
|
|
99
|
+
개명 여부 확정 대기.
|
|
100
|
+
tone="filled"는 0011(박스 없는 것이 기본값)에서 벗어나는 자리다. 기본값은 여전히 plain이고,
|
|
101
|
+
쓰는 조건과 근거는 ADR 0037에 있다.
|
|
95
102
|
|
|
96
103
|
live-preview:
|
|
97
104
|
status: draft
|
|
@@ -53,7 +53,7 @@ Palda가 목표로 하는 것은 단순히 컴포넌트를 많이 보유하는
|
|
|
53
53
|
→ 섹션·카드 `body1`(16) → 행 `body2`(14) → 보조 `label`(12). 화면이 고르는 것은 페이지 제목과
|
|
54
54
|
**본문 목록 여부** 둘이고 나머지는 pattern이 이미 갖고 있다. 선언서의 본문 문장이 가리키는
|
|
55
55
|
목록은 `ListRow`의 `emphasis="strong"`으로 제목 `body1`(16) + 설명 `body2`(14)가 된다
|
|
56
|
-
([`decisions/0034`](https://github.com/highpixel-co/palda-design-system/blob/
|
|
56
|
+
([`decisions/0034`](https://github.com/highpixel-co/palda-design-system/blob/abee826d0afda16bd3776f015931e0a820a8876e/docs/decisions/0034-the-body-list-lifts-one-step.md)).
|
|
57
57
|
|
|
58
58
|
**주 액션의 자리**는 [`DESIGN_GRAMMAR.md`](DESIGN_GRAMMAR.md) §주 액션을 어디 두나가 갖는다 —
|
|
59
59
|
페이지 헤더 우측과 `BottomBar` 둘 다 맞는 자리이고, 읽다가 가끔 누르는 화면은 위, 채워 넣고
|
|
@@ -85,7 +85,7 @@ AI에게 가장 효과적인 기준은 긴 설명보다 실제 정답 화면이
|
|
|
85
85
|
- 데스크톱과 모바일에서 달라지는 부분
|
|
86
86
|
- loading·empty·error 처리 방식
|
|
87
87
|
|
|
88
|
-
산출물은 [`../examples/approved/`](https://github.com/highpixel-co/palda-design-system/tree/
|
|
88
|
+
산출물은 [`../examples/approved/`](https://github.com/highpixel-co/palda-design-system/tree/abee826d0afda16bd3776f015931e0a820a8876e/examples/approved)에 보관한다.
|
|
89
89
|
|
|
90
90
|
**완료 기준:** AI가 새 화면을 만들 때 가장 가까운 승인 화면을 골라 구조와 판단 기준을 재사용할
|
|
91
91
|
수 있다.
|
|
@@ -103,7 +103,7 @@ AI가 자주 만들 수 있는 잘못된 화면도 시각적으로 남긴다.
|
|
|
103
103
|
- 모바일에서 버튼이나 긴 한글이 잘리는 화면
|
|
104
104
|
|
|
105
105
|
각 사례에는 문제가 되는 이유와 올바른 대안을 함께 기록한다. 산출물은
|
|
106
|
-
[`../examples/anti-patterns/`](https://github.com/highpixel-co/palda-design-system/tree/
|
|
106
|
+
[`../examples/anti-patterns/`](https://github.com/highpixel-co/palda-design-system/tree/abee826d0afda16bd3776f015931e0a820a8876e/examples/anti-patterns)에 보관한다.
|
|
107
107
|
|
|
108
108
|
**완료 기준:** AI가 동일한 실수를 반복하지 않고, 잘못된 구현을 구체적인 사례와 비교해 수정할 수
|
|
109
109
|
있다.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
적는다. prop 이름과 기본값은 타입이 원본이므로 문서로 옮겨 적지 않는다.
|
|
16
16
|
|
|
17
17
|
**새 컴포넌트는 코드보다 먼저 property 목록과 상태 배치표를 낸다.** 상태는
|
|
18
|
-
[`decisions/0004`](https://github.com/highpixel-co/palda-design-system/blob/
|
|
18
|
+
[`decisions/0004`](https://github.com/highpixel-co/palda-design-system/blob/abee826d0afda16bd3776f015931e0a820a8876e/docs/decisions/0004-component-state-naming.md)의 다섯 종류(상호작용 · 네이티브 ·
|
|
19
19
|
값에서 파생 · 컴포넌트 소유 · 부모가 정함)에 배치하고, **prop으로 빼도 되는 것은 "부모가 정함"
|
|
20
20
|
하나뿐이다.** `hovered`·`focused`·`filled` 같은 prop은 만들지 않는다. pattern에서 어느 것이
|
|
21
21
|
상태를 소유하는지는 [`DESIGN_GRAMMAR.md`](DESIGN_GRAMMAR.md)의 §상태는 누가 갖나에 있다.
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
([`DESIGN_GRAMMAR.md`](DESIGN_GRAMMAR.md) §자리는 시스템이 정하지 않는다). 컴포넌트 시안은 셸이 없어 이 검사를 받지 않는다.
|
|
45
45
|
- **렌더해서 흐리게 본다.** 시안을 실제로 그린 뒤, 스크린샷을 흐리게 봤을 때 가장 먼저 보이는
|
|
46
46
|
것이 본문이어야 한다. 상태 배너·경고처럼 면적이 큰 것이 본문보다 먼저 보이면 그것을 뺀다
|
|
47
|
-
([`decisions/0024`](https://github.com/highpixel-co/palda-design-system/blob/
|
|
47
|
+
([`decisions/0024`](https://github.com/highpixel-co/palda-design-system/blob/abee826d0afda16bd3776f015931e0a820a8876e/docs/decisions/0024-state-belongs-to-the-nearest-owner.md)). 검사가 읽지
|
|
48
48
|
못하는 자리라 이 확인은 사람이 한다.
|
|
49
49
|
- **시안은 자기 클래스만 정의한다.** 시스템 클래스(`.palda-*`)를 셀렉터에 쓰지 않는다 — 붙이는
|
|
50
50
|
것은 마크업이 하고, 늘리거나 자리를 잡는 것은 감싸는 자리가 한다. pattern은 `className`을 받지
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
`Toast`다.
|
|
61
61
|
|
|
62
62
|
**진행 중은 글자가 아니라 `disabled`로 표현한다.** `Button`에 로딩 전용 prop이 없는 것이
|
|
63
|
-
그래서다([`components/src/Button/Button.tsx`](https://github.com/highpixel-co/palda-design-system/blob/
|
|
63
|
+
그래서다([`components/src/Button/Button.tsx`](https://github.com/highpixel-co/palda-design-system/blob/abee826d0afda16bd3776f015931e0a820a8876e/components/src/Button/Button.tsx)).
|
|
64
64
|
|
|
65
65
|
### "~기"로 끝난다
|
|
66
66
|
|