banhaten 0.1.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/LICENSE +21 -0
- package/README.md +361 -0
- package/banhaten.config.example.json +13 -0
- package/package.json +59 -0
- package/registry/assets/activity-feed-avatar.png +0 -0
- package/registry/assets/avatars/avatar-01.jpg +0 -0
- package/registry/assets/avatars/avatar-02.jpg +0 -0
- package/registry/assets/avatars/avatar-03.jpg +0 -0
- package/registry/assets/avatars/avatar-04.jpg +0 -0
- package/registry/assets/avatars/avatar-05.jpg +0 -0
- package/registry/assets/avatars/avatar-06.jpg +0 -0
- package/registry/assets/avatars/avatar-07.jpg +0 -0
- package/registry/assets/avatars/avatar-08.jpg +0 -0
- package/registry/assets/avatars/avatar-09.jpg +0 -0
- package/registry/assets/avatars/avatar-10.jpg +0 -0
- package/registry/assets/avatars/avatar-11.jpg +0 -0
- package/registry/assets/avatars/avatar-12.jpg +0 -0
- package/registry/assets/avatars/avatar-13.jpg +0 -0
- package/registry/assets/avatars/avatar-14.jpg +0 -0
- package/registry/assets/avatars/avatar-15.jpg +0 -0
- package/registry/assets/avatars/avatar-16.jpg +0 -0
- package/registry/assets/avatars/avatar-17.jpg +0 -0
- package/registry/assets/avatars/avatar-18.jpg +0 -0
- package/registry/assets/avatars/avatar-19.jpg +0 -0
- package/registry/assets/avatars/avatar-20.jpg +0 -0
- package/registry/assets/avatars/avatar-21.jpg +0 -0
- package/registry/assets/avatars/avatar-22.jpg +0 -0
- package/registry/assets/avatars/avatar-23.jpg +0 -0
- package/registry/assets/avatars/avatar-24.jpg +0 -0
- package/registry/assets/avatars/avatar-25.jpg +0 -0
- package/registry/assets/avatars/avatar-26.jpg +0 -0
- package/registry/assets/avatars/avatar-27.jpg +0 -0
- package/registry/assets/avatars/avatar-28.jpg +0 -0
- package/registry/assets/avatars/avatar-29.jpg +0 -0
- package/registry/assets/avatars/avatar-30.jpg +0 -0
- package/registry/assets/avatars/avatar-31.jpg +0 -0
- package/registry/assets/avatars/avatar-32.jpg +0 -0
- package/registry/assets/avatars/avatar-33.jpg +0 -0
- package/registry/assets/avatars/avatar-34.jpg +0 -0
- package/registry/assets/avatars/avatar-35.jpg +0 -0
- package/registry/assets/image-assets.json +744 -0
- package/registry/assets/images/art-01.jpg +0 -0
- package/registry/assets/images/art-02.jpg +0 -0
- package/registry/assets/images/art-03.jpg +0 -0
- package/registry/assets/images/art-04.jpg +0 -0
- package/registry/assets/images/art-05.jpg +0 -0
- package/registry/assets/images/art-06.jpg +0 -0
- package/registry/assets/images/art-07.jpg +0 -0
- package/registry/assets/images/art-08.jpg +0 -0
- package/registry/assets/images/art-09.jpg +0 -0
- package/registry/assets/images/art-10.jpg +0 -0
- package/registry/assets/images/art-11.jpg +0 -0
- package/registry/assets/images/art-12.jpg +0 -0
- package/registry/assets/images/art-13.jpg +0 -0
- package/registry/assets/images/art-14.jpg +0 -0
- package/registry/assets/images/art-15.jpg +0 -0
- package/registry/assets/images/art-16.jpg +0 -0
- package/registry/assets/images/art-17.jpg +0 -0
- package/registry/assets/images/art-18.jpg +0 -0
- package/registry/assets/images/art-19.jpg +0 -0
- package/registry/assets/images/art-20.jpg +0 -0
- package/registry/assets/images/art-21.jpg +0 -0
- package/registry/assets/images/art-22.jpg +0 -0
- package/registry/assets/images/art-23.jpg +0 -0
- package/registry/assets/images/art-24.jpg +0 -0
- package/registry/assets/images/art-25.jpg +0 -0
- package/registry/assets/images/art-26.jpg +0 -0
- package/registry/assets/images/art-27.jpg +0 -0
- package/registry/assets/images/nature-01.jpg +0 -0
- package/registry/assets/images/nature-02.jpg +0 -0
- package/registry/assets/images/nature-03.jpg +0 -0
- package/registry/assets/images/nature-04.jpg +0 -0
- package/registry/assets/images/nature-05.jpg +0 -0
- package/registry/assets/images/nature-06.jpg +0 -0
- package/registry/assets/images/nature-07.jpg +0 -0
- package/registry/assets/images/nature-08.jpg +0 -0
- package/registry/assets/images/nature-09.jpg +0 -0
- package/registry/assets/images/nature-10.jpg +0 -0
- package/registry/assets/images/nature-11.jpg +0 -0
- package/registry/assets/images/nature-12.jpg +0 -0
- package/registry/assets/images/nature-13.jpg +0 -0
- package/registry/assets/images/nature-14.jpg +0 -0
- package/registry/assets/images/nature-15.jpg +0 -0
- package/registry/assets/images/nature-16.jpg +0 -0
- package/registry/assets/images/nature-17.jpg +0 -0
- package/registry/assets/images/nature-18.jpg +0 -0
- package/registry/assets/images/nature-19.jpg +0 -0
- package/registry/assets/images/nature-20.jpg +0 -0
- package/registry/components/accordion.tsx +119 -0
- package/registry/components/alert.tsx +282 -0
- package/registry/components/attribute.tsx +452 -0
- package/registry/components/avatar.tsx +142 -0
- package/registry/components/badge.tsx +567 -0
- package/registry/components/button-group.tsx +246 -0
- package/registry/components/button.tsx +102 -0
- package/registry/components/card.tsx +613 -0
- package/registry/components/checkbox.tsx +244 -0
- package/registry/components/date-picker.tsx +1143 -0
- package/registry/components/divider.tsx +82 -0
- package/registry/components/expanded/ActivityFeed.tsx +226 -0
- package/registry/components/expanded/Banner.tsx +145 -0
- package/registry/components/expanded/BannerBoard.tsx +225 -0
- package/registry/components/expanded/Breadcrumbs.tsx +156 -0
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +211 -0
- package/registry/components/expanded/CatalogDivider.tsx +48 -0
- package/registry/components/expanded/CatalogTag.tsx +92 -0
- package/registry/components/expanded/CommandBar.tsx +406 -0
- package/registry/components/expanded/FileUpload.tsx +231 -0
- package/registry/components/expanded/IconExplorer.tsx +612 -0
- package/registry/components/expanded/OnboardingStepListItem.tsx +67 -0
- package/registry/components/expanded/PageHeader.tsx +184 -0
- package/registry/components/expanded/Slideout.tsx +514 -0
- package/registry/components/expanded/Steps.tsx +266 -0
- package/registry/components/expanded/Table.tsx +1014 -0
- package/registry/components/expanded/Tabs.tsx +86 -0
- package/registry/components/expanded/Timeline.tsx +235 -0
- package/registry/components/expanded/TimelineShowcase.tsx +158 -0
- package/registry/components/expanded/activityFeed.css +292 -0
- package/registry/components/expanded/banner.css +312 -0
- package/registry/components/expanded/breadcrumbs.css +140 -0
- package/registry/components/expanded/catalogComponentsShowcase.css +87 -0
- package/registry/components/expanded/commandBar.css +473 -0
- package/registry/components/expanded/divider.css +75 -0
- package/registry/components/expanded/fileUpload.css +228 -0
- package/registry/components/expanded/iconExplorer.css +764 -0
- package/registry/components/expanded/iconPacks.ts +866 -0
- package/registry/components/expanded/onboardingStepListItem.css +126 -0
- package/registry/components/expanded/pageHeader.css +287 -0
- package/registry/components/expanded/slideout.css +955 -0
- package/registry/components/expanded/steps.css +329 -0
- package/registry/components/expanded/table.css +607 -0
- package/registry/components/expanded/tabs.css +197 -0
- package/registry/components/expanded/tag.css +148 -0
- package/registry/components/expanded/timeline.css +282 -0
- package/registry/components/input-content.ts +106 -0
- package/registry/components/input.tsx +866 -0
- package/registry/components/menu.tsx +758 -0
- package/registry/components/modal.tsx +799 -0
- package/registry/components/pagination.tsx +543 -0
- package/registry/components/progress-slider.tsx +216 -0
- package/registry/components/progress.tsx +367 -0
- package/registry/components/radio-card.tsx +654 -0
- package/registry/components/radio-group.tsx +570 -0
- package/registry/components/select-content.tsx +313 -0
- package/registry/components/select.tsx +871 -0
- package/registry/components/slider.tsx +380 -0
- package/registry/components/social-button.tsx +360 -0
- package/registry/components/spinner.tsx +31 -0
- package/registry/components/tag.tsx +423 -0
- package/registry/components/textarea.tsx +625 -0
- package/registry/components/toggle.tsx +272 -0
- package/registry/components/toolbar.tsx +467 -0
- package/registry/components/tooltip.tsx +427 -0
- package/registry/examples/accordion-demo.tsx +34 -0
- package/registry/examples/alert-demo.tsx +14 -0
- package/registry/examples/attribute-demo.tsx +65 -0
- package/registry/examples/avatar-demo.tsx +74 -0
- package/registry/examples/badge-demo.tsx +53 -0
- package/registry/examples/button-demo.tsx +83 -0
- package/registry/examples/button-group-demo.tsx +42 -0
- package/registry/examples/card-demo.tsx +48 -0
- package/registry/examples/checkbox-demo.tsx +67 -0
- package/registry/examples/date-picker-demo.tsx +74 -0
- package/registry/examples/divider-demo.tsx +17 -0
- package/registry/examples/expanded/activity-feed-demo.tsx +22 -0
- package/registry/examples/expanded/banner-demo.tsx +23 -0
- package/registry/examples/expanded/catalog-components-demo.tsx +5 -0
- package/registry/examples/expanded/command-bar-demo.tsx +10 -0
- package/registry/examples/expanded/icons-demo.tsx +5 -0
- package/registry/examples/expanded/onboarding-step-demo.tsx +11 -0
- package/registry/examples/expanded/page-header-demo.tsx +19 -0
- package/registry/examples/expanded/slideout-demo.tsx +15 -0
- package/registry/examples/expanded/steps-demo.tsx +18 -0
- package/registry/examples/expanded/tabs-demo.tsx +13 -0
- package/registry/examples/expanded/timeline-demo.tsx +18 -0
- package/registry/examples/input-demo.tsx +87 -0
- package/registry/examples/menu-demo.tsx +109 -0
- package/registry/examples/modal-demo.tsx +16 -0
- package/registry/examples/pagination-demo.tsx +17 -0
- package/registry/examples/progress-demo.tsx +37 -0
- package/registry/examples/progress-slider-demo.tsx +29 -0
- package/registry/examples/radio-card-demo.tsx +51 -0
- package/registry/examples/radio-group-demo.tsx +62 -0
- package/registry/examples/select-demo.tsx +73 -0
- package/registry/examples/slider-demo.tsx +31 -0
- package/registry/examples/social-button-demo.tsx +51 -0
- package/registry/examples/tag-demo.tsx +29 -0
- package/registry/examples/textarea-demo.tsx +79 -0
- package/registry/examples/toggle-demo.tsx +59 -0
- package/registry/examples/toolbar-demo.tsx +80 -0
- package/registry/examples/tooltip-demo.tsx +115 -0
- package/registry/hooks/use-direction.ts +27 -0
- package/registry/index.json +1213 -0
- package/registry/styles/globals.css +4600 -0
- package/registry/utils/cn.ts +6 -0
- package/src/cli/index.js +826 -0
- package/tokens/Color mode.zip +0 -0
- package/tokens/Numbers.zip +0 -0
- package/tokens/Radius.zip +0 -0
- package/tokens/Theme.zip +0 -0
- package/tokens/banhaten.tokens.json +5525 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { createElement, useEffect, useMemo, useState, type CSSProperties } from "react";
|
|
2
|
+
import {
|
|
3
|
+
iconPacks,
|
|
4
|
+
iconSemantics,
|
|
5
|
+
type IconPack,
|
|
6
|
+
type IconPosture,
|
|
7
|
+
type IconSemantic,
|
|
8
|
+
} from "./iconPacks";
|
|
9
|
+
import "./iconExplorer.css";
|
|
10
|
+
|
|
11
|
+
type ExplorerView = "evaluate" | "catalog" | "compare";
|
|
12
|
+
type Appearance = "default" | "rounded" | "sharp";
|
|
13
|
+
type Density = "comfortable" | "compact";
|
|
14
|
+
|
|
15
|
+
type CatalogResponse = {
|
|
16
|
+
total?: number;
|
|
17
|
+
title?: string;
|
|
18
|
+
categories?: Record<string, string[]>;
|
|
19
|
+
uncategorized?: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type CatalogRow = {
|
|
23
|
+
name: string;
|
|
24
|
+
category: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type CatalogState =
|
|
28
|
+
| { status: "idle" }
|
|
29
|
+
| { status: "loading" }
|
|
30
|
+
| { status: "error"; message: string }
|
|
31
|
+
| { status: "loaded"; total: number; title: string; rows: CatalogRow[] };
|
|
32
|
+
|
|
33
|
+
const postureOptions: Array<{ value: "all" | IconPosture; label: string }> = [
|
|
34
|
+
{ value: "all", label: "All candidates" },
|
|
35
|
+
{ value: "shortlist", label: "Shortlist" },
|
|
36
|
+
{ value: "broad", label: "Broad coverage" },
|
|
37
|
+
{ value: "specialized", label: "Specialized" },
|
|
38
|
+
{ value: "brand", label: "Brand logos" },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function Icon({ name, size = 18, className }: { name: string; size?: number; className?: string }) {
|
|
42
|
+
return createElement("iconify-icon", {
|
|
43
|
+
icon: name,
|
|
44
|
+
width: size,
|
|
45
|
+
height: size,
|
|
46
|
+
class: className,
|
|
47
|
+
"aria-hidden": "true",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectionToRows(collection: CatalogResponse) {
|
|
52
|
+
const rows: CatalogRow[] = [];
|
|
53
|
+
const seen = new Set<string>();
|
|
54
|
+
|
|
55
|
+
Object.entries(collection.categories ?? {}).forEach(([category, names]) => {
|
|
56
|
+
names.forEach((name) => {
|
|
57
|
+
if (seen.has(name)) return;
|
|
58
|
+
seen.add(name);
|
|
59
|
+
rows.push({ name, category });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
(collection.uncategorized ?? []).forEach((name) => {
|
|
64
|
+
if (seen.has(name)) return;
|
|
65
|
+
seen.add(name);
|
|
66
|
+
rows.push({ name, category: "Uncategorized" });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return rows;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function IconExplorer() {
|
|
73
|
+
const [activePackId, setActivePackId] = useState("lucide");
|
|
74
|
+
const [activeVariantByPack, setActiveVariantByPack] = useState<Record<string, string>>({});
|
|
75
|
+
const [view, setView] = useState<ExplorerView>("evaluate");
|
|
76
|
+
const [packQuery, setPackQuery] = useState("");
|
|
77
|
+
const [postureFilter, setPostureFilter] = useState<"all" | IconPosture>("all");
|
|
78
|
+
const [iconSize, setIconSize] = useState(18);
|
|
79
|
+
const [appearance, setAppearance] = useState<Appearance>("default");
|
|
80
|
+
const [density, setDensity] = useState<Density>("comfortable");
|
|
81
|
+
const [catalogs, setCatalogs] = useState<Record<string, CatalogState>>({});
|
|
82
|
+
const [catalogQuery, setCatalogQuery] = useState("");
|
|
83
|
+
const [catalogCategory, setCatalogCategory] = useState("all");
|
|
84
|
+
const [renderLimit, setRenderLimit] = useState(180);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (document.querySelector('script[data-iconify-icon="true"]')) return;
|
|
88
|
+
|
|
89
|
+
const script = document.createElement("script");
|
|
90
|
+
script.src = "https://code.iconify.design/iconify-icon/3.0.0/iconify-icon.min.js";
|
|
91
|
+
script.async = true;
|
|
92
|
+
script.dataset.iconifyIcon = "true";
|
|
93
|
+
document.head.appendChild(script);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const activePack = useMemo(
|
|
97
|
+
() => iconPacks.find((pack) => pack.id === activePackId) ?? iconPacks[0],
|
|
98
|
+
[activePackId],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const activeVariant = useMemo(() => {
|
|
102
|
+
const id = activeVariantByPack[activePack.id] ?? activePack.variants[0].id;
|
|
103
|
+
return activePack.variants.find((variant) => variant.id === id) ?? activePack.variants[0];
|
|
104
|
+
}, [activePack, activeVariantByPack]);
|
|
105
|
+
|
|
106
|
+
function getIconName(pack: IconPack, semantic: IconSemantic) {
|
|
107
|
+
const variantId = activeVariantByPack[pack.id] ?? pack.variants[0].id;
|
|
108
|
+
const variant = pack.variants.find((item) => item.id === variantId) ?? pack.variants[0];
|
|
109
|
+
const override = variant.overrides?.[semantic];
|
|
110
|
+
let base = override ?? pack.icons[semantic] ?? pack.icons.home;
|
|
111
|
+
|
|
112
|
+
if (!override && pack.variantMode === "prefix" && variant.prefix) {
|
|
113
|
+
base = `${variant.prefix}${base}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!override && pack.variantMode === "suffix" && variant.suffix) {
|
|
117
|
+
base = `${base}${variant.suffix}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return `${pack.prefix}:${base}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const filteredPacks = useMemo(() => {
|
|
124
|
+
const query = packQuery.trim().toLowerCase();
|
|
125
|
+
return iconPacks.filter((pack) => {
|
|
126
|
+
const matchesQuery = `${pack.name} ${pack.license} ${pack.tags.join(" ")}`
|
|
127
|
+
.toLowerCase()
|
|
128
|
+
.includes(query);
|
|
129
|
+
const matchesPosture = postureFilter === "all" || pack.posture === postureFilter;
|
|
130
|
+
return matchesQuery && matchesPosture;
|
|
131
|
+
});
|
|
132
|
+
}, [packQuery, postureFilter]);
|
|
133
|
+
|
|
134
|
+
const activeCatalog = catalogs[activePack.id] ?? { status: "idle" };
|
|
135
|
+
|
|
136
|
+
const catalogRows = useMemo(() => {
|
|
137
|
+
if (activeCatalog.status !== "loaded") return [];
|
|
138
|
+
|
|
139
|
+
const query = catalogQuery.trim().toLowerCase();
|
|
140
|
+
return activeCatalog.rows.filter((row) => {
|
|
141
|
+
const matchesQuery = !query || row.name.toLowerCase().includes(query);
|
|
142
|
+
const matchesCategory = catalogCategory === "all" || row.category === catalogCategory;
|
|
143
|
+
return matchesQuery && matchesCategory;
|
|
144
|
+
});
|
|
145
|
+
}, [activeCatalog, catalogCategory, catalogQuery]);
|
|
146
|
+
|
|
147
|
+
const catalogCategories = useMemo(() => {
|
|
148
|
+
if (activeCatalog.status !== "loaded") return [];
|
|
149
|
+
return Array.from(new Set(activeCatalog.rows.map((row) => row.category))).sort();
|
|
150
|
+
}, [activeCatalog]);
|
|
151
|
+
|
|
152
|
+
async function loadCatalog() {
|
|
153
|
+
setCatalogs((current) => ({ ...current, [activePack.id]: { status: "loading" } }));
|
|
154
|
+
try {
|
|
155
|
+
const response = await fetch(`https://api.iconify.design/collection?prefix=${activePack.prefix}&info=1`);
|
|
156
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
157
|
+
|
|
158
|
+
const collection = (await response.json()) as CatalogResponse;
|
|
159
|
+
setCatalogs((current) => ({
|
|
160
|
+
...current,
|
|
161
|
+
[activePack.id]: {
|
|
162
|
+
status: "loaded",
|
|
163
|
+
total: collection.total ?? 0,
|
|
164
|
+
title: collection.title ?? activePack.name,
|
|
165
|
+
rows: collectionToRows(collection),
|
|
166
|
+
},
|
|
167
|
+
}));
|
|
168
|
+
setRenderLimit(180);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
setCatalogs((current) => ({
|
|
171
|
+
...current,
|
|
172
|
+
[activePack.id]: {
|
|
173
|
+
status: "error",
|
|
174
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
175
|
+
},
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function selectPack(packId: string) {
|
|
181
|
+
setActivePackId(packId);
|
|
182
|
+
setCatalogCategory("all");
|
|
183
|
+
setCatalogQuery("");
|
|
184
|
+
setRenderLimit(180);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function selectVariant(variantId: string) {
|
|
188
|
+
setActiveVariantByPack((current) => ({ ...current, [activePack.id]: variantId }));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const radius =
|
|
192
|
+
appearance === "sharp"
|
|
193
|
+
? "var(--bh-radius-none)"
|
|
194
|
+
: appearance === "rounded"
|
|
195
|
+
? "var(--bh-radius-full)"
|
|
196
|
+
: "var(--bh-control-default)";
|
|
197
|
+
const panelRadius =
|
|
198
|
+
appearance === "sharp" ? "var(--bh-radius-none)" : "var(--bh-radius-lg-8)";
|
|
199
|
+
const rowHeight =
|
|
200
|
+
density === "compact"
|
|
201
|
+
? "calc(var(--bh-space-6xl-32) + var(--bh-space-xs-4))"
|
|
202
|
+
: "calc(var(--bh-space-7xl-40) + var(--bh-space-xxs-2))";
|
|
203
|
+
const previewStyle = { "--preview-icon-size": `${iconSize}px` } as CSSProperties;
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<section className="icons-page" aria-label="Icon pack explorer">
|
|
207
|
+
<aside className="icons-sidebar">
|
|
208
|
+
<div className="icons-sidebar__header">
|
|
209
|
+
<span className="icons-mark">
|
|
210
|
+
<Icon name="lucide:boxes" size={20} />
|
|
211
|
+
</span>
|
|
212
|
+
<div>
|
|
213
|
+
<h1>Icons</h1>
|
|
214
|
+
<p>Open-source pack review</p>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div className="icons-sidebar__tools">
|
|
219
|
+
<input
|
|
220
|
+
aria-label="Filter icon packs"
|
|
221
|
+
className="icons-input"
|
|
222
|
+
onChange={(event) => setPackQuery(event.target.value)}
|
|
223
|
+
placeholder="Filter packs"
|
|
224
|
+
type="search"
|
|
225
|
+
value={packQuery}
|
|
226
|
+
/>
|
|
227
|
+
<select
|
|
228
|
+
aria-label="Filter candidates"
|
|
229
|
+
className="icons-select"
|
|
230
|
+
onChange={(event) => setPostureFilter(event.target.value as "all" | IconPosture)}
|
|
231
|
+
value={postureFilter}
|
|
232
|
+
>
|
|
233
|
+
{postureOptions.map((option) => (
|
|
234
|
+
<option key={option.value} value={option.value}>
|
|
235
|
+
{option.label}
|
|
236
|
+
</option>
|
|
237
|
+
))}
|
|
238
|
+
</select>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div className="icons-pack-list">
|
|
242
|
+
{filteredPacks.map((pack) => (
|
|
243
|
+
<button
|
|
244
|
+
className={pack.id === activePack.id ? "icons-pack is-active" : "icons-pack"}
|
|
245
|
+
key={pack.id}
|
|
246
|
+
onClick={() => selectPack(pack.id)}
|
|
247
|
+
type="button"
|
|
248
|
+
>
|
|
249
|
+
<span className="icons-pack__glyph">
|
|
250
|
+
<Icon name={getIconName(pack, "home")} size={22} />
|
|
251
|
+
</span>
|
|
252
|
+
<span className="icons-pack__copy">
|
|
253
|
+
<strong>{pack.name}</strong>
|
|
254
|
+
<span>
|
|
255
|
+
{pack.total.toLocaleString()} icons, {pack.license}
|
|
256
|
+
</span>
|
|
257
|
+
</span>
|
|
258
|
+
</button>
|
|
259
|
+
))}
|
|
260
|
+
</div>
|
|
261
|
+
</aside>
|
|
262
|
+
|
|
263
|
+
<main className="icons-main">
|
|
264
|
+
<header className="icons-topbar">
|
|
265
|
+
<div>
|
|
266
|
+
<h2>Icon pack explorer</h2>
|
|
267
|
+
<p>
|
|
268
|
+
{activePack.total.toLocaleString()} icons through Iconify prefix <code>{activePack.prefix}</code>
|
|
269
|
+
</p>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<nav className="icons-tabs" aria-label="Icon explorer views">
|
|
273
|
+
{(["evaluate", "catalog", "compare"] as ExplorerView[]).map((item) => (
|
|
274
|
+
<button
|
|
275
|
+
className={view === item ? "icons-tab is-active" : "icons-tab"}
|
|
276
|
+
key={item}
|
|
277
|
+
onClick={() => setView(item)}
|
|
278
|
+
type="button"
|
|
279
|
+
>
|
|
280
|
+
{item[0].toUpperCase() + item.slice(1)}
|
|
281
|
+
</button>
|
|
282
|
+
))}
|
|
283
|
+
</nav>
|
|
284
|
+
</header>
|
|
285
|
+
|
|
286
|
+
{view === "evaluate" && (
|
|
287
|
+
<div className="icons-content">
|
|
288
|
+
<section className="icons-summary-grid">
|
|
289
|
+
<div className="icons-panel icons-hero-panel">
|
|
290
|
+
<span className="icons-hero-glyph">
|
|
291
|
+
<Icon name={getIconName(activePack, "home")} size={46} />
|
|
292
|
+
</span>
|
|
293
|
+
<div className="icons-hero-copy">
|
|
294
|
+
<h3>{activePack.name}</h3>
|
|
295
|
+
<p>{activePack.summary}</p>
|
|
296
|
+
<div className="icons-tags">
|
|
297
|
+
<span className="icons-tag is-warm">{activePack.posture}</span>
|
|
298
|
+
<span className="icons-tag">{activePack.license}</span>
|
|
299
|
+
{activePack.tags.map((tag) => (
|
|
300
|
+
<span className="icons-tag" key={tag}>
|
|
301
|
+
{tag}
|
|
302
|
+
</span>
|
|
303
|
+
))}
|
|
304
|
+
</div>
|
|
305
|
+
<div className="icons-links">
|
|
306
|
+
<a href={activePack.officialUrl} rel="noreferrer" target="_blank">
|
|
307
|
+
Official
|
|
308
|
+
</a>
|
|
309
|
+
<a href={activePack.licenseUrl} rel="noreferrer" target="_blank">
|
|
310
|
+
License
|
|
311
|
+
</a>
|
|
312
|
+
<a href={`https://icon-sets.iconify.design/${activePack.prefix}/`} rel="noreferrer" target="_blank">
|
|
313
|
+
Iconify catalog
|
|
314
|
+
</a>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div className="icons-panel">
|
|
320
|
+
<div className="icons-panel__header">
|
|
321
|
+
<h3>Fit snapshot</h3>
|
|
322
|
+
</div>
|
|
323
|
+
<div className="icons-score-list">
|
|
324
|
+
{[
|
|
325
|
+
["System fit", activePack.scores.fit],
|
|
326
|
+
["Coverage", activePack.scores.coverage],
|
|
327
|
+
["Variant depth", activePack.scores.variants],
|
|
328
|
+
["Dense UI clarity", activePack.scores.density],
|
|
329
|
+
].map(([label, value]) => (
|
|
330
|
+
<div className="icons-score" key={label}>
|
|
331
|
+
<span>
|
|
332
|
+
<strong>{label}</strong>
|
|
333
|
+
<em>{value}</em>
|
|
334
|
+
</span>
|
|
335
|
+
<div>
|
|
336
|
+
<i style={{ width: `${value}%` }} />
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</section>
|
|
343
|
+
|
|
344
|
+
<section className="icons-panel">
|
|
345
|
+
<div className="icons-panel__header">
|
|
346
|
+
<h3>Variants</h3>
|
|
347
|
+
<span>{activeVariant.label}</span>
|
|
348
|
+
</div>
|
|
349
|
+
<div className="icons-variants">
|
|
350
|
+
{activePack.variants.map((variant) => (
|
|
351
|
+
<button
|
|
352
|
+
className={variant.id === activeVariant.id ? "icons-variant is-active" : "icons-variant"}
|
|
353
|
+
key={variant.id}
|
|
354
|
+
onClick={() => selectVariant(variant.id)}
|
|
355
|
+
type="button"
|
|
356
|
+
>
|
|
357
|
+
{variant.label}
|
|
358
|
+
</button>
|
|
359
|
+
))}
|
|
360
|
+
</div>
|
|
361
|
+
</section>
|
|
362
|
+
|
|
363
|
+
<section className="icons-panel">
|
|
364
|
+
<div className="icons-panel__header">
|
|
365
|
+
<h3>Icon samples</h3>
|
|
366
|
+
<span>{activePack.prefix}</span>
|
|
367
|
+
</div>
|
|
368
|
+
<div className="icons-sample-grid">
|
|
369
|
+
{iconSemantics.map(([semantic, label]) => (
|
|
370
|
+
<button className="icons-sample" key={semantic} title={getIconName(activePack, semantic)} type="button">
|
|
371
|
+
<Icon name={getIconName(activePack, semantic)} size={Math.max(iconSize, 22)} />
|
|
372
|
+
<span>{label}</span>
|
|
373
|
+
</button>
|
|
374
|
+
))}
|
|
375
|
+
</div>
|
|
376
|
+
</section>
|
|
377
|
+
|
|
378
|
+
<section className="icons-component-grid">
|
|
379
|
+
<div className="icons-panel">
|
|
380
|
+
<div className="icons-panel__header">
|
|
381
|
+
<h3>Component controls</h3>
|
|
382
|
+
</div>
|
|
383
|
+
<div className="icons-controls">
|
|
384
|
+
<label>
|
|
385
|
+
<span>Icon size</span>
|
|
386
|
+
<input
|
|
387
|
+
max={30}
|
|
388
|
+
min={14}
|
|
389
|
+
onChange={(event) => setIconSize(Number(event.target.value))}
|
|
390
|
+
type="range"
|
|
391
|
+
value={iconSize}
|
|
392
|
+
/>
|
|
393
|
+
</label>
|
|
394
|
+
<label>
|
|
395
|
+
<span>Appearance</span>
|
|
396
|
+
<select
|
|
397
|
+
className="icons-select"
|
|
398
|
+
onChange={(event) => setAppearance(event.target.value as Appearance)}
|
|
399
|
+
value={appearance}
|
|
400
|
+
>
|
|
401
|
+
<option value="default">Default</option>
|
|
402
|
+
<option value="rounded">Rounded</option>
|
|
403
|
+
<option value="sharp">Sharp</option>
|
|
404
|
+
</select>
|
|
405
|
+
</label>
|
|
406
|
+
<label>
|
|
407
|
+
<span>Density</span>
|
|
408
|
+
<select
|
|
409
|
+
className="icons-select"
|
|
410
|
+
onChange={(event) => setDensity(event.target.value as Density)}
|
|
411
|
+
value={density}
|
|
412
|
+
>
|
|
413
|
+
<option value="comfortable">Comfortable</option>
|
|
414
|
+
<option value="compact">Compact</option>
|
|
415
|
+
</select>
|
|
416
|
+
</label>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<div className="icons-panel">
|
|
421
|
+
<div className="icons-panel__header">
|
|
422
|
+
<h3>Component preview</h3>
|
|
423
|
+
</div>
|
|
424
|
+
<div className="icons-preview" style={previewStyle}>
|
|
425
|
+
<div className="icons-button-row">
|
|
426
|
+
<button className="preview-button is-primary" style={{ borderRadius: radius }} type="button">
|
|
427
|
+
<Icon name={getIconName(activePack, "add")} size={iconSize} />
|
|
428
|
+
Create
|
|
429
|
+
</button>
|
|
430
|
+
<button className="preview-button" style={{ borderRadius: radius }} type="button">
|
|
431
|
+
<Icon name={getIconName(activePack, "download")} size={iconSize} />
|
|
432
|
+
Export
|
|
433
|
+
</button>
|
|
434
|
+
<button className="preview-button is-danger" style={{ borderRadius: radius }} type="button">
|
|
435
|
+
<Icon name={getIconName(activePack, "trash")} size={iconSize} />
|
|
436
|
+
Delete
|
|
437
|
+
</button>
|
|
438
|
+
<button className="preview-button is-icon-only" style={{ borderRadius: radius }} type="button">
|
|
439
|
+
<Icon name={getIconName(activePack, "settings")} size={iconSize} />
|
|
440
|
+
</button>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<div className="icons-field-row">
|
|
444
|
+
<div className="icons-field" style={{ borderRadius: radius }}>
|
|
445
|
+
<Icon name={getIconName(activePack, "search")} size={iconSize} />
|
|
446
|
+
Search projects, users, invoices
|
|
447
|
+
</div>
|
|
448
|
+
<div className="icons-field is-select" style={{ borderRadius: radius }}>
|
|
449
|
+
<span>
|
|
450
|
+
<Icon name={getIconName(activePack, "filter")} size={iconSize} />
|
|
451
|
+
Status
|
|
452
|
+
</span>
|
|
453
|
+
<Icon name={getIconName(activePack, "chevron")} size={16} />
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<div className="icons-menu-preview" style={{ borderRadius: panelRadius }}>
|
|
458
|
+
{[
|
|
459
|
+
["home", "Dashboard", "chevron"],
|
|
460
|
+
["users", "Customers", "24"],
|
|
461
|
+
["bell", "Notifications", "Live"],
|
|
462
|
+
].map(([left, label, right]) => (
|
|
463
|
+
<div className="icons-menu-row" key={label} style={{ minHeight: rowHeight }}>
|
|
464
|
+
<Icon name={getIconName(activePack, left as IconSemantic)} size={iconSize} />
|
|
465
|
+
<span>{label}</span>
|
|
466
|
+
{right === "Live" ? (
|
|
467
|
+
<em>
|
|
468
|
+
<Icon name={getIconName(activePack, "check")} size={14} />
|
|
469
|
+
Live
|
|
470
|
+
</em>
|
|
471
|
+
) : right === "24" ? (
|
|
472
|
+
<small>24</small>
|
|
473
|
+
) : (
|
|
474
|
+
<Icon name={getIconName(activePack, "chevron")} size={16} />
|
|
475
|
+
)}
|
|
476
|
+
</div>
|
|
477
|
+
))}
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<div className="icons-table-preview" style={{ borderRadius: panelRadius }}>
|
|
481
|
+
{[
|
|
482
|
+
["chart", "Usage API", "Infrastructure", "92.8%", "more"],
|
|
483
|
+
["calendar", "Billing review", "Finance", "Jun 12", "edit"],
|
|
484
|
+
["warning", "Risk queue", "Operations", "4 open", "more"],
|
|
485
|
+
].map(([left, name, area, value, action]) => (
|
|
486
|
+
<div className="icons-table-row" key={name} style={{ minHeight: rowHeight }}>
|
|
487
|
+
<Icon name={getIconName(activePack, left as IconSemantic)} size={iconSize} />
|
|
488
|
+
<strong>{name}</strong>
|
|
489
|
+
<span>{area}</span>
|
|
490
|
+
<span>{value}</span>
|
|
491
|
+
<Icon name={getIconName(activePack, action as IconSemantic)} size={16} />
|
|
492
|
+
</div>
|
|
493
|
+
))}
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<div className="icons-empty-preview" style={{ borderRadius: panelRadius }}>
|
|
497
|
+
<div>
|
|
498
|
+
<span style={{ borderRadius: panelRadius }}>
|
|
499
|
+
<Icon name={getIconName(activePack, "filter")} size={28} />
|
|
500
|
+
</span>
|
|
501
|
+
<h3>No matching records</h3>
|
|
502
|
+
<p>Adjust the active filters or create a saved view for this workflow.</p>
|
|
503
|
+
<button className="preview-button is-primary" style={{ borderRadius: radius }} type="button">
|
|
504
|
+
<Icon name={getIconName(activePack, "add")} size={iconSize} />
|
|
505
|
+
Saved view
|
|
506
|
+
</button>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</section>
|
|
512
|
+
</div>
|
|
513
|
+
)}
|
|
514
|
+
|
|
515
|
+
{view === "catalog" && (
|
|
516
|
+
<div className="icons-content">
|
|
517
|
+
<section className="icons-panel">
|
|
518
|
+
<div className="icons-panel__header">
|
|
519
|
+
<div>
|
|
520
|
+
<h3>{activePack.name} catalog</h3>
|
|
521
|
+
<span>{catalogStatusText(activeCatalog, catalogRows.length)}</span>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
<div className="icons-catalog-tools">
|
|
525
|
+
<input
|
|
526
|
+
className="icons-input"
|
|
527
|
+
onChange={(event) => {
|
|
528
|
+
setCatalogQuery(event.target.value);
|
|
529
|
+
setRenderLimit(180);
|
|
530
|
+
}}
|
|
531
|
+
placeholder="Search icons in this pack"
|
|
532
|
+
type="search"
|
|
533
|
+
value={catalogQuery}
|
|
534
|
+
/>
|
|
535
|
+
<select
|
|
536
|
+
className="icons-select"
|
|
537
|
+
onChange={(event) => {
|
|
538
|
+
setCatalogCategory(event.target.value);
|
|
539
|
+
setRenderLimit(180);
|
|
540
|
+
}}
|
|
541
|
+
value={catalogCategory}
|
|
542
|
+
>
|
|
543
|
+
<option value="all">All categories</option>
|
|
544
|
+
{catalogCategories.map((category) => (
|
|
545
|
+
<option key={category} value={category}>
|
|
546
|
+
{category}
|
|
547
|
+
</option>
|
|
548
|
+
))}
|
|
549
|
+
</select>
|
|
550
|
+
<button className="preview-button" disabled={activeCatalog.status === "loading"} onClick={loadCatalog} type="button">
|
|
551
|
+
<Icon name="lucide:download-cloud" size={18} />
|
|
552
|
+
{activeCatalog.status === "loading" ? "Loading" : "Load catalog"}
|
|
553
|
+
</button>
|
|
554
|
+
</div>
|
|
555
|
+
</section>
|
|
556
|
+
|
|
557
|
+
{activeCatalog.status === "error" && <div className="icons-error">Could not load catalog: {activeCatalog.message}</div>}
|
|
558
|
+
|
|
559
|
+
<section className="icons-catalog-grid">
|
|
560
|
+
{activeCatalog.status === "loaded" &&
|
|
561
|
+
catalogRows.slice(0, renderLimit).map((row) => (
|
|
562
|
+
<button className="icons-catalog-card" key={`${row.category}-${row.name}`} title={`${activePack.prefix}:${row.name}`} type="button">
|
|
563
|
+
<Icon name={`${activePack.prefix}:${row.name}`} size={30} />
|
|
564
|
+
<span>{row.name}</span>
|
|
565
|
+
</button>
|
|
566
|
+
))}
|
|
567
|
+
</section>
|
|
568
|
+
|
|
569
|
+
{activeCatalog.status === "loaded" && renderLimit < catalogRows.length && (
|
|
570
|
+
<button className="preview-button icons-load-more" onClick={() => setRenderLimit((current) => current + 180)} type="button">
|
|
571
|
+
<Icon name="lucide:plus" size={18} />
|
|
572
|
+
Show more
|
|
573
|
+
</button>
|
|
574
|
+
)}
|
|
575
|
+
</div>
|
|
576
|
+
)}
|
|
577
|
+
|
|
578
|
+
{view === "compare" && (
|
|
579
|
+
<div className="icons-content">
|
|
580
|
+
<section className="icons-compare-grid">
|
|
581
|
+
{iconPacks.map((pack) => (
|
|
582
|
+
<article className="icons-compare-card" key={pack.id}>
|
|
583
|
+
<div>
|
|
584
|
+
<strong>{pack.name}</strong>
|
|
585
|
+
<span>{pack.license}</span>
|
|
586
|
+
</div>
|
|
587
|
+
<div className="icons-compare-icons">
|
|
588
|
+
{(["home", "search", "settings", "users", "warning"] as IconSemantic[]).map((semantic) => (
|
|
589
|
+
<span key={semantic}>
|
|
590
|
+
<Icon name={getIconName(pack, semantic)} size={22} />
|
|
591
|
+
</span>
|
|
592
|
+
))}
|
|
593
|
+
</div>
|
|
594
|
+
<p>
|
|
595
|
+
{pack.total.toLocaleString()} icons, {pack.tags.join(", ")}
|
|
596
|
+
</p>
|
|
597
|
+
</article>
|
|
598
|
+
))}
|
|
599
|
+
</section>
|
|
600
|
+
</div>
|
|
601
|
+
)}
|
|
602
|
+
</main>
|
|
603
|
+
</section>
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function catalogStatusText(catalog: CatalogState, matchCount: number) {
|
|
608
|
+
if (catalog.status === "idle") return "Catalog names load on demand from Iconify.";
|
|
609
|
+
if (catalog.status === "loading") return "Loading the full icon name list...";
|
|
610
|
+
if (catalog.status === "error") return "Catalog failed to load.";
|
|
611
|
+
return `${catalog.total.toLocaleString()} icons loaded, ${matchCount.toLocaleString()} matching.`;
|
|
612
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import "./onboardingStepListItem.css";
|
|
2
|
+
|
|
3
|
+
export type OnboardingStepState = "finished" | "active" | "inactive";
|
|
4
|
+
|
|
5
|
+
export type OnboardingStepListItemProps = {
|
|
6
|
+
className?: string;
|
|
7
|
+
dir?: "ltr" | "rtl";
|
|
8
|
+
label?: string;
|
|
9
|
+
showLeadingLine?: boolean;
|
|
10
|
+
showTrailingLine?: boolean;
|
|
11
|
+
state?: OnboardingStepState;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function OnboardingStepListItem({
|
|
15
|
+
className = "",
|
|
16
|
+
dir = "ltr",
|
|
17
|
+
label,
|
|
18
|
+
showLeadingLine = true,
|
|
19
|
+
showTrailingLine = true,
|
|
20
|
+
state = "finished",
|
|
21
|
+
}: OnboardingStepListItemProps) {
|
|
22
|
+
const rtl = dir === "rtl";
|
|
23
|
+
const isFinished = state === "finished";
|
|
24
|
+
const isActive = state === "active";
|
|
25
|
+
const isInactive = state === "inactive";
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
className={[
|
|
30
|
+
"ds-onboarding-step",
|
|
31
|
+
`ds-onboarding-step--${state}`,
|
|
32
|
+
rtl ? "ds-onboarding-step--rtl" : "ds-onboarding-step--ltr",
|
|
33
|
+
className,
|
|
34
|
+
]
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.join(" ")}
|
|
37
|
+
dir={dir}
|
|
38
|
+
>
|
|
39
|
+
<span className="ds-onboarding-step__line-wrap" aria-hidden="true">
|
|
40
|
+
{isFinished && showLeadingLine && <span className="ds-onboarding-step__line ds-onboarding-step__line--leading" />}
|
|
41
|
+
{isFinished && <CheckCircleIcon />}
|
|
42
|
+
{isFinished && showTrailingLine && <span className="ds-onboarding-step__line ds-onboarding-step__line--trailing" />}
|
|
43
|
+
{isActive && <span className="ds-onboarding-step__line ds-onboarding-step__line--active" />}
|
|
44
|
+
{isInactive && <span className="ds-onboarding-step__line ds-onboarding-step__line--inactive" />}
|
|
45
|
+
</span>
|
|
46
|
+
{label && <span className="ds-onboarding-step__label">{label}</span>}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CheckCircleIcon() {
|
|
52
|
+
return (
|
|
53
|
+
<svg
|
|
54
|
+
className="ds-onboarding-step__check"
|
|
55
|
+
fill="none"
|
|
56
|
+
height="20"
|
|
57
|
+
viewBox="0 0 20 20"
|
|
58
|
+
width="20"
|
|
59
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
60
|
+
>
|
|
61
|
+
<path
|
|
62
|
+
d="M10 1.667A8.333 8.333 0 1 1 10 18.333 8.333 8.333 0 0 1 10 1.667Zm-.831 11.667 5.89-5.893-1.179-1.178-4.711 4.714-2.357-2.357-1.179 1.178 3.536 3.536Z"
|
|
63
|
+
fill="currentColor"
|
|
64
|
+
/>
|
|
65
|
+
</svg>
|
|
66
|
+
);
|
|
67
|
+
}
|