@highpixel-co/palda-design-system 0.5.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-Q4UXASZH.js → chunk-3OXUONDZ.js} +34 -10
- package/dist/{chunk-GPCPGABI.js → chunk-PWAQFOM3.js} +113 -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 +20 -5
- package/dist/guide/docs/ACCESSIBILITY.md +27 -1
- package/dist/guide/docs/AI_UI_DESIGNER_HANDOFF.md +6 -4
- package/dist/guide/docs/COMPONENT_POLICY.md +2 -2
- package/dist/guide/docs/CONTENT.md +1 -1
- package/dist/guide/docs/DESIGN_GRAMMAR.md +239 -37
- package/dist/guide/docs/DESIGN_PRINCIPLES.md +18 -9
- 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 +27 -16
- package/dist/harness/check.mjs +13 -7
- package/dist/harness/metadata.json +38 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -2
- package/dist/patterns.d.ts +27 -3
- package/dist/patterns.js +2 -2
- package/dist/scripts/check-examples.mjs +262 -57
- package/dist/styles.css +105 -35
- package/dist/ui.d.ts +73 -6
- 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
|
|
@@ -105,12 +113,27 @@ function FormSection({ children, description, error, footer, title }) {
|
|
|
105
113
|
|
|
106
114
|
// ../../patterns/src/ListRow/ListRow.tsx
|
|
107
115
|
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
108
|
-
var TITLE_TYPOGRAPHY_CLASS4 =
|
|
109
|
-
|
|
116
|
+
var TITLE_TYPOGRAPHY_CLASS4 = {
|
|
117
|
+
default: "palda-text-body2-regular-semibold",
|
|
118
|
+
strong: "palda-text-body1-regular-semibold"
|
|
119
|
+
};
|
|
120
|
+
var DESCRIPTION_TYPOGRAPHY_CLASS4 = {
|
|
121
|
+
default: "palda-text-label-reading-medium",
|
|
122
|
+
strong: "palda-text-body2-reading-medium"
|
|
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
|
+
};
|
|
110
132
|
function ListRow({
|
|
111
133
|
chevron,
|
|
112
134
|
children,
|
|
113
135
|
description,
|
|
136
|
+
emphasis = "default",
|
|
114
137
|
leading,
|
|
115
138
|
loading = false,
|
|
116
139
|
onClick,
|
|
@@ -122,21 +145,22 @@ function ListRow({
|
|
|
122
145
|
const classes = [
|
|
123
146
|
"palda-list-row",
|
|
124
147
|
`palda-list-row--${tone}`,
|
|
148
|
+
`palda-list-row--${emphasis}`,
|
|
125
149
|
selected ? "palda-list-row--selected" : "",
|
|
126
150
|
loading ? "palda-list-row--loading" : ""
|
|
127
151
|
].filter(Boolean).join(" ");
|
|
128
152
|
if (loading) {
|
|
129
153
|
return /* @__PURE__ */ jsxs6("div", { "aria-hidden": "true", className: classes, children: [
|
|
130
|
-
/* @__PURE__ */ jsx6(
|
|
131
|
-
/* @__PURE__ */ jsx6(
|
|
154
|
+
/* @__PURE__ */ jsx6(Skeleton, { text: SKELETON_TITLE_TEXT[emphasis], width: "narrow" }),
|
|
155
|
+
/* @__PURE__ */ jsx6(Skeleton, { text: SKELETON_DESCRIPTION_TEXT[emphasis], width: "wide" })
|
|
132
156
|
] });
|
|
133
157
|
}
|
|
134
158
|
const showChevron = chevron ?? Boolean(onClick);
|
|
135
159
|
const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
136
160
|
leading ? /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", className: "palda-list-row__leading", children: leading }) : null,
|
|
137
161
|
/* @__PURE__ */ jsxs6("span", { className: "palda-list-row__copy", children: [
|
|
138
|
-
/* @__PURE__ */ jsx6("span", { className: TITLE_TYPOGRAPHY_CLASS4, children: title }),
|
|
139
|
-
description ? /* @__PURE__ */ jsx6("span", { className: `palda-list-row__description ${DESCRIPTION_TYPOGRAPHY_CLASS4}`, children: description }) : null,
|
|
162
|
+
/* @__PURE__ */ jsx6("span", { className: TITLE_TYPOGRAPHY_CLASS4[emphasis], children: title }),
|
|
163
|
+
description ? /* @__PURE__ */ jsx6("span", { className: `palda-list-row__description ${DESCRIPTION_TYPOGRAPHY_CLASS4[emphasis]}`, children: description }) : null,
|
|
140
164
|
children
|
|
141
165
|
] }),
|
|
142
166
|
trailing ? /* @__PURE__ */ jsx6("span", { className: "palda-list-row__trailing", children: trailing }) : null,
|
|
@@ -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,13 +611,20 @@ 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
|
|
575
|
-
function Tab({
|
|
619
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
620
|
+
function Tab({
|
|
621
|
+
children,
|
|
622
|
+
className = "",
|
|
623
|
+
icon,
|
|
624
|
+
badge,
|
|
625
|
+
selected = false,
|
|
626
|
+
...props
|
|
627
|
+
}) {
|
|
576
628
|
const classes = [
|
|
577
629
|
"palda-tab",
|
|
578
630
|
selected ? "palda-tab--selected" : "",
|
|
@@ -580,14 +632,15 @@ function Tab({ children, className = "", icon, selected = false, ...props }) {
|
|
|
580
632
|
className
|
|
581
633
|
].filter(Boolean).join(" ");
|
|
582
634
|
return /* @__PURE__ */ jsxs12("button", { ...props, "aria-selected": selected, className: classes, role: "tab", type: "button", children: [
|
|
583
|
-
icon ? /* @__PURE__ */
|
|
584
|
-
/* @__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
|
|
585
638
|
] });
|
|
586
639
|
}
|
|
587
640
|
|
|
588
641
|
// ../../components/src/TextField/TextField.tsx
|
|
589
642
|
import { useId as useId3, useState as useState2 } from "react";
|
|
590
|
-
import { jsx as
|
|
643
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
591
644
|
var LABEL_TYPOGRAPHY_CLASS3 = "palda-text-body2-regular-semibold";
|
|
592
645
|
var FOOTER_TYPOGRAPHY_CLASS = "palda-text-label-regular-semibold";
|
|
593
646
|
var CONTROL_TYPOGRAPHY_CLASS = {
|
|
@@ -649,17 +702,17 @@ function TextField({
|
|
|
649
702
|
return /* @__PURE__ */ jsxs13("div", { className: classes, children: [
|
|
650
703
|
label === void 0 || label === null || label === "" ? null : /* @__PURE__ */ jsxs13("label", { className: `palda-text-field__label ${LABEL_TYPOGRAPHY_CLASS3}`, htmlFor: controlId, children: [
|
|
651
704
|
label,
|
|
652
|
-
required ? /* @__PURE__ */
|
|
705
|
+
required ? /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "palda-text-field__required", children: "*" }) : null
|
|
653
706
|
] }),
|
|
654
707
|
/* @__PURE__ */ jsxs13("div", { className: "palda-text-field__field", children: [
|
|
655
708
|
size === "md" ? (
|
|
656
709
|
// input과 textarea는 같은 prop을 받지만 이벤트 타입만 다르다.
|
|
657
|
-
/* @__PURE__ */
|
|
658
|
-
) : /* @__PURE__ */
|
|
659
|
-
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
|
|
660
713
|
] }),
|
|
661
714
|
description || showCount ? /* @__PURE__ */ jsxs13("div", { className: `palda-text-field__footer ${FOOTER_TYPOGRAPHY_CLASS}`, children: [
|
|
662
|
-
/* @__PURE__ */
|
|
715
|
+
/* @__PURE__ */ jsx16("span", { className: "palda-text-field__description", id: descriptionId, children: description }),
|
|
663
716
|
showCount ? /* @__PURE__ */ jsxs13("span", { className: "palda-text-field__count", id: countId, children: [
|
|
664
717
|
length,
|
|
665
718
|
"/",
|
|
@@ -669,6 +722,22 @@ function TextField({
|
|
|
669
722
|
] });
|
|
670
723
|
}
|
|
671
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
|
+
|
|
672
741
|
// ../../components/src/Toast/toastStore.ts
|
|
673
742
|
var queue = [];
|
|
674
743
|
var lastId = 0;
|
|
@@ -703,8 +772,8 @@ import { useSyncExternalStore } from "react";
|
|
|
703
772
|
|
|
704
773
|
// ../../components/src/Toast/Toast.tsx
|
|
705
774
|
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
706
|
-
import { jsx as
|
|
707
|
-
var
|
|
775
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
776
|
+
var TYPOGRAPHY_CLASS7 = "palda-text-body2-reading-medium";
|
|
708
777
|
var DEFAULT_DURATION_MS = 5e3;
|
|
709
778
|
function Toast({
|
|
710
779
|
actionLabel,
|
|
@@ -733,7 +802,7 @@ function Toast({
|
|
|
733
802
|
remainingRef.current = Math.max(remainingRef.current - (Date.now() - startedAt), 0);
|
|
734
803
|
};
|
|
735
804
|
}, [duration, onClose, paused]);
|
|
736
|
-
const classes = ["palda-toast", `palda-toast--${variant}`,
|
|
805
|
+
const classes = ["palda-toast", `palda-toast--${variant}`, TYPOGRAPHY_CLASS7, className].filter(Boolean).join(" ");
|
|
737
806
|
return /* @__PURE__ */ jsxs14(
|
|
738
807
|
"div",
|
|
739
808
|
{
|
|
@@ -744,17 +813,17 @@ function Toast({
|
|
|
744
813
|
onMouseLeave: () => setPaused(false),
|
|
745
814
|
role: variant === "danger" ? "alert" : "status",
|
|
746
815
|
children: [
|
|
747
|
-
icon ? /* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
749
|
-
actionLabel ? /* @__PURE__ */
|
|
750
|
-
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(
|
|
751
820
|
"button",
|
|
752
821
|
{
|
|
753
822
|
"aria-label": closeLabel,
|
|
754
823
|
className: "palda-toast__close",
|
|
755
824
|
onClick: onClose,
|
|
756
825
|
type: "button",
|
|
757
|
-
children: /* @__PURE__ */
|
|
826
|
+
children: /* @__PURE__ */ jsx18(CloseIcon, {})
|
|
758
827
|
}
|
|
759
828
|
) : null
|
|
760
829
|
]
|
|
@@ -763,12 +832,12 @@ function Toast({
|
|
|
763
832
|
}
|
|
764
833
|
|
|
765
834
|
// ../../components/src/Toast/Toaster.tsx
|
|
766
|
-
import { jsx as
|
|
835
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
767
836
|
import { createElement } from "react";
|
|
768
837
|
function Toaster({ className = "", placement = "bottom-center" }) {
|
|
769
838
|
const toasts = useSyncExternalStore(subscribeToToasts, getToasts, getToasts);
|
|
770
839
|
const classes = ["palda-toaster", `palda-toaster--${placement}`, className].filter(Boolean).join(" ");
|
|
771
|
-
return /* @__PURE__ */
|
|
840
|
+
return /* @__PURE__ */ jsx19("div", { className: classes, children: toasts.map(({ id, message, onAction, ...options }) => /* @__PURE__ */ createElement(
|
|
772
841
|
Toast,
|
|
773
842
|
{
|
|
774
843
|
...options,
|
|
@@ -794,7 +863,7 @@ import {
|
|
|
794
863
|
useRef as useRef4,
|
|
795
864
|
useState as useState4
|
|
796
865
|
} from "react";
|
|
797
|
-
import { jsx as
|
|
866
|
+
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
798
867
|
var OPPOSITE = {
|
|
799
868
|
bottom: "top",
|
|
800
869
|
left: "right",
|
|
@@ -944,7 +1013,7 @@ function Tooltip({ children, className = "", label, placement = "top" }) {
|
|
|
944
1013
|
),
|
|
945
1014
|
children: [
|
|
946
1015
|
label,
|
|
947
|
-
/* @__PURE__ */
|
|
1016
|
+
/* @__PURE__ */ jsx20("span", { "aria-hidden": "true", className: "palda-tooltip__arrow" })
|
|
948
1017
|
]
|
|
949
1018
|
}
|
|
950
1019
|
) : null
|
|
@@ -967,9 +1036,11 @@ export {
|
|
|
967
1036
|
NavigationButton,
|
|
968
1037
|
ProgressBar,
|
|
969
1038
|
Radio,
|
|
1039
|
+
Skeleton,
|
|
970
1040
|
Switch,
|
|
971
1041
|
Tab,
|
|
972
1042
|
TextField,
|
|
1043
|
+
Thumbnail,
|
|
973
1044
|
toast,
|
|
974
1045
|
Toaster,
|
|
975
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
|
+
- 이미지가 없거나 깨졌을 때 회색 면만 남기면 되는가, 대체 아이콘이 필요한가
|
|
@@ -58,6 +58,10 @@ list-row:
|
|
|
58
58
|
- 섹션 안에서 tone="filled"로 값을 강조해 담는 요약 행 (옅은 회색 필, 보더 없음).
|
|
59
59
|
페이지 바디 최상위 블록은 Card다 — 안쪽 여백은 둘 다 16으로 같고 라운드가 갈린다
|
|
60
60
|
- onClick을 주면 누를 수 있는 행(선택 상태 포함)
|
|
61
|
+
- 그 목록이 화면의 본문이면 emphasis="strong"으로 행을 한 칸 올린다
|
|
62
|
+
emphasis:
|
|
63
|
+
default: 제목 body2(14) + 설명 label(12). 곁·요약·참조 나열이다. 기본값이다
|
|
64
|
+
strong: 제목 body1(16) + 설명 body2(14). 선언서의 본문 문장이 가리키는 목록 하나에만 쓴다
|
|
61
65
|
avoid_for:
|
|
62
66
|
- 진행률·CTA·취소를 함께 든 작업 행. leading이 16px 아이콘 고정이라 썸네일이 들어가지 않고,
|
|
63
67
|
세로 여백과 가운데 정렬이 제목·설명 두 줄 기준이라 여러 단 내용이 붙어 보인다.
|
|
@@ -68,7 +72,11 @@ list-row:
|
|
|
68
72
|
선택은 보더가 아니라 색이다. onClick이 있으면 chevron이 기본으로 붙고, 오른쪽에 컨트롤이 오면
|
|
69
73
|
chevron={false}로 끈다. 목록 위에서 갈래를 가르는 것은 Tab이다(0023).
|
|
70
74
|
목록 전체의 empty/error는 EmptyState가 맡는다(docs/DESIGN_GRAMMAR.md의 §상태는 누가 갖나).
|
|
71
|
-
뼈대 높이는 텍스트 스타일의 font-size를
|
|
75
|
+
뼈대 높이는 텍스트 스타일의 font-size를 따르고 emphasis를 따라 같이 올라간다.
|
|
76
|
+
emphasis는 크기를 고르는 자리가 아니라 이 목록이 화면의 본문인지를 밝히는 자리다 — 판정은
|
|
77
|
+
"선언서의 본문 문장이 가리키는 목록인가" 하나이고 한 화면에 strong은 하나다(0034).
|
|
78
|
+
제목만 올리지 않는다. 16 → 12로 두 칸을 건너뛰면 설명이 각주가 된다.
|
|
79
|
+
tone="filled"와 겹쳐 쓰지 않는다 — 곁이 본문 크기를 갖는 일이다(0032).
|
|
72
80
|
|
|
73
81
|
form-section:
|
|
74
82
|
status: draft
|
|
@@ -79,11 +87,18 @@ form-section:
|
|
|
79
87
|
- 설정·정보 섹션을 제목 아래로 묶기 — 0011 이후 섹션의 기본 단위다
|
|
80
88
|
- 입력 필드를 제목 아래로 묶는 폼의 한 덩어리
|
|
81
89
|
- 섹션 단위 오류 표시
|
|
90
|
+
tone:
|
|
91
|
+
plain: 면이 없다. 0011의 기본값이고, 제목과 여백만으로 갈린다
|
|
92
|
+
filled: >-
|
|
93
|
+
내용만 옅은 회색 필(surface/raised)에 담고 제목은 캔버스에 남긴다. 섹션이 여럿 쌓여 제목과
|
|
94
|
+
여백만으로 묶음이 안 갈릴 때만 쓴다. 면 위에서 TextField는 흰 면으로 한 단계 밝아진다
|
|
82
95
|
note: >-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
섹션을 Card로 감싸지 않고 이것을 쓴다. 섹션 사이 여백은 AppShell이 --space-8(32)로 준다.
|
|
97
|
+
라벨↔컨트롤 간격은 TextField가 갖고 있어 여기서 다시 주지 않는다. 섹션 오류는 role="alert"이고,
|
|
98
|
+
필드 하나의 오류는 TextField의 error를 쓴다. 폼이 아닌 섹션에도 쓰이므로 이름이 좁다 —
|
|
99
|
+
개명 여부 확정 대기.
|
|
100
|
+
tone="filled"는 0011(박스 없는 것이 기본값)에서 벗어나는 자리다. 기본값은 여전히 plain이고,
|
|
101
|
+
쓰는 조건과 근거는 ADR 0037에 있다.
|
|
87
102
|
|
|
88
103
|
live-preview:
|
|
89
104
|
status: draft
|
|
@@ -2,9 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
**모든 컴포넌트와 화면이 예외 없이 지켜야 하는 접근성 최소선이다.**
|
|
4
4
|
|
|
5
|
-
- 텍스트와 UI의 색상 대비를 확인한다.
|
|
5
|
+
- 텍스트와 UI의 색상 대비를 확인한다. 수치는 아래 §대비가 갖는다.
|
|
6
6
|
- 모든 조작 요소에 visible focus를 제공한다.
|
|
7
7
|
- icon-only button에는 접근 가능한 이름을 제공한다.
|
|
8
8
|
- 색상만으로 상태를 전달하지 않는다.
|
|
9
9
|
- Form error는 관련 입력과 연결한다.
|
|
10
10
|
- motion 감소 설정을 존중한다.
|
|
11
|
+
|
|
12
|
+
## 대비
|
|
13
|
+
|
|
14
|
+
**읽어야 하는 글자는 아래를 넘는다.** 넘지 못하면 시각 표현이 아니라 위반이다 —
|
|
15
|
+
[`DESIGN_PRINCIPLES.md`](DESIGN_PRINCIPLES.md) §7의 "글자가 배경에 묻힌다 → 읽히는 쪽이 이긴다"에
|
|
16
|
+
수치를 준 것이다.
|
|
17
|
+
|
|
18
|
+
| 무엇 | 하한 |
|
|
19
|
+
| ------------------------------------------------- | ------- |
|
|
20
|
+
| 본문 글자 (18.66px 미만, 또는 24px 미만 non-bold) | `4.5:1` |
|
|
21
|
+
| 큰 글자 (18.66px 이상 bold, 또는 24px 이상) | `3:1` |
|
|
22
|
+
| 조작 요소의 경계와 상태 표시 | `3:1` |
|
|
23
|
+
|
|
24
|
+
흰 배경(`surface/base`) 위 실측이다.
|
|
25
|
+
|
|
26
|
+
| 토큰 | 값 | 대비 |
|
|
27
|
+
| ------------------ | ---------------------- | ------------ |
|
|
28
|
+
| `text/primary` | `gray-900` `#323236` | `12.76:1` |
|
|
29
|
+
| `text/accent` | `purple-600` `#4345e5` | `6.52:1` |
|
|
30
|
+
| `text/primary-sub` | `gray-500` `#86868d` | **`3.62:1`** |
|
|
31
|
+
|
|
32
|
+
**`text/primary-sub`는 14px 본문에서 하한을 넘지 못한다.** 24px 이상이거나 18.66px 이상 bold인
|
|
33
|
+
자리에만 쓴다. 그보다 작은 글자에 쓰면 읽히지 않는다.
|
|
34
|
+
|
|
35
|
+
**아직 정리되지 않은 자리가 있다.** `ListRow`의 `description`이 `label`(12px)에
|
|
36
|
+
`text/primary-sub`라 같은 미달이다. 토큰 값을 올릴지 그 자리만 바꿀지는 결정 대기다.
|