@dazl/component-gallery 1.0.0 → 2.0.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/README.md +4 -2
- package/dist/example/example.d.ts +6 -2
- package/dist/example/example.d.ts.map +1 -1
- package/dist/index.js +26 -11
- package/dist/route/index.js +141 -87
- package/dist/styles.css +105 -1
- package/package.json +2 -2
- package/src/example/example.module.css +1 -2
- package/src/example/example.tsx +20 -4
- package/src/route/component-gallery.module.css +11 -4
- package/src/route/component-gallery.tsx +1 -1
- package/src/route/example-card.module.css +19 -25
- package/src/route/example-card.tsx +16 -16
package/README.md
CHANGED
|
@@ -65,13 +65,15 @@ import { Button } from './button';
|
|
|
65
65
|
export default function ButtonExample() {
|
|
66
66
|
return (
|
|
67
67
|
<>
|
|
68
|
-
<Section
|
|
68
|
+
<Section layout="row">
|
|
69
|
+
<Section.Title>Sizes</Section.Title>
|
|
69
70
|
<Button size="sm">Small</Button>
|
|
70
71
|
<Button size="md">Medium</Button>
|
|
71
72
|
<Button size="lg">Large</Button>
|
|
72
73
|
</Section>
|
|
73
74
|
|
|
74
|
-
<Section
|
|
75
|
+
<Section layout="column">
|
|
76
|
+
<Section.Title>States</Section.Title>
|
|
75
77
|
<Button disabled>Disabled</Button>
|
|
76
78
|
<Button loading>Loading</Button>
|
|
77
79
|
</Section>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export interface SectionProps {
|
|
2
|
-
title?: string;
|
|
3
2
|
layout?: 'row' | 'column';
|
|
4
3
|
children: React.ReactNode;
|
|
5
4
|
}
|
|
6
|
-
export declare const Section:
|
|
5
|
+
export declare const Section: {
|
|
6
|
+
({ layout, children }: SectionProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
Title: import("react").FC<{
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
7
11
|
//# sourceMappingURL=example.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../../src/example/example.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../../src/example/example.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IACzB,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAED,eAAO,MAAM,OAAO;2BAAqC,YAAY;;kBAa5B,KAAK,CAAC,SAAS;;CAFvD,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import { jsxs
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Children, isValidElement } from "react";
|
|
3
|
+
const section = "section";
|
|
4
|
+
const sectionTitle = "sectionTitle";
|
|
5
|
+
const sectionContentColumn = "sectionContentColumn";
|
|
6
|
+
const sectionContentRow = "sectionContentRow";
|
|
7
|
+
const styles = {
|
|
8
|
+
section,
|
|
9
|
+
sectionTitle,
|
|
10
|
+
sectionContentColumn,
|
|
11
|
+
sectionContentRow
|
|
12
|
+
};
|
|
13
|
+
const Section = ({ layout = "column", children }) => {
|
|
14
|
+
const { title, content } = extractTitle(children);
|
|
15
|
+
return /* @__PURE__ */ jsxs("section", { className: styles.section, children: [
|
|
16
|
+
title,
|
|
17
|
+
/* @__PURE__ */ jsx("div", { className: layout === "column" ? styles.sectionContentColumn : styles.sectionContentRow, children: content })
|
|
18
|
+
] });
|
|
19
|
+
};
|
|
20
|
+
const SectionTitle = ({ children }) => /* @__PURE__ */ jsx("div", { className: styles.sectionTitle, children });
|
|
21
|
+
Section.Title = SectionTitle;
|
|
22
|
+
const extractTitle = (children) => {
|
|
23
|
+
const [title, ...content] = Children.toArray(children);
|
|
24
|
+
return isValidElement(title) && title.type === SectionTitle ? { title, content } : { title: null, content: children };
|
|
25
|
+
};
|
|
11
26
|
export {
|
|
12
|
-
|
|
27
|
+
Section
|
|
13
28
|
};
|
package/dist/route/index.js
CHANGED
|
@@ -1,59 +1,92 @@
|
|
|
1
|
-
import { jsxs
|
|
2
|
-
import { useCallback
|
|
3
|
-
import { z
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, memo, useState, lazy, Suspense, Component, useMemo, useEffect, useRef } from "react";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const componentGallery = "componentGallery";
|
|
5
|
+
const styles$1 = {
|
|
6
|
+
componentGallery
|
|
7
|
+
};
|
|
8
|
+
const useLinkBehaviorOverride = () => {
|
|
9
|
+
return useCallback((container) => {
|
|
10
|
+
if (!container) return;
|
|
11
|
+
const handleClick = (event) => {
|
|
12
|
+
if (!(event.target instanceof Element)) return;
|
|
13
|
+
const link = event.target.closest("a");
|
|
14
|
+
if (!link) return;
|
|
15
|
+
const href = link.getAttribute("href");
|
|
16
|
+
if (!href) return;
|
|
17
|
+
event.preventDefault();
|
|
18
|
+
if (isAbsoluteUrl(href)) {
|
|
19
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
container.addEventListener("click", handleClick);
|
|
23
|
+
return () => {
|
|
24
|
+
container.removeEventListener("click", handleClick);
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
};
|
|
28
|
+
const isAbsoluteUrl = (url) => /^[a-z][a-z0-9+.-]*:/i.test(url);
|
|
29
|
+
const exampleCard = "exampleCard";
|
|
30
|
+
const exampleCardHeader = "exampleCardHeader";
|
|
31
|
+
const exampleCardContent = "exampleCardContent";
|
|
32
|
+
const exampleCardLoading = "exampleCardLoading";
|
|
33
|
+
const exampleCardSpinner = "exampleCardSpinner";
|
|
34
|
+
const exampleCardError = "exampleCardError";
|
|
35
|
+
const exampleCardErrorIcon = "exampleCardErrorIcon";
|
|
36
|
+
const exampleCardErrorMessage = "exampleCardErrorMessage";
|
|
37
|
+
const styles = {
|
|
38
|
+
exampleCard,
|
|
39
|
+
exampleCardHeader,
|
|
40
|
+
exampleCardContent,
|
|
41
|
+
exampleCardLoading,
|
|
42
|
+
exampleCardSpinner,
|
|
43
|
+
exampleCardError,
|
|
44
|
+
exampleCardErrorIcon,
|
|
45
|
+
exampleCardErrorMessage
|
|
46
|
+
};
|
|
47
|
+
const ExampleCard = memo(function Example({ example }) {
|
|
48
|
+
const [Component2] = useState(() => lazy(() => import(
|
|
29
49
|
/* @vite-ignore */
|
|
30
|
-
`/${
|
|
31
|
-
)))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/* @__PURE__ */
|
|
50
|
+
`/${example.relativePath}`
|
|
51
|
+
)));
|
|
52
|
+
const contentRef = useLinkBehaviorOverride();
|
|
53
|
+
return /* @__PURE__ */ jsxs("div", { id: example.relativePath, className: styles.exampleCard, children: [
|
|
54
|
+
/* @__PURE__ */ jsx("h3", { className: styles.exampleCardHeader, children: example.displayName }),
|
|
55
|
+
/* @__PURE__ */ jsx(ErrorBoundary, { fallback: (error) => /* @__PURE__ */ jsx(ExampleCardError, { error }), children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ExampleCardLoading, {}), children: /* @__PURE__ */ jsx("div", { ref: contentRef, className: styles.exampleCardContent, children: /* @__PURE__ */ jsx(Component2, {}) }) }) })
|
|
56
|
+
] });
|
|
57
|
+
});
|
|
58
|
+
const ExampleCardLoading = () => {
|
|
59
|
+
return /* @__PURE__ */ jsx("div", { className: styles.exampleCardLoading, children: /* @__PURE__ */ jsx("div", { className: styles.exampleCardSpinner }) });
|
|
60
|
+
};
|
|
61
|
+
const ExampleCardError = ({ error }) => {
|
|
62
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.exampleCardError, children: [
|
|
63
|
+
/* @__PURE__ */ jsx("span", { className: styles.exampleCardErrorIcon, children: "⚠" }),
|
|
64
|
+
/* @__PURE__ */ jsx("span", { className: styles.exampleCardErrorMessage, children: error.message })
|
|
35
65
|
] });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
constructor(r) {
|
|
42
|
-
super(r), this.state = { error: null };
|
|
66
|
+
};
|
|
67
|
+
class ErrorBoundary extends Component {
|
|
68
|
+
constructor(props) {
|
|
69
|
+
super(props);
|
|
70
|
+
this.state = { error: null };
|
|
43
71
|
}
|
|
44
|
-
static getDerivedStateFromError(
|
|
45
|
-
return { error
|
|
72
|
+
static getDerivedStateFromError(error) {
|
|
73
|
+
return { error };
|
|
46
74
|
}
|
|
47
75
|
render() {
|
|
48
|
-
|
|
76
|
+
if (this.state.error) {
|
|
77
|
+
return this.props.fallback(this.state.error);
|
|
78
|
+
}
|
|
79
|
+
return this.props.children;
|
|
49
80
|
}
|
|
50
81
|
}
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
82
|
+
const scrollExampleCardIntoView = (id) => {
|
|
83
|
+
const element = document.getElementById(id);
|
|
84
|
+
if (!element) return;
|
|
85
|
+
element.scrollIntoView({
|
|
54
86
|
behavior: "instant",
|
|
55
87
|
block: "nearest"
|
|
56
|
-
})
|
|
88
|
+
});
|
|
89
|
+
element.animate(
|
|
57
90
|
[
|
|
58
91
|
{ boxShadow: "0 0 0 0 #3E63DD" },
|
|
59
92
|
{ boxShadow: "0 0 0 4px #3E63DD", offset: 0.2 },
|
|
@@ -63,53 +96,74 @@ const j = (e) => {
|
|
|
63
96
|
duration: 900,
|
|
64
97
|
easing: "ease-out"
|
|
65
98
|
}
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
const componentExampleInfoSchema = z.object({
|
|
102
|
+
displayName: z.string(),
|
|
103
|
+
relativePath: z.string()
|
|
104
|
+
});
|
|
105
|
+
const componentGalleryParamsSchema = z.object({
|
|
106
|
+
columns: z.number().default(2),
|
|
107
|
+
examples: z.array(componentExampleInfoSchema).default([]),
|
|
108
|
+
selectedExamplePath: z.string().optional(),
|
|
109
|
+
random: z.number().optional()
|
|
110
|
+
});
|
|
111
|
+
const useLocationHash = () => {
|
|
112
|
+
const [hash, setHash] = useState(() => typeof window !== "undefined" ? window.location.hash : "");
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const handleHashChange = () => setHash(window.location.hash);
|
|
115
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
116
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
117
|
+
}, []);
|
|
118
|
+
return hash;
|
|
119
|
+
};
|
|
120
|
+
const useComponentGalleryParams = () => {
|
|
121
|
+
const hash = useLocationHash();
|
|
122
|
+
return useMemo(() => {
|
|
84
123
|
try {
|
|
85
|
-
const
|
|
86
|
-
return
|
|
124
|
+
const parsed = JSON.parse(decodeURIComponent(hash.slice(1)));
|
|
125
|
+
return componentGalleryParamsSchema.parse(parsed);
|
|
87
126
|
} catch {
|
|
88
127
|
return { columns: 1, examples: [] };
|
|
89
128
|
}
|
|
90
|
-
}, [
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
129
|
+
}, [hash]);
|
|
130
|
+
};
|
|
131
|
+
const useScrollToExampleCard = (relativePath, counter) => {
|
|
132
|
+
const containerRef = useRef(null);
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const container = containerRef.current;
|
|
135
|
+
if (!relativePath || !container) return;
|
|
136
|
+
let scrolledManually = false;
|
|
137
|
+
let scrollingProgrammatically = false;
|
|
138
|
+
let animationFrameId = 0;
|
|
139
|
+
const handleScroll = () => {
|
|
140
|
+
if (scrollingProgrammatically) return;
|
|
141
|
+
scrolledManually = true;
|
|
142
|
+
};
|
|
143
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
144
|
+
if (scrolledManually) return;
|
|
145
|
+
scrollingProgrammatically = true;
|
|
146
|
+
scrollExampleCardIntoView(relativePath);
|
|
147
|
+
cancelAnimationFrame(animationFrameId);
|
|
148
|
+
animationFrameId = requestAnimationFrame(() => {
|
|
149
|
+
scrollingProgrammatically = false;
|
|
150
|
+
});
|
|
103
151
|
});
|
|
104
|
-
|
|
105
|
-
|
|
152
|
+
window.addEventListener("scroll", handleScroll);
|
|
153
|
+
resizeObserver.observe(container);
|
|
154
|
+
return () => {
|
|
155
|
+
window.removeEventListener("scroll", handleScroll);
|
|
156
|
+
resizeObserver.disconnect();
|
|
157
|
+
cancelAnimationFrame(animationFrameId);
|
|
106
158
|
};
|
|
107
|
-
}, [
|
|
159
|
+
}, [relativePath, counter]);
|
|
160
|
+
return containerRef;
|
|
108
161
|
};
|
|
109
|
-
function
|
|
110
|
-
const { columns
|
|
111
|
-
|
|
162
|
+
function ComponentGallery() {
|
|
163
|
+
const { columns, examples, selectedExamplePath, random } = useComponentGalleryParams();
|
|
164
|
+
const ref = useScrollToExampleCard(selectedExamplePath, random);
|
|
165
|
+
return /* @__PURE__ */ jsx("div", { ref, className: styles$1.componentGallery, style: { "--columns": columns }, children: examples.map((example) => /* @__PURE__ */ jsx(ExampleCard, { example }, example.relativePath)) });
|
|
112
166
|
}
|
|
113
167
|
export {
|
|
114
|
-
|
|
168
|
+
ComponentGallery as default
|
|
115
169
|
};
|
package/dist/styles.css
CHANGED
|
@@ -1 +1,105 @@
|
|
|
1
|
-
.
|
|
1
|
+
.section {
|
|
2
|
+
margin-bottom: 32px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.section:last-child {
|
|
6
|
+
margin-bottom: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.sectionTitle {
|
|
10
|
+
margin-bottom: 16px;
|
|
11
|
+
font: bold 12px/1.5 system-ui;
|
|
12
|
+
color: light-dark(#666, #999);
|
|
13
|
+
text-transform: uppercase;
|
|
14
|
+
letter-spacing: 0.05em;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sectionContentColumn {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 16px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sectionContentRow {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: row;
|
|
26
|
+
flex-wrap: wrap;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 16px;
|
|
29
|
+
}
|
|
30
|
+
html,
|
|
31
|
+
body {
|
|
32
|
+
overscroll-behavior: none;
|
|
33
|
+
background-color: light-dark(#f3f3f3, #151515);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.componentGallery {
|
|
37
|
+
display: grid;
|
|
38
|
+
gap: 16px;
|
|
39
|
+
padding: 16px;
|
|
40
|
+
grid-template-columns: repeat(var(--columns, 1), minmax(0, 1fr));
|
|
41
|
+
|
|
42
|
+
& > * {
|
|
43
|
+
/** Give extra space to example cards when scrolling programmatically. */
|
|
44
|
+
scroll-margin: 16px;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
.exampleCard {
|
|
48
|
+
border-radius: 8px;
|
|
49
|
+
padding: 16px;
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
background-color: light-dark(#fff, #222);
|
|
53
|
+
overflow: auto;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.exampleCardHeader {
|
|
57
|
+
margin-bottom: 16px;
|
|
58
|
+
font: bold 12px/1.5 system-ui;
|
|
59
|
+
color: light-dark(#666, #999);
|
|
60
|
+
text-transform: uppercase;
|
|
61
|
+
letter-spacing: 0.05em;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.exampleCardContent {
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.exampleCardLoading {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
padding: 0 32px 32px 32px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.exampleCardSpinner {
|
|
75
|
+
width: 24px;
|
|
76
|
+
height: 24px;
|
|
77
|
+
border: 2px solid light-dark(#eee, #444);
|
|
78
|
+
border-top-color: light-dark(#999, #888);
|
|
79
|
+
border-radius: 50%;
|
|
80
|
+
animation: exampleCardSpin 1s linear infinite;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.exampleCardError {
|
|
84
|
+
padding: 32px 0;
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
gap: 8px;
|
|
88
|
+
text-align: center;
|
|
89
|
+
color: light-dark(#ce2c31, #ec5d5e);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.exampleCardErrorIcon {
|
|
93
|
+
font: 32px/1.5 system-ui;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.exampleCardErrorMessage {
|
|
97
|
+
font: 14px/1.5 system-ui;
|
|
98
|
+
overflow-wrap: break-word;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@keyframes exampleCardSpin {
|
|
102
|
+
to {
|
|
103
|
+
transform: rotate(360deg);
|
|
104
|
+
}
|
|
105
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dazl/component-gallery",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "View your components in a gallery inside Dazl",
|
|
5
5
|
"author": "Dazl",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"directory": "packages/component-gallery"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"zod": "
|
|
37
|
+
"zod": "^4.3.6"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": ">=19.0.0"
|
package/src/example/example.tsx
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
|
+
import { Children, isValidElement, type ReactNode } from 'react';
|
|
2
|
+
|
|
1
3
|
import styles from './example.module.css';
|
|
2
4
|
|
|
3
5
|
export interface SectionProps {
|
|
4
|
-
title?: string;
|
|
5
6
|
layout?: 'row' | 'column';
|
|
6
7
|
children: React.ReactNode;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export const Section = ({
|
|
10
|
+
export const Section = ({ layout = 'column', children }: SectionProps) => {
|
|
11
|
+
const { title, content } = extractTitle(children);
|
|
12
|
+
|
|
10
13
|
return (
|
|
11
14
|
<section className={styles.section}>
|
|
12
|
-
{title
|
|
15
|
+
{title}
|
|
13
16
|
<div className={layout === 'column' ? styles.sectionContentColumn : styles.sectionContentRow}>
|
|
14
|
-
{
|
|
17
|
+
{content}
|
|
15
18
|
</div>
|
|
16
19
|
</section>
|
|
17
20
|
);
|
|
18
21
|
};
|
|
22
|
+
|
|
23
|
+
const SectionTitle: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
24
|
+
<div className={styles.sectionTitle}>{children}</div>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
Section.Title = SectionTitle;
|
|
28
|
+
|
|
29
|
+
const extractTitle = (children: ReactNode): { title: ReactNode; content: ReactNode } => {
|
|
30
|
+
const [title, ...content] = Children.toArray(children);
|
|
31
|
+
return isValidElement(title) && title.type === SectionTitle
|
|
32
|
+
? { title, content }
|
|
33
|
+
: { title: null, content: children };
|
|
34
|
+
};
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
html,
|
|
2
|
+
body {
|
|
3
|
+
overscroll-behavior: none;
|
|
4
|
+
background-color: light-dark(#f3f3f3, #151515);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.componentGallery {
|
|
2
8
|
display: grid;
|
|
3
9
|
gap: 16px;
|
|
4
10
|
padding: 16px;
|
|
5
11
|
grid-template-columns: repeat(var(--columns, 1), minmax(0, 1fr));
|
|
6
|
-
}
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
& > * {
|
|
14
|
+
/** Give extra space to example cards when scrolling programmatically. */
|
|
15
|
+
scroll-margin: 16px;
|
|
16
|
+
}
|
|
10
17
|
}
|
|
@@ -9,7 +9,7 @@ export default function ComponentGallery() {
|
|
|
9
9
|
const ref = useScrollToExampleCard(selectedExamplePath, random);
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
|
-
<div ref={ref} className={styles.
|
|
12
|
+
<div ref={ref} className={styles.componentGallery} style={{ '--columns': columns } as CSSProperties}>
|
|
13
13
|
{examples.map((example) => (
|
|
14
14
|
<ExampleCard key={example.relativePath} example={example} />
|
|
15
15
|
))}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.
|
|
1
|
+
.exampleCard {
|
|
2
2
|
border-radius: 8px;
|
|
3
3
|
padding: 16px;
|
|
4
4
|
display: flex;
|
|
@@ -7,59 +7,53 @@
|
|
|
7
7
|
overflow: auto;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
.
|
|
10
|
+
.exampleCardHeader {
|
|
11
11
|
margin-bottom: 16px;
|
|
12
|
-
font
|
|
13
|
-
font-size: 12px;
|
|
12
|
+
font: bold 12px/1.5 system-ui;
|
|
14
13
|
color: light-dark(#666, #999);
|
|
15
14
|
text-transform: uppercase;
|
|
16
15
|
letter-spacing: 0.05em;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
.
|
|
20
|
-
flex: 1;
|
|
18
|
+
.exampleCardContent {
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
.
|
|
21
|
+
.exampleCardLoading {
|
|
24
22
|
display: flex;
|
|
25
23
|
align-items: center;
|
|
26
24
|
justify-content: center;
|
|
27
25
|
padding: 0 32px 32px 32px;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
.
|
|
28
|
+
.exampleCardSpinner {
|
|
31
29
|
width: 24px;
|
|
32
30
|
height: 24px;
|
|
33
31
|
border: 2px solid light-dark(#eee, #444);
|
|
34
32
|
border-top-color: light-dark(#999, #888);
|
|
35
33
|
border-radius: 50%;
|
|
36
|
-
animation:
|
|
34
|
+
animation: exampleCardSpin 1s linear infinite;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
transform: rotate(360deg);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.errorState {
|
|
46
|
-
padding: 32px;
|
|
37
|
+
.exampleCardError {
|
|
38
|
+
padding: 32px 0;
|
|
47
39
|
display: flex;
|
|
48
40
|
flex-direction: column;
|
|
49
|
-
align-items: center;
|
|
50
|
-
justify-content: center;
|
|
51
41
|
gap: 8px;
|
|
52
42
|
text-align: center;
|
|
53
43
|
color: light-dark(#ce2c31, #ec5d5e);
|
|
54
44
|
}
|
|
55
45
|
|
|
56
|
-
.
|
|
57
|
-
font
|
|
46
|
+
.exampleCardErrorIcon {
|
|
47
|
+
font: 32px/1.5 system-ui;
|
|
58
48
|
}
|
|
59
49
|
|
|
60
|
-
.
|
|
61
|
-
|
|
50
|
+
.exampleCardErrorMessage {
|
|
51
|
+
font: 14px/1.5 system-ui;
|
|
62
52
|
overflow-wrap: break-word;
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes exampleCardSpin {
|
|
56
|
+
to {
|
|
57
|
+
transform: rotate(360deg);
|
|
58
|
+
}
|
|
65
59
|
}
|
|
@@ -7,37 +7,37 @@ interface ExampleCardProps {
|
|
|
7
7
|
example: ComponentExampleInfo;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export const ExampleCard = memo(function
|
|
10
|
+
export const ExampleCard = memo(function Example({ example }: ExampleCardProps) {
|
|
11
11
|
const [Component] = useState(() => lazy(() => import(/* @vite-ignore */ `/${example.relativePath}`)));
|
|
12
12
|
const contentRef = useLinkBehaviorOverride();
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<div id={example.relativePath} className={styles.
|
|
16
|
-
<h3 className={styles.
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
<
|
|
15
|
+
<div id={example.relativePath} className={styles.exampleCard}>
|
|
16
|
+
<h3 className={styles.exampleCardHeader}>{example.displayName}</h3>
|
|
17
|
+
<ErrorBoundary fallback={(error) => <ExampleCardError error={error} />}>
|
|
18
|
+
<Suspense fallback={<ExampleCardLoading />}>
|
|
19
|
+
<div ref={contentRef} className={styles.exampleCardContent}>
|
|
20
20
|
<Component />
|
|
21
|
-
</
|
|
22
|
-
</
|
|
23
|
-
</
|
|
21
|
+
</div>
|
|
22
|
+
</Suspense>
|
|
23
|
+
</ErrorBoundary>
|
|
24
24
|
</div>
|
|
25
25
|
);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const ExampleCardLoading = () => {
|
|
29
29
|
return (
|
|
30
|
-
<div className={styles.
|
|
31
|
-
<div className={styles.
|
|
30
|
+
<div className={styles.exampleCardLoading}>
|
|
31
|
+
<div className={styles.exampleCardSpinner} />
|
|
32
32
|
</div>
|
|
33
33
|
);
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
const
|
|
36
|
+
const ExampleCardError = ({ error }: { error: Error }) => {
|
|
37
37
|
return (
|
|
38
|
-
<div className={styles.
|
|
39
|
-
<span className={styles.
|
|
40
|
-
<span className={styles.
|
|
38
|
+
<div className={styles.exampleCardError}>
|
|
39
|
+
<span className={styles.exampleCardErrorIcon}>⚠</span>
|
|
40
|
+
<span className={styles.exampleCardErrorMessage}>{error.message}</span>
|
|
41
41
|
</div>
|
|
42
42
|
);
|
|
43
43
|
};
|