@dfosco/storyboard-react 1.15.2 → 1.16.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/package.json +1 -1
- package/src/Viewfinder.jsx +48 -24
- package/src/Viewfinder.module.css +101 -34
package/package.json
CHANGED
package/src/Viewfinder.jsx
CHANGED
|
@@ -90,8 +90,10 @@ function getCurrentBranch(basePath) {
|
|
|
90
90
|
* @param {Record<string, unknown>} props.pageModules - import.meta.glob result for page files
|
|
91
91
|
* @param {string} [props.basePath] - Base URL path (defaults to import.meta.env.BASE_URL)
|
|
92
92
|
* @param {string} [props.title] - Header title (defaults to "Viewfinder")
|
|
93
|
+
* @param {string} [props.subtitle] - Optional subtitle displayed below the title
|
|
94
|
+
* @param {boolean} [props.showThumbnails] - Show thumbnail previews (defaults to false)
|
|
93
95
|
*/
|
|
94
|
-
export default function Viewfinder({ scenes = {}, pageModules = {}, basePath, title = 'Viewfinder' }) {
|
|
96
|
+
export default function Viewfinder({ scenes = {}, pageModules = {}, basePath, title = 'Viewfinder', subtitle, showThumbnails = false }) {
|
|
95
97
|
const [branches, setBranches] = useState(null)
|
|
96
98
|
|
|
97
99
|
const sceneNames = useMemo(() => Object.keys(scenes), [scenes])
|
|
@@ -110,13 +112,19 @@ export default function Viewfinder({ scenes = {}, pageModules = {}, basePath, ti
|
|
|
110
112
|
|
|
111
113
|
const currentBranch = useMemo(() => getCurrentBranch(basePath), [basePath])
|
|
112
114
|
|
|
115
|
+
const MOCK_BRANCHES = useMemo(() => [
|
|
116
|
+
{ branch: 'main', folder: '' },
|
|
117
|
+
{ branch: 'feat/comments-v2', folder: 'branch--feat-comments-v2' },
|
|
118
|
+
{ branch: 'fix/nav-overflow', folder: 'branch--fix-nav-overflow' },
|
|
119
|
+
], [])
|
|
120
|
+
|
|
113
121
|
useEffect(() => {
|
|
114
122
|
const url = `${branchBasePath}branches.json`
|
|
115
123
|
fetch(url)
|
|
116
|
-
.then(r => r.ok ? r.json() :
|
|
117
|
-
.then(data => setBranches(Array.isArray(data) ? data :
|
|
118
|
-
.catch(() => setBranches(
|
|
119
|
-
}, [branchBasePath])
|
|
124
|
+
.then(r => r.ok ? r.json() : null)
|
|
125
|
+
.then(data => setBranches(Array.isArray(data) && data.length > 0 ? data : MOCK_BRANCHES))
|
|
126
|
+
.catch(() => setBranches(MOCK_BRANCHES))
|
|
127
|
+
}, [branchBasePath, MOCK_BRANCHES])
|
|
120
128
|
|
|
121
129
|
const handleBranchChange = (e) => {
|
|
122
130
|
const folder = e.target.value
|
|
@@ -129,15 +137,21 @@ export default function Viewfinder({ scenes = {}, pageModules = {}, basePath, ti
|
|
|
129
137
|
<div className={styles.container}>
|
|
130
138
|
<header className={styles.header}>
|
|
131
139
|
<div className={styles.headerTop}>
|
|
132
|
-
<
|
|
140
|
+
<div>
|
|
141
|
+
<h1 className={styles.title}>{title}</h1>
|
|
142
|
+
{subtitle && <p className={styles.subtitle}>{subtitle}</p>}
|
|
143
|
+
</div>
|
|
133
144
|
{branches && branches.length > 0 && (
|
|
134
145
|
<div className={styles.branchDropdown}>
|
|
135
|
-
<
|
|
146
|
+
<svg className={styles.branchIcon} width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
147
|
+
<path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.492 2.492 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z" />
|
|
148
|
+
</svg>
|
|
136
149
|
<select
|
|
137
150
|
id="branch-select"
|
|
138
151
|
className={styles.branchSelect}
|
|
139
152
|
defaultValue=""
|
|
140
153
|
onChange={handleBranchChange}
|
|
154
|
+
aria-label="Switch branch"
|
|
141
155
|
>
|
|
142
156
|
<option value="" disabled>{currentBranch}</option>
|
|
143
157
|
{branches.map((b) => (
|
|
@@ -149,7 +163,7 @@ export default function Viewfinder({ scenes = {}, pageModules = {}, basePath, ti
|
|
|
149
163
|
</div>
|
|
150
164
|
)}
|
|
151
165
|
</div>
|
|
152
|
-
<p className={styles.
|
|
166
|
+
<p className={styles.sceneCount}>
|
|
153
167
|
{sceneNames.length} scene{sceneNames.length !== 1 ? 's' : ''}
|
|
154
168
|
</p>
|
|
155
169
|
</header>
|
|
@@ -158,27 +172,37 @@ export default function Viewfinder({ scenes = {}, pageModules = {}, basePath, ti
|
|
|
158
172
|
<p className={styles.empty}>No scenes found. Add a <code>*.scene.json</code> file to get started.</p>
|
|
159
173
|
) : (
|
|
160
174
|
<section>
|
|
161
|
-
<h2 className={styles.sectionTitle}>Scenes</h2>
|
|
162
|
-
<div className={styles.grid}>
|
|
175
|
+
{/* <h2 className={styles.sectionTitle}>Scenes</h2> */}
|
|
176
|
+
<div className={showThumbnails ? styles.grid : styles.list}>
|
|
163
177
|
{sceneNames.map((name) => {
|
|
164
178
|
const meta = getSceneMeta(name)
|
|
165
179
|
return (
|
|
166
|
-
<a key={name} href={resolveSceneRoute(name, knownRoutes)} className={styles.card}>
|
|
167
|
-
|
|
168
|
-
<
|
|
169
|
-
|
|
180
|
+
<a key={name} href={resolveSceneRoute(name, knownRoutes)} className={showThumbnails ? styles.card : styles.listItem}>
|
|
181
|
+
{showThumbnails && (
|
|
182
|
+
<div className={styles.thumbnail}>
|
|
183
|
+
<PlaceholderGraphic name={name} />
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
170
186
|
<div className={styles.cardBody}>
|
|
171
187
|
<p className={styles.sceneName}>{name}</p>
|
|
172
|
-
{meta?.author && (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
{meta?.author && (() => {
|
|
189
|
+
const authors = Array.isArray(meta.author) ? meta.author : [meta.author]
|
|
190
|
+
return (
|
|
191
|
+
<div className={styles.author}>
|
|
192
|
+
<span className={styles.authorAvatars}>
|
|
193
|
+
{authors.map((a) => (
|
|
194
|
+
<img
|
|
195
|
+
key={a}
|
|
196
|
+
src={`https://github.com/${a}.png?size=32`}
|
|
197
|
+
alt={a}
|
|
198
|
+
className={styles.authorAvatar}
|
|
199
|
+
/>
|
|
200
|
+
))}
|
|
201
|
+
</span>
|
|
202
|
+
<span className={styles.authorName}>{authors.join(', ')}</span>
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
})()}
|
|
182
206
|
</div>
|
|
183
207
|
</a>
|
|
184
208
|
)
|
|
@@ -2,36 +2,72 @@
|
|
|
2
2
|
min-height: 100vh;
|
|
3
3
|
background-color: var(--bgColor-default, #0d1117);
|
|
4
4
|
color: var(--fgColor-default, #e6edf3);
|
|
5
|
-
padding:
|
|
5
|
+
padding: 80px 32px 48px;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
.header {
|
|
9
|
-
max-width:
|
|
10
|
-
margin: 0 auto
|
|
9
|
+
max-width: 720px;
|
|
10
|
+
margin: 0 auto 64px;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.title {
|
|
14
|
-
font-size:
|
|
15
|
-
font-weight:
|
|
16
|
-
margin: 0 0
|
|
14
|
+
font-size: 72px;
|
|
15
|
+
font-weight: 400;
|
|
16
|
+
margin: 0 0 12px;
|
|
17
17
|
color: var(--fgColor-default, #e6edf3);
|
|
18
|
-
letter-spacing: -0.
|
|
18
|
+
letter-spacing: -0.03em;
|
|
19
|
+
line-height: 1;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
.subtitle {
|
|
22
|
-
font-size:
|
|
23
|
+
font-size: 15px;
|
|
23
24
|
color: var(--fgColor-muted, #848d97);
|
|
24
|
-
margin: 0;
|
|
25
|
+
margin: 4px 0 0;
|
|
26
|
+
letter-spacing: 0.01em;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sceneCount {
|
|
30
|
+
font-size: 13px;
|
|
31
|
+
color: var(--fgColor-muted, #848d97);
|
|
32
|
+
margin: 16px 0 0;
|
|
33
|
+
letter-spacing: 0.01em;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
.grid {
|
|
28
37
|
display: grid;
|
|
29
38
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
30
39
|
gap: 16px;
|
|
31
|
-
max-width:
|
|
40
|
+
max-width: 720px;
|
|
41
|
+
margin: 0 auto;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.list {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
max-width: 720px;
|
|
32
48
|
margin: 0 auto;
|
|
33
49
|
}
|
|
34
50
|
|
|
51
|
+
.listItem {
|
|
52
|
+
display: block;
|
|
53
|
+
padding: 8px 0;
|
|
54
|
+
text-decoration: none;
|
|
55
|
+
color: inherit;
|
|
56
|
+
/* border-bottom: 1px solid var(--borderColor-muted, #21262d); */
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.listItem:first-child {
|
|
60
|
+
/* border-top: 1px solid var(--borderColor-muted, #21262d); */
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.listItem:hover {
|
|
64
|
+
text-decoration: none !important;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.listItem .author {
|
|
68
|
+
margin-top: 8px;
|
|
69
|
+
}
|
|
70
|
+
|
|
35
71
|
.card {
|
|
36
72
|
display: block;
|
|
37
73
|
border: 1px solid var(--borderColor-default, #30363d);
|
|
@@ -71,33 +107,39 @@
|
|
|
71
107
|
|
|
72
108
|
.cardBody {
|
|
73
109
|
padding: 12px 16px;
|
|
74
|
-
|
|
110
|
+
|
|
111
|
+
&:hover {
|
|
112
|
+
background-color: var(--bgColor-muted);
|
|
113
|
+
}
|
|
75
114
|
}
|
|
76
115
|
|
|
77
116
|
.sceneName {
|
|
78
|
-
font-size:
|
|
79
|
-
font-weight:
|
|
117
|
+
font-size: 28px;
|
|
118
|
+
font-weight: 400;
|
|
80
119
|
color: var(--fgColor-default, #e6edf3);
|
|
81
120
|
margin: 0;
|
|
121
|
+
letter-spacing: -0.02em;
|
|
122
|
+
line-height: 1.2;
|
|
123
|
+
transition: font-style 0.15s ease;
|
|
82
124
|
}
|
|
83
125
|
|
|
84
126
|
.empty {
|
|
85
127
|
text-align: center;
|
|
86
128
|
padding: 80px 24px;
|
|
87
129
|
color: var(--fgColor-muted, #848d97);
|
|
88
|
-
font-size:
|
|
89
|
-
max-width:
|
|
130
|
+
font-size: 15px;
|
|
131
|
+
max-width: 720px;
|
|
90
132
|
margin: 0 auto;
|
|
91
133
|
}
|
|
92
134
|
|
|
93
135
|
.sectionTitle {
|
|
94
|
-
font-size:
|
|
95
|
-
font-weight:
|
|
136
|
+
font-size: 11px;
|
|
137
|
+
font-weight: 700;
|
|
96
138
|
text-transform: uppercase;
|
|
97
|
-
letter-spacing: 0.
|
|
139
|
+
letter-spacing: 0.12em;
|
|
98
140
|
color: var(--fgColor-muted, #848d97);
|
|
99
|
-
margin: 0 auto
|
|
100
|
-
max-width:
|
|
141
|
+
margin: 0 auto 20px;
|
|
142
|
+
max-width: 720px;
|
|
101
143
|
}
|
|
102
144
|
|
|
103
145
|
.headerTop {
|
|
@@ -110,35 +152,38 @@
|
|
|
110
152
|
.branchDropdown {
|
|
111
153
|
display: flex;
|
|
112
154
|
align-items: center;
|
|
113
|
-
gap:
|
|
155
|
+
gap: 0;
|
|
114
156
|
flex-shrink: 0;
|
|
157
|
+
position: relative;
|
|
115
158
|
}
|
|
116
159
|
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
.branchIcon {
|
|
161
|
+
position: absolute;
|
|
162
|
+
left: 10px;
|
|
120
163
|
color: var(--fgColor-muted, #848d97);
|
|
121
|
-
|
|
164
|
+
pointer-events: none;
|
|
165
|
+
z-index: 1;
|
|
122
166
|
}
|
|
123
167
|
|
|
124
168
|
.branchSelect {
|
|
125
169
|
appearance: none;
|
|
126
|
-
background-color:
|
|
170
|
+
background-color: transparent;
|
|
127
171
|
color: var(--fgColor-default, #e6edf3);
|
|
128
172
|
border: 1px solid var(--borderColor-default, #30363d);
|
|
129
|
-
border-radius:
|
|
130
|
-
padding:
|
|
173
|
+
border-radius: 20px;
|
|
174
|
+
padding: 6px 32px 6px 32px;
|
|
131
175
|
font-size: 13px;
|
|
132
176
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
133
177
|
cursor: pointer;
|
|
134
178
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23848d97'%3E%3Cpath d='M6 8.5L1.5 4h9L6 8.5z'/%3E%3C/svg%3E");
|
|
135
179
|
background-repeat: no-repeat;
|
|
136
|
-
background-position: right
|
|
180
|
+
background-position: right 12px center;
|
|
137
181
|
min-width: 140px;
|
|
182
|
+
transition: border-color 0.15s ease;
|
|
138
183
|
}
|
|
139
184
|
|
|
140
185
|
.branchSelect:hover {
|
|
141
|
-
border-color: var(--
|
|
186
|
+
border-color: var(--fgColor-muted, #848d97);
|
|
142
187
|
}
|
|
143
188
|
|
|
144
189
|
.branchSelect:focus-visible {
|
|
@@ -149,17 +194,39 @@
|
|
|
149
194
|
.author {
|
|
150
195
|
display: flex;
|
|
151
196
|
align-items: center;
|
|
152
|
-
gap:
|
|
197
|
+
gap: 8px;
|
|
153
198
|
margin-top: 6px;
|
|
154
199
|
}
|
|
155
200
|
|
|
201
|
+
.authorAvatars {
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: row;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.authorAvatars:hover .authorAvatar {
|
|
207
|
+
margin-left: 2px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.authorAvatars:hover .authorAvatar:first-child {
|
|
211
|
+
margin-left: 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
156
214
|
.authorAvatar {
|
|
157
|
-
width:
|
|
158
|
-
height:
|
|
215
|
+
width: 18px;
|
|
216
|
+
height: 18px;
|
|
159
217
|
border-radius: 50%;
|
|
218
|
+
margin-left: -6px;
|
|
219
|
+
transition: margin-left 0.15s ease;
|
|
220
|
+
outline: 2px solid var(--bgColor-default, #0d1117);
|
|
221
|
+
position: relative;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.authorAvatar:first-child {
|
|
225
|
+
margin-left: 0;
|
|
160
226
|
}
|
|
161
227
|
|
|
162
228
|
.authorName {
|
|
163
|
-
font-size:
|
|
229
|
+
font-size: 13px;
|
|
164
230
|
color: var(--fgColor-muted, #848d97);
|
|
231
|
+
letter-spacing: 0.01em;
|
|
165
232
|
}
|