banhaten 0.1.0 → 0.1.2
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/README.md +21 -9
- package/package.json +8 -2
- package/registry/components/accordion.tsx +37 -1
- package/registry/components/alert.tsx +14 -28
- package/registry/components/attribute.tsx +6 -10
- package/registry/components/autocomplete.tsx +637 -0
- package/registry/components/avatar.tsx +259 -24
- package/registry/components/badge.tsx +97 -35
- package/registry/components/button-group.tsx +1 -1
- package/registry/components/card.tsx +1 -1
- package/registry/components/checkbox.tsx +19 -16
- package/registry/components/date-picker-state.ts +253 -0
- package/registry/components/date-picker.tsx +115 -158
- package/registry/components/expanded/ActivityFeed.tsx +37 -23
- package/registry/components/expanded/Banner.tsx +54 -19
- package/registry/components/expanded/Breadcrumbs.tsx +10 -38
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +11 -16
- package/registry/components/expanded/CatalogTag.tsx +4 -11
- package/registry/components/expanded/CommandBar.tsx +33 -53
- package/registry/components/expanded/EmptyState.tsx +155 -0
- package/registry/components/expanded/FileUpload.tsx +362 -59
- package/registry/components/expanded/OnboardingStepListItem.tsx +6 -10
- package/registry/components/expanded/PageHeader.tsx +2 -11
- package/registry/components/expanded/Slideout.tsx +12 -23
- package/registry/components/expanded/Steps.tsx +6 -8
- package/registry/components/expanded/Table.tsx +18 -40
- package/registry/components/expanded/Timeline.tsx +5 -24
- package/registry/components/expanded/activityFeed.css +10 -54
- package/registry/components/expanded/banner.css +8 -75
- package/registry/components/expanded/breadcrumbs.css +1 -1
- package/registry/components/expanded/commandBar.css +23 -26
- package/registry/components/expanded/divider.css +1 -1
- package/registry/components/expanded/emptyState.css +111 -0
- package/registry/components/expanded/fileUpload.css +304 -75
- package/registry/components/expanded/pageHeader.css +1 -1
- package/registry/components/expanded/slideout.css +1 -0
- package/registry/components/expanded/steps.css +15 -51
- package/registry/components/expanded/table.css +6 -1
- package/registry/components/expanded/timeline.css +18 -15
- package/registry/components/input-otp.tsx +574 -0
- package/registry/components/input.tsx +140 -59
- package/registry/components/menu.tsx +470 -80
- package/registry/components/pagination.tsx +6 -18
- package/registry/components/popover.tsx +840 -0
- package/registry/components/radio-card.tsx +25 -31
- package/registry/components/select-content.tsx +28 -123
- package/registry/components/select.tsx +13 -9
- package/registry/components/skeleton.css +57 -0
- package/registry/components/skeleton.tsx +482 -0
- package/registry/components/social-button.tsx +24 -90
- package/registry/components/spinner.tsx +91 -7
- package/registry/components/textarea.tsx +21 -36
- package/registry/components/toggle.tsx +7 -23
- package/registry/components/tooltip.tsx +8 -4
- package/registry/examples/attribute-demo.tsx +2 -2
- package/registry/examples/autocomplete-demo.tsx +109 -0
- package/registry/examples/avatar-demo.tsx +102 -47
- package/registry/examples/badge-demo.tsx +16 -0
- package/registry/examples/checkbox-demo.tsx +3 -8
- package/registry/examples/date-picker-demo.tsx +75 -22
- package/registry/examples/expanded/banner-demo.tsx +31 -6
- package/registry/examples/expanded/breadcrumbs-demo.tsx +59 -0
- package/registry/examples/expanded/command-bar-demo.tsx +236 -0
- package/registry/examples/expanded/empty-state-demo.tsx +39 -0
- package/registry/examples/expanded/file-upload-demo.tsx +60 -0
- package/registry/examples/expanded/steps-demo.tsx +11 -0
- package/registry/examples/expanded/table-demo.tsx +142 -0
- package/registry/examples/input-demo.tsx +1 -1
- package/registry/examples/input-otp-demo.tsx +72 -0
- package/registry/examples/menu-demo.tsx +101 -88
- package/registry/examples/popover-demo.tsx +546 -0
- package/registry/examples/progress-demo.tsx +2 -2
- package/registry/examples/select-demo.tsx +32 -18
- package/registry/examples/skeleton-demo.tsx +56 -0
- package/registry/examples/social-button-demo.tsx +33 -33
- package/registry/examples/spinner-demo.tsx +59 -0
- package/registry/examples/tag-demo.tsx +1 -1
- package/registry/examples/textarea-demo.tsx +1 -1
- package/registry/index.json +266 -20
- package/registry/styles/globals.css +93 -3
- package/src/cli/index.js +997 -62
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
2
3
|
import "./activityFeed.css";
|
|
3
4
|
|
|
4
5
|
const defaultAvatarSrc = new URL("../assets/activity-feed-avatar.png", import.meta.url).href;
|
|
@@ -85,26 +86,48 @@ function Indicator({
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function ActionButton({ action }: { action: ActivityFeedAction }) {
|
|
88
|
-
const
|
|
89
|
+
const variant = action.variant === "primary" ? "default" : "outline";
|
|
89
90
|
|
|
90
91
|
if (action.href) {
|
|
91
92
|
return (
|
|
92
|
-
<
|
|
93
|
-
{action.
|
|
94
|
-
|
|
93
|
+
<Button
|
|
94
|
+
aria-label={action.ariaLabel}
|
|
95
|
+
asChild
|
|
96
|
+
className="ds-activity-feed-item__button"
|
|
97
|
+
size="sm"
|
|
98
|
+
variant={variant}
|
|
99
|
+
>
|
|
100
|
+
<a
|
|
101
|
+
aria-disabled={action.disabled || undefined}
|
|
102
|
+
href={action.disabled ? undefined : action.href}
|
|
103
|
+
onClick={(event) => {
|
|
104
|
+
if (action.disabled) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
action.onClick?.();
|
|
110
|
+
}}
|
|
111
|
+
tabIndex={action.disabled ? -1 : undefined}
|
|
112
|
+
>
|
|
113
|
+
{action.label}
|
|
114
|
+
</a>
|
|
115
|
+
</Button>
|
|
95
116
|
);
|
|
96
117
|
}
|
|
97
118
|
|
|
98
119
|
return (
|
|
99
|
-
<
|
|
120
|
+
<Button
|
|
100
121
|
aria-label={action.ariaLabel}
|
|
101
|
-
className=
|
|
122
|
+
className="ds-activity-feed-item__button"
|
|
102
123
|
disabled={action.disabled}
|
|
103
124
|
onClick={action.onClick}
|
|
125
|
+
size="sm"
|
|
104
126
|
type="button"
|
|
127
|
+
variant={variant}
|
|
105
128
|
>
|
|
106
129
|
{action.label}
|
|
107
|
-
</
|
|
130
|
+
</Button>
|
|
108
131
|
);
|
|
109
132
|
}
|
|
110
133
|
|
|
@@ -135,17 +158,11 @@ export function ActivityFeedItem({
|
|
|
135
158
|
const rtl = dir === "rtl";
|
|
136
159
|
const nodeId = rtl ? (type === "caption" ? "587:18720" : "587:18701") : type === "caption" ? "584:18591" : "584:10411";
|
|
137
160
|
|
|
138
|
-
const textParts =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
]
|
|
144
|
-
: [
|
|
145
|
-
label && <strong key="label">{label}</strong>,
|
|
146
|
-
hasSupportText && supportText && <span className="ds-activity-feed-item__support" key="support">{supportText}</span>,
|
|
147
|
-
hasLink && link && <span className="ds-activity-feed-item__link" key="link">{link}</span>,
|
|
148
|
-
];
|
|
161
|
+
const textParts = [
|
|
162
|
+
label && <strong key="label">{label}</strong>,
|
|
163
|
+
hasSupportText && supportText && <span className="ds-activity-feed-item__support" key="support">{supportText}</span>,
|
|
164
|
+
hasLink && link && <span className="ds-activity-feed-item__link" key="link">{link}</span>,
|
|
165
|
+
];
|
|
149
166
|
|
|
150
167
|
return (
|
|
151
168
|
<article
|
|
@@ -158,14 +175,13 @@ export function ActivityFeedItem({
|
|
|
158
175
|
data-node-id={nodeId}
|
|
159
176
|
dir={rtl ? "rtl" : "ltr"}
|
|
160
177
|
>
|
|
161
|
-
|
|
178
|
+
<Indicator avatarAlt={avatarAlt} avatarSrc={avatarSrc} rtl={rtl} showLine={showLine} type={type} />
|
|
162
179
|
|
|
163
180
|
<div className="ds-activity-feed-item__content">
|
|
164
181
|
<div className="ds-activity-feed-item__container">
|
|
165
182
|
<div className="ds-activity-feed-item__topline">
|
|
166
|
-
{rtl && hasStatus && <StatusDot />}
|
|
167
183
|
<div className="ds-activity-feed-item__text">{textParts}</div>
|
|
168
|
-
{
|
|
184
|
+
{hasStatus && <StatusDot />}
|
|
169
185
|
</div>
|
|
170
186
|
|
|
171
187
|
{type === "slot" ? (
|
|
@@ -191,8 +207,6 @@ export function ActivityFeedItem({
|
|
|
191
207
|
|
|
192
208
|
{showPaddingBottom && <div className="ds-activity-feed-item__bottom-space" aria-hidden="true" />}
|
|
193
209
|
</div>
|
|
194
|
-
|
|
195
|
-
{rtl && <Indicator avatarAlt={avatarAlt} avatarSrc={avatarSrc} rtl={rtl} showLine={showLine} type={type} />}
|
|
196
210
|
</article>
|
|
197
211
|
);
|
|
198
212
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { MegaphoneIcon as LucideMegaphoneIcon, XIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { Button, type ButtonProps } from "@/components/ui/button";
|
|
5
|
+
import { Input } from "@/components/ui/input";
|
|
6
|
+
|
|
2
7
|
import "./banner.css";
|
|
3
8
|
|
|
4
9
|
export type BannerType = "slim" | "single-action-inline" | "single-action" | "input";
|
|
@@ -30,19 +35,17 @@ export type BannerProps = {
|
|
|
30
35
|
};
|
|
31
36
|
|
|
32
37
|
function MegaphoneIcon() {
|
|
33
|
-
return
|
|
34
|
-
<svg aria-hidden="true" className="ds-banner__icon" viewBox="0 0 24 24">
|
|
35
|
-
<path d="M3 10.5v3a2.5 2.5 0 0 0 2.5 2.5h.63l1.15 3.44A1.5 1.5 0 0 0 8.7 20.5h1.1a1 1 0 0 0 .95-1.32L9.7 16H11l7.24 3.1A1.25 1.25 0 0 0 20 17.95V6.05a1.25 1.25 0 0 0-1.76-1.14L11 8H5.5A2.5 2.5 0 0 0 3 10.5Zm15 6.69-6-2.58V9.39l6-2.58v10.38ZM5 10.5A.5.5 0 0 1 5.5 10H10v4H5.5a.5.5 0 0 1-.5-.5v-3Z" />
|
|
36
|
-
</svg>
|
|
37
|
-
);
|
|
38
|
+
return <LucideMegaphoneIcon aria-hidden="true" className="ds-banner__icon" strokeWidth={2.1} />;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function CloseIcon() {
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
42
|
+
return <XIcon aria-hidden="true" className="ds-banner__close-icon" strokeWidth={2.25} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getBannerActionVariant(color: BannerColor): ButtonProps["variant"] {
|
|
46
|
+
if (color === "brand-light") return "default";
|
|
47
|
+
if (color === "grey") return "secondary";
|
|
48
|
+
return "white";
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
function cx(...classes: Array<string | false | undefined>) {
|
|
@@ -75,10 +78,18 @@ export function Banner({
|
|
|
75
78
|
const isInlineAction = type === "slim" || type === "single-action-inline";
|
|
76
79
|
const inlineActionLabel = type === "single-action-inline" ? actionLabel : linkLabel;
|
|
77
80
|
const hasInlineAction = isInlineAction && (showInlineAction ?? true) && Boolean(inlineActionLabel);
|
|
81
|
+
const actionVariant = getBannerActionVariant(color);
|
|
78
82
|
const closeButton = (
|
|
79
|
-
<
|
|
83
|
+
<Button
|
|
84
|
+
aria-label={closeLabel ?? "Dismiss banner"}
|
|
85
|
+
className="ds-banner__close"
|
|
86
|
+
onClick={onClose}
|
|
87
|
+
size="icon-xs"
|
|
88
|
+
type="button"
|
|
89
|
+
variant="ghost"
|
|
90
|
+
>
|
|
80
91
|
<CloseIcon />
|
|
81
|
-
</
|
|
92
|
+
</Button>
|
|
82
93
|
);
|
|
83
94
|
|
|
84
95
|
return (
|
|
@@ -110,18 +121,30 @@ export function Banner({
|
|
|
110
121
|
</div>
|
|
111
122
|
|
|
112
123
|
{hasInlineAction && (
|
|
113
|
-
<
|
|
124
|
+
<Button
|
|
125
|
+
className="ds-banner__link"
|
|
126
|
+
onClick={onAction}
|
|
127
|
+
size="sm"
|
|
128
|
+
type="button"
|
|
129
|
+
variant={type === "single-action-inline" ? actionVariant : "link"}
|
|
130
|
+
>
|
|
114
131
|
{inlineActionLabel}
|
|
115
|
-
</
|
|
132
|
+
</Button>
|
|
116
133
|
)}
|
|
117
134
|
</div>
|
|
118
135
|
|
|
119
136
|
{type === "single-action" && (
|
|
120
137
|
<div className="ds-banner__tail">
|
|
121
138
|
{actionLabel && (
|
|
122
|
-
<
|
|
139
|
+
<Button
|
|
140
|
+
className="ds-banner__action"
|
|
141
|
+
onClick={onAction}
|
|
142
|
+
size="sm"
|
|
143
|
+
type="button"
|
|
144
|
+
variant={actionVariant}
|
|
145
|
+
>
|
|
123
146
|
{actionLabel}
|
|
124
|
-
</
|
|
147
|
+
</Button>
|
|
125
148
|
)}
|
|
126
149
|
{closeButton}
|
|
127
150
|
</div>
|
|
@@ -129,11 +152,23 @@ export function Banner({
|
|
|
129
152
|
|
|
130
153
|
{type === "input" && (
|
|
131
154
|
<div className="ds-banner__tail ds-banner__tail--input">
|
|
132
|
-
<
|
|
155
|
+
<Input
|
|
156
|
+
aria-label={inputAriaLabel}
|
|
157
|
+
className="ds-banner__input"
|
|
158
|
+
hasInformationIcon={false}
|
|
159
|
+
placeholder={inputPlaceholder}
|
|
160
|
+
size="md"
|
|
161
|
+
/>
|
|
133
162
|
{actionLabel && (
|
|
134
|
-
<
|
|
163
|
+
<Button
|
|
164
|
+
className="ds-banner__action"
|
|
165
|
+
onClick={onAction}
|
|
166
|
+
size="sm"
|
|
167
|
+
type="button"
|
|
168
|
+
variant={actionVariant}
|
|
169
|
+
>
|
|
135
170
|
{actionLabel}
|
|
136
|
-
</
|
|
171
|
+
</Button>
|
|
137
172
|
)}
|
|
138
173
|
{closeButton}
|
|
139
174
|
</div>
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ChevronRightIcon,
|
|
4
|
+
EllipsisIcon,
|
|
5
|
+
FolderIcon,
|
|
6
|
+
HomeIcon,
|
|
7
|
+
} from "lucide-react";
|
|
2
8
|
import "./breadcrumbs.css";
|
|
3
9
|
|
|
4
10
|
export type BreadcrumbSeparator = "slash" | "chevron";
|
|
@@ -106,51 +112,17 @@ function Separator({ separator }: { separator: BreadcrumbSeparator }) {
|
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
export function BreadcrumbHomeIcon() {
|
|
109
|
-
return
|
|
110
|
-
<svg aria-hidden="true" viewBox="0 0 20 20">
|
|
111
|
-
<path
|
|
112
|
-
d="M3.5 8.7 10 3.4l6.5 5.3v7.1a.9.9 0 0 1-.9.9h-3.2v-4.9H7.6v4.9H4.4a.9.9 0 0 1-.9-.9V8.7Z"
|
|
113
|
-
fill="none"
|
|
114
|
-
stroke="currentColor"
|
|
115
|
-
strokeLinejoin="round"
|
|
116
|
-
strokeWidth="1.7"
|
|
117
|
-
/>
|
|
118
|
-
</svg>
|
|
119
|
-
);
|
|
115
|
+
return <HomeIcon aria-hidden="true" strokeWidth={1.9} />;
|
|
120
116
|
}
|
|
121
117
|
|
|
122
118
|
export function BreadcrumbFolderIcon() {
|
|
123
|
-
return
|
|
124
|
-
<svg aria-hidden="true" viewBox="0 0 20 20">
|
|
125
|
-
<path
|
|
126
|
-
d="M2.8 5.7c0-.9.7-1.6 1.6-1.6h3.8l1.5 1.8h5.9c.9 0 1.6.7 1.6 1.6v6.8c0 .9-.7 1.6-1.6 1.6H4.4c-.9 0-1.6-.7-1.6-1.6V5.7Z"
|
|
127
|
-
fill="none"
|
|
128
|
-
stroke="currentColor"
|
|
129
|
-
strokeLinejoin="round"
|
|
130
|
-
strokeWidth="1.7"
|
|
131
|
-
/>
|
|
132
|
-
</svg>
|
|
133
|
-
);
|
|
119
|
+
return <FolderIcon aria-hidden="true" strokeWidth={1.9} />;
|
|
134
120
|
}
|
|
135
121
|
|
|
136
122
|
function ChevronIcon() {
|
|
137
|
-
return
|
|
138
|
-
<svg aria-hidden="true" viewBox="0 0 16 16">
|
|
139
|
-
<path d="m6 3.5 4 4.5-4 4.5" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.6" />
|
|
140
|
-
</svg>
|
|
141
|
-
);
|
|
123
|
+
return <ChevronRightIcon aria-hidden="true" strokeWidth={2} />;
|
|
142
124
|
}
|
|
143
125
|
|
|
144
126
|
function OverflowIcon() {
|
|
145
|
-
return
|
|
146
|
-
<svg aria-hidden="true" viewBox="0 0 20 20">
|
|
147
|
-
<path
|
|
148
|
-
d="M5.2 10h.1M10 10h.1M14.8 10h.1"
|
|
149
|
-
fill="none"
|
|
150
|
-
stroke="currentColor"
|
|
151
|
-
strokeLinecap="round"
|
|
152
|
-
strokeWidth="2.4"
|
|
153
|
-
/>
|
|
154
|
-
</svg>
|
|
155
|
-
);
|
|
127
|
+
return <EllipsisIcon aria-hidden="true" strokeWidth={2.25} />;
|
|
156
128
|
}
|
|
@@ -36,17 +36,12 @@ const breadcrumbAriaLabel = "Catalog breadcrumbs";
|
|
|
36
36
|
const rtlBreadcrumbAriaLabel = "\u0627\u0644\u0645\u0633\u0627\u0631";
|
|
37
37
|
|
|
38
38
|
const uploadFiles: FileUploadFile[] = [
|
|
39
|
-
{ id: "
|
|
40
|
-
{ id: "
|
|
41
|
-
{ id: "
|
|
39
|
+
{ id: "uploaded", name: "file-name.pdf", sizeLabel: "32mb", versionLabel: "v1.2.2", status: "uploaded" },
|
|
40
|
+
{ id: "uploading", name: "file-name.pdf", sizeLabel: "32mb", versionLabel: "v1.2.2", status: "uploading" },
|
|
41
|
+
{ id: "progress", name: "file-name.pdf", sizeLabel: "32 KB", progress: 40, status: "uploading-progress" },
|
|
42
|
+
{ id: "error", name: "file-name.pdf", sizeLabel: "32mb", versionLabel: "v1.2.2", status: "error", errorLabel: "Upload failed" },
|
|
42
43
|
];
|
|
43
44
|
|
|
44
|
-
const fileUploadCopy = {
|
|
45
|
-
browseLabel: "Browse",
|
|
46
|
-
helperText: "SVG, PNG, PDF, or CSV up to 50 MB",
|
|
47
|
-
prompt: "Drag and drop files here, or",
|
|
48
|
-
};
|
|
49
|
-
|
|
50
45
|
const projectRows: ProjectRow[] = [
|
|
51
46
|
{ id: "1", name: "Component audit", leader: "Maya Chen", progress: 84, status: "On track", deadline: "Jun 12" },
|
|
52
47
|
{ id: "2", name: "Icon decision", leader: "Ahmed Galal", progress: 64, status: "Review", deadline: "Jun 18" },
|
|
@@ -121,7 +116,7 @@ export function CatalogComponentsShowcase() {
|
|
|
121
116
|
aria-label={rtlBreadcrumbAriaLabel}
|
|
122
117
|
dir="rtl"
|
|
123
118
|
items={rtlBreadcrumbItems}
|
|
124
|
-
overflowLabel="\u0627\u0644\u0645\u0632\u064a\u062f"
|
|
119
|
+
overflowLabel={"\u0627\u0644\u0645\u0632\u064a\u062f"}
|
|
125
120
|
separator="chevron"
|
|
126
121
|
style="raised"
|
|
127
122
|
/>
|
|
@@ -134,7 +129,7 @@ export function CatalogComponentsShowcase() {
|
|
|
134
129
|
<CatalogDivider borderStyle="dotted" />
|
|
135
130
|
<CatalogContentDivider label="Text" />
|
|
136
131
|
<CatalogContentDivider actionLabel="Button" onAction={() => undefined} />
|
|
137
|
-
<CatalogContentDivider dir="rtl" label="\u0646\u0635" borderStyle="dotted" />
|
|
132
|
+
<CatalogContentDivider dir="rtl" label={"\u0646\u0635"} borderStyle="dotted" />
|
|
138
133
|
</div>
|
|
139
134
|
</ShowcaseCard>
|
|
140
135
|
|
|
@@ -162,21 +157,21 @@ export function CatalogComponentsShowcase() {
|
|
|
162
157
|
large
|
|
163
158
|
</CatalogTag>
|
|
164
159
|
<CatalogTag
|
|
165
|
-
avatar="\u0645"
|
|
166
|
-
closeLabel="\u0625\u0632\u0627\u0644\u0629 \u0648\u0633\u0645"
|
|
160
|
+
avatar={"\u0645"}
|
|
161
|
+
closeLabel={"\u0625\u0632\u0627\u0644\u0629 \u0648\u0633\u0645"}
|
|
167
162
|
dir="rtl"
|
|
168
163
|
type="avatar"
|
|
169
164
|
showCloseButton
|
|
170
165
|
>
|
|
171
|
-
\u0645\u0644\u0635\u0642
|
|
166
|
+
{"\u0645\u0644\u0635\u0642"}
|
|
172
167
|
</CatalogTag>
|
|
173
168
|
</div>
|
|
174
169
|
</ShowcaseCard>
|
|
175
170
|
|
|
176
171
|
<ShowcaseCard title="File Upload" description="Large and small queued-file upload states.">
|
|
177
172
|
<div className="catalog-showcase__upload-grid">
|
|
178
|
-
<FileUpload
|
|
179
|
-
<FileUpload
|
|
173
|
+
<FileUpload files={uploadFiles} multiple />
|
|
174
|
+
<FileUpload files={uploadFiles.slice(0, 4)} size="sm" />
|
|
180
175
|
</div>
|
|
181
176
|
</ShowcaseCard>
|
|
182
177
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { PlusIcon as LucidePlusIcon, XIcon } from "lucide-react";
|
|
2
3
|
import "./tag.css";
|
|
3
4
|
|
|
4
5
|
export type TagType = "simple" | "dot" | "flag" | "avatar" | "icon";
|
|
@@ -61,7 +62,7 @@ export function CatalogTag({
|
|
|
61
62
|
<span aria-hidden="true" className="ds-tag__leading">
|
|
62
63
|
{type === "dot" && <span className="ds-tag__dot" />}
|
|
63
64
|
{type === "flag" && (flag ?? <span className="ds-tag__flag" />)}
|
|
64
|
-
{type === "avatar" && avatar}
|
|
65
|
+
{type === "avatar" && <span className="ds-tag__avatar">{avatar}</span>}
|
|
65
66
|
{type === "icon" && (icon ?? <PlusIcon />)}
|
|
66
67
|
</span>
|
|
67
68
|
)}
|
|
@@ -76,17 +77,9 @@ export function CatalogTag({
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function PlusIcon() {
|
|
79
|
-
return
|
|
80
|
-
<svg aria-hidden="true" viewBox="0 0 16 16">
|
|
81
|
-
<path d="M8 3.5v9M3.5 8h9" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="1.7" />
|
|
82
|
-
</svg>
|
|
83
|
-
);
|
|
80
|
+
return <LucidePlusIcon aria-hidden="true" strokeWidth={2.25} />;
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
function CloseIcon() {
|
|
87
|
-
return
|
|
88
|
-
<svg aria-hidden="true" viewBox="0 0 16 16">
|
|
89
|
-
<path d="m4.5 4.5 7 7M11.5 4.5l-7 7" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="1.6" />
|
|
90
|
-
</svg>
|
|
91
|
-
);
|
|
84
|
+
return <XIcon aria-hidden="true" strokeWidth={2.25} />;
|
|
92
85
|
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import "./commandBar.css";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
ArrowUpDownIcon as LucideArrowUpDownIcon,
|
|
5
|
+
BookOpenIcon,
|
|
6
|
+
CommandIcon,
|
|
7
|
+
CornerDownLeftIcon,
|
|
8
|
+
SearchIcon as LucideSearchIcon,
|
|
9
|
+
XIcon,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
|
|
12
|
+
import { Button } from "@/components/ui/button";
|
|
13
|
+
|
|
3
14
|
export type CommandBarType = "default" | "recent" | "results" | "no-result";
|
|
4
15
|
export type CommandBarBreakpoint = "desktop" | "mobile";
|
|
5
16
|
|
|
@@ -34,7 +45,7 @@ type CommandRow = {
|
|
|
34
45
|
readonly visual: "avatar" | "icon";
|
|
35
46
|
};
|
|
36
47
|
|
|
37
|
-
const
|
|
48
|
+
const shortcutLabel = "Command K";
|
|
38
49
|
|
|
39
50
|
const ltrCopy: CommandBarCopy = {
|
|
40
51
|
clear: "Clear search",
|
|
@@ -248,9 +259,15 @@ function FilterSection({ copy }: { copy: CommandBarCopy }) {
|
|
|
248
259
|
<SectionHeading>{copy.searchFor}</SectionHeading>
|
|
249
260
|
<div className="ds-command-bar__tags" aria-label={copy.searchFor}>
|
|
250
261
|
{copy.tags.map((tag) => (
|
|
251
|
-
<
|
|
262
|
+
<Button
|
|
263
|
+
className="ds-command-bar__tag"
|
|
264
|
+
key={tag}
|
|
265
|
+
size="sm"
|
|
266
|
+
type="button"
|
|
267
|
+
variant="secondary"
|
|
268
|
+
>
|
|
252
269
|
{tag}
|
|
253
|
-
</
|
|
270
|
+
</Button>
|
|
254
271
|
))}
|
|
255
272
|
</div>
|
|
256
273
|
</div>
|
|
@@ -326,7 +343,14 @@ function CommandFooter({ copy }: { copy: CommandBarCopy }) {
|
|
|
326
343
|
}
|
|
327
344
|
|
|
328
345
|
function Shortcut() {
|
|
329
|
-
return
|
|
346
|
+
return (
|
|
347
|
+
<kbd aria-label={shortcutLabel} className="ds-command-bar__shortcut">
|
|
348
|
+
<CommandIcon aria-hidden="true" className="ds-command-bar__shortcut-icon" />
|
|
349
|
+
<span aria-hidden="true" className="ds-command-bar__shortcut-key">
|
|
350
|
+
K
|
|
351
|
+
</span>
|
|
352
|
+
</kbd>
|
|
353
|
+
);
|
|
330
354
|
}
|
|
331
355
|
|
|
332
356
|
function IconWrap() {
|
|
@@ -342,65 +366,21 @@ function Avatar() {
|
|
|
342
366
|
}
|
|
343
367
|
|
|
344
368
|
function SearchIcon({ size = 24 }: { size?: number }) {
|
|
345
|
-
return
|
|
346
|
-
<svg aria-hidden="true" fill="none" height={size} viewBox="0 0 24 24" width={size}>
|
|
347
|
-
<path
|
|
348
|
-
d="M10.75 18.5a7.75 7.75 0 1 1 5.48-2.27l3.02 3.02"
|
|
349
|
-
stroke="currentColor"
|
|
350
|
-
strokeLinecap="round"
|
|
351
|
-
strokeLinejoin="round"
|
|
352
|
-
strokeWidth="2"
|
|
353
|
-
/>
|
|
354
|
-
</svg>
|
|
355
|
-
);
|
|
369
|
+
return <LucideSearchIcon aria-hidden="true" height={size} strokeWidth={2} width={size} />;
|
|
356
370
|
}
|
|
357
371
|
|
|
358
372
|
function CloseIcon() {
|
|
359
|
-
return
|
|
360
|
-
<svg aria-hidden="true" fill="none" height="20" viewBox="0 0 20 20" width="20">
|
|
361
|
-
<path d="m5 5 10 10M15 5 5 15" stroke="currentColor" strokeLinecap="round" strokeWidth="1.8" />
|
|
362
|
-
</svg>
|
|
363
|
-
);
|
|
373
|
+
return <XIcon aria-hidden="true" height="20" strokeWidth={2.2} width="20" />;
|
|
364
374
|
}
|
|
365
375
|
|
|
366
376
|
function BookIcon() {
|
|
367
|
-
return
|
|
368
|
-
<svg aria-hidden="true" fill="none" height="20" viewBox="0 0 20 20" width="20">
|
|
369
|
-
<path
|
|
370
|
-
d="M5.5 3.5h8.2c.9 0 1.6.7 1.6 1.6v10.4H6.2a1.5 1.5 0 0 0 0 3h9.1M5.5 3.5a2 2 0 0 0-2 2v11a2 2 0 0 1 2-2m0-11v11m2.4-7.8h4.8"
|
|
371
|
-
stroke="currentColor"
|
|
372
|
-
strokeLinecap="round"
|
|
373
|
-
strokeLinejoin="round"
|
|
374
|
-
strokeWidth="1.6"
|
|
375
|
-
/>
|
|
376
|
-
</svg>
|
|
377
|
-
);
|
|
377
|
+
return <BookOpenIcon aria-hidden="true" height="20" strokeWidth={1.8} width="20" />;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
function ArrowUpDownIcon() {
|
|
381
|
-
return
|
|
382
|
-
<svg aria-hidden="true" fill="none" height="16" viewBox="0 0 16 16" width="16">
|
|
383
|
-
<path
|
|
384
|
-
d="M5 2v12m0 0 2.5-2.5M5 14l-2.5-2.5M11 14V2m0 0 2.5 2.5M11 2 8.5 4.5"
|
|
385
|
-
stroke="currentColor"
|
|
386
|
-
strokeLinecap="round"
|
|
387
|
-
strokeLinejoin="round"
|
|
388
|
-
strokeWidth="1.45"
|
|
389
|
-
/>
|
|
390
|
-
</svg>
|
|
391
|
-
);
|
|
381
|
+
return <LucideArrowUpDownIcon aria-hidden="true" height="16" strokeWidth={1.9} width="16" />;
|
|
392
382
|
}
|
|
393
383
|
|
|
394
384
|
function EnterIcon() {
|
|
395
|
-
return
|
|
396
|
-
<svg aria-hidden="true" fill="none" height="16" viewBox="0 0 16 16" width="16">
|
|
397
|
-
<path
|
|
398
|
-
d="M12.5 3.5v3.25a3 3 0 0 1-3 3H3.75m0 0L6.2 7.3M3.75 9.75 6.2 12.2"
|
|
399
|
-
stroke="currentColor"
|
|
400
|
-
strokeLinecap="round"
|
|
401
|
-
strokeLinejoin="round"
|
|
402
|
-
strokeWidth="1.45"
|
|
403
|
-
/>
|
|
404
|
-
</svg>
|
|
405
|
-
);
|
|
385
|
+
return <CornerDownLeftIcon aria-hidden="true" height="16" strokeWidth={1.9} width="16" />;
|
|
406
386
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { SearchIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { Button, type ButtonProps } from "@/components/ui/button";
|
|
5
|
+
|
|
6
|
+
import "./emptyState.css";
|
|
7
|
+
|
|
8
|
+
export type EmptyStateAlign = "center" | "start";
|
|
9
|
+
export type EmptyStateSize = "default" | "compact";
|
|
10
|
+
|
|
11
|
+
export type EmptyStateAction = {
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
href?: string;
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
label: ReactNode;
|
|
17
|
+
onAction?: () => void;
|
|
18
|
+
rel?: string;
|
|
19
|
+
size?: ButtonProps["size"];
|
|
20
|
+
target?: string;
|
|
21
|
+
type?: "button" | "submit" | "reset";
|
|
22
|
+
variant?: ButtonProps["variant"];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type EmptyStateProps = Omit<ComponentPropsWithoutRef<"section">, "title"> & {
|
|
26
|
+
actions?: EmptyStateAction[];
|
|
27
|
+
align?: EmptyStateAlign;
|
|
28
|
+
description?: ReactNode;
|
|
29
|
+
dir?: "ltr" | "rtl" | "auto";
|
|
30
|
+
icon?: ReactNode;
|
|
31
|
+
iconLabel?: string;
|
|
32
|
+
size?: EmptyStateSize;
|
|
33
|
+
title: ReactNode;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function EmptyState({
|
|
37
|
+
actions = [],
|
|
38
|
+
align = "center",
|
|
39
|
+
className,
|
|
40
|
+
description,
|
|
41
|
+
dir = "ltr",
|
|
42
|
+
icon,
|
|
43
|
+
iconLabel,
|
|
44
|
+
size = "default",
|
|
45
|
+
title,
|
|
46
|
+
...props
|
|
47
|
+
}: EmptyStateProps) {
|
|
48
|
+
const hasActions = actions.length > 0;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<section
|
|
52
|
+
className={cx(
|
|
53
|
+
"ds-empty-state",
|
|
54
|
+
`ds-empty-state--${align}`,
|
|
55
|
+
`ds-empty-state--${size}`,
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
dir={dir}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<span
|
|
62
|
+
aria-hidden={iconLabel ? undefined : true}
|
|
63
|
+
aria-label={iconLabel}
|
|
64
|
+
className="ds-empty-state__icon-wrap"
|
|
65
|
+
role={iconLabel ? "img" : undefined}
|
|
66
|
+
>
|
|
67
|
+
<span className="ds-empty-state__icon-media">
|
|
68
|
+
{icon ?? <SearchIcon />}
|
|
69
|
+
</span>
|
|
70
|
+
</span>
|
|
71
|
+
|
|
72
|
+
<div className="ds-empty-state__copy">
|
|
73
|
+
<h3 className="ds-empty-state__title" dir="auto">
|
|
74
|
+
{title}
|
|
75
|
+
</h3>
|
|
76
|
+
{description && (
|
|
77
|
+
<p className="ds-empty-state__description" dir="auto">
|
|
78
|
+
{description}
|
|
79
|
+
</p>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
{hasActions && (
|
|
84
|
+
<div className="ds-empty-state__actions">
|
|
85
|
+
{actions.map((action, index) => (
|
|
86
|
+
<EmptyStateActionButton
|
|
87
|
+
action={action}
|
|
88
|
+
actionCount={actions.length}
|
|
89
|
+
index={index}
|
|
90
|
+
key={index}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
</section>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function EmptyStateActionButton({
|
|
100
|
+
action,
|
|
101
|
+
actionCount,
|
|
102
|
+
index,
|
|
103
|
+
}: {
|
|
104
|
+
action: EmptyStateAction;
|
|
105
|
+
actionCount: number;
|
|
106
|
+
index: number;
|
|
107
|
+
}) {
|
|
108
|
+
const variant =
|
|
109
|
+
action.variant ?? (actionCount > 1 && index === 0 ? "secondary" : "default");
|
|
110
|
+
const content = (
|
|
111
|
+
<>
|
|
112
|
+
{action.icon}
|
|
113
|
+
{action.label}
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (action.href) {
|
|
118
|
+
const isDisabled = Boolean(action.disabled);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Button
|
|
122
|
+
aria-label={action.ariaLabel}
|
|
123
|
+
asChild
|
|
124
|
+
size={action.size ?? "default"}
|
|
125
|
+
variant={variant}
|
|
126
|
+
>
|
|
127
|
+
<a
|
|
128
|
+
aria-disabled={isDisabled || undefined}
|
|
129
|
+
href={isDisabled ? undefined : action.href}
|
|
130
|
+
rel={action.rel ?? (action.target === "_blank" ? "noreferrer" : undefined)}
|
|
131
|
+
target={action.target}
|
|
132
|
+
>
|
|
133
|
+
{content}
|
|
134
|
+
</a>
|
|
135
|
+
</Button>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<Button
|
|
141
|
+
aria-label={action.ariaLabel}
|
|
142
|
+
disabled={action.disabled}
|
|
143
|
+
onClick={action.onAction}
|
|
144
|
+
size={action.size ?? "default"}
|
|
145
|
+
type={action.type ?? "button"}
|
|
146
|
+
variant={variant}
|
|
147
|
+
>
|
|
148
|
+
{content}
|
|
149
|
+
</Button>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function cx(...classes: Array<string | false | null | undefined>) {
|
|
154
|
+
return classes.filter(Boolean).join(" ");
|
|
155
|
+
}
|