@screenbook/ui 1.6.0 → 1.7.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/astro.config.mjs +20 -0
- package/dist/server/entry.mjs +1 -1
- package/dist/server/{manifest_bx_Ion-2.mjs → manifest_DHNGfdfn.mjs} +1 -1
- package/package.json +55 -51
- package/public/logo.svg +5 -0
- package/src/components/LinkIcon.astro +93 -0
- package/src/components/MockFormEditor.tsx +1280 -0
- package/src/components/MockPreview.astro +811 -0
- package/src/layouts/Layout.astro +123 -0
- package/src/pages/api/save-mock.ts +182 -0
- package/src/pages/coverage.astro +465 -0
- package/src/pages/editor.astro +33 -0
- package/src/pages/graph.astro +423 -0
- package/src/pages/impact.astro +555 -0
- package/src/pages/index.astro +227 -0
- package/src/pages/screen/[id].astro +299 -0
- package/src/styles/global.css +904 -0
- package/src/styles/mock-editor.css +1351 -0
- package/src/utils/impactAnalysis.ts +304 -0
- package/src/utils/loadCoverage.ts +30 -0
- package/src/utils/loadScreens.ts +18 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Layout from "@/layouts/Layout.astro"
|
|
3
|
+
import { loadScreens } from "@/utils/loadScreens"
|
|
4
|
+
|
|
5
|
+
const screens = loadScreens()
|
|
6
|
+
const allTags = [...new Set(screens.flatMap((s) => s.tags ?? []))]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<Layout title="Screens" currentPage="screens">
|
|
10
|
+
<div class="container">
|
|
11
|
+
<div class="page-header">
|
|
12
|
+
<h1 class="page-title">Screens</h1>
|
|
13
|
+
<p class="page-description">
|
|
14
|
+
Browse all screens in your application. Click on a screen to see details
|
|
15
|
+
and navigation flows.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
screens.length === 0 ? (
|
|
21
|
+
<div class="empty-state">
|
|
22
|
+
<svg
|
|
23
|
+
class="empty-state-icon"
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
fill="none"
|
|
26
|
+
viewBox="0 0 24 24"
|
|
27
|
+
stroke="currentColor"
|
|
28
|
+
stroke-width="1.5"
|
|
29
|
+
>
|
|
30
|
+
<path
|
|
31
|
+
stroke-linecap="round"
|
|
32
|
+
stroke-linejoin="round"
|
|
33
|
+
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
<h2 class="empty-state-title">No screens found</h2>
|
|
37
|
+
<p class="empty-state-description">
|
|
38
|
+
Get started by generating screen metadata from your routes.
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<div class="empty-state-steps">
|
|
42
|
+
<div class="empty-state-step">
|
|
43
|
+
<span class="step-number">1</span>
|
|
44
|
+
<div class="step-content">
|
|
45
|
+
<div class="step-title">Generate screen.meta.ts files</div>
|
|
46
|
+
<code class="step-code">screenbook generate</code>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="empty-state-step">
|
|
50
|
+
<span class="step-number">2</span>
|
|
51
|
+
<div class="step-content">
|
|
52
|
+
<div class="step-title">Build the screen catalog</div>
|
|
53
|
+
<code class="step-code">screenbook build</code>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="empty-state-step">
|
|
57
|
+
<span class="step-number">3</span>
|
|
58
|
+
<div class="step-content">
|
|
59
|
+
<div class="step-title">Restart the dev server</div>
|
|
60
|
+
<code class="step-code">screenbook dev</code>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="empty-state-features">
|
|
66
|
+
<p class="features-title">What you'll get:</p>
|
|
67
|
+
<ul class="features-list">
|
|
68
|
+
<li>Screen catalog with search & filter</li>
|
|
69
|
+
<li>Navigation graph visualization</li>
|
|
70
|
+
<li>Impact analysis for API changes</li>
|
|
71
|
+
<li>CI lint for documentation coverage</li>
|
|
72
|
+
</ul>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
) : (
|
|
76
|
+
<>
|
|
77
|
+
<div class="search-wrapper">
|
|
78
|
+
<svg
|
|
79
|
+
class="search-icon"
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
fill="none"
|
|
82
|
+
viewBox="0 0 24 24"
|
|
83
|
+
stroke="currentColor"
|
|
84
|
+
stroke-width="2"
|
|
85
|
+
>
|
|
86
|
+
<path
|
|
87
|
+
stroke-linecap="round"
|
|
88
|
+
stroke-linejoin="round"
|
|
89
|
+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
90
|
+
/>
|
|
91
|
+
</svg>
|
|
92
|
+
<label for="search" class="sr-only">
|
|
93
|
+
Search screens
|
|
94
|
+
</label>
|
|
95
|
+
<input
|
|
96
|
+
type="text"
|
|
97
|
+
id="search"
|
|
98
|
+
placeholder="Search screens..."
|
|
99
|
+
class="search-input"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{allTags.length > 0 && (
|
|
104
|
+
<div class="tags" role="group" aria-label="Filter by tag">
|
|
105
|
+
{allTags.map((tag) => (
|
|
106
|
+
<button class="tag" data-tag={tag} aria-pressed="false">
|
|
107
|
+
{tag}
|
|
108
|
+
</button>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
<h2 class="sr-only">All Screens</h2>
|
|
114
|
+
<div class="screen-grid" id="screen-list" aria-live="polite">
|
|
115
|
+
{screens.map((screen) => (
|
|
116
|
+
<a
|
|
117
|
+
href={`/screen/${screen.id}`}
|
|
118
|
+
class="card-link"
|
|
119
|
+
data-tags={screen.tags?.join(",") ?? ""}
|
|
120
|
+
data-title={screen.title.toLowerCase()}
|
|
121
|
+
data-id={screen.id.toLowerCase()}
|
|
122
|
+
>
|
|
123
|
+
<div class="card">
|
|
124
|
+
<h3 class="card-title">{screen.title}</h3>
|
|
125
|
+
<div class="card-route">{screen.route}</div>
|
|
126
|
+
<div class="card-meta">
|
|
127
|
+
<span class="card-meta-item">
|
|
128
|
+
<svg
|
|
129
|
+
aria-hidden="true"
|
|
130
|
+
fill="none"
|
|
131
|
+
viewBox="0 0 24 24"
|
|
132
|
+
stroke="currentColor"
|
|
133
|
+
stroke-width="2"
|
|
134
|
+
>
|
|
135
|
+
<path
|
|
136
|
+
stroke-linecap="round"
|
|
137
|
+
stroke-linejoin="round"
|
|
138
|
+
d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5l-3.9 19.5m-2.1-19.5l-3.9 19.5"
|
|
139
|
+
/>
|
|
140
|
+
</svg>
|
|
141
|
+
{screen.id}
|
|
142
|
+
</span>
|
|
143
|
+
{screen.owner && screen.owner.length > 0 && (
|
|
144
|
+
<span class="card-meta-item">
|
|
145
|
+
<svg
|
|
146
|
+
aria-hidden="true"
|
|
147
|
+
fill="none"
|
|
148
|
+
viewBox="0 0 24 24"
|
|
149
|
+
stroke="currentColor"
|
|
150
|
+
stroke-width="2"
|
|
151
|
+
>
|
|
152
|
+
<path
|
|
153
|
+
stroke-linecap="round"
|
|
154
|
+
stroke-linejoin="round"
|
|
155
|
+
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"
|
|
156
|
+
/>
|
|
157
|
+
</svg>
|
|
158
|
+
{screen.owner.join(", ")}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
{screen.tags && screen.tags.length > 0 && (
|
|
163
|
+
<div class="card-tags">
|
|
164
|
+
{screen.tags.map((tag) => (
|
|
165
|
+
<span class="card-tag">{tag}</span>
|
|
166
|
+
))}
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
</a>
|
|
171
|
+
))}
|
|
172
|
+
</div>
|
|
173
|
+
</>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<script>
|
|
179
|
+
const searchInput = document.getElementById("search") as HTMLInputElement
|
|
180
|
+
const screenList = document.getElementById("screen-list")
|
|
181
|
+
const tagButtons = document.querySelectorAll(".tag")
|
|
182
|
+
|
|
183
|
+
let activeTag: string | null = null
|
|
184
|
+
|
|
185
|
+
function filterScreens() {
|
|
186
|
+
const query = searchInput?.value.toLowerCase() ?? ""
|
|
187
|
+
const cards = screenList?.querySelectorAll(
|
|
188
|
+
".card-link",
|
|
189
|
+
) as NodeListOf<HTMLElement>
|
|
190
|
+
|
|
191
|
+
cards?.forEach((card) => {
|
|
192
|
+
const title = card.dataset.title ?? ""
|
|
193
|
+
const id = card.dataset.id ?? ""
|
|
194
|
+
const tags = card.dataset.tags ?? ""
|
|
195
|
+
|
|
196
|
+
const matchesSearch = title.includes(query) || id.includes(query)
|
|
197
|
+
const matchesTag = !activeTag || tags.includes(activeTag)
|
|
198
|
+
|
|
199
|
+
card.style.display = matchesSearch && matchesTag ? "block" : "none"
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
searchInput?.addEventListener("input", filterScreens)
|
|
204
|
+
|
|
205
|
+
tagButtons.forEach((btn) => {
|
|
206
|
+
btn.addEventListener("click", () => {
|
|
207
|
+
const tag = (btn as HTMLElement).dataset.tag
|
|
208
|
+
if (activeTag === tag) {
|
|
209
|
+
activeTag = null
|
|
210
|
+
tagButtons.forEach((b) => {
|
|
211
|
+
b.classList.remove("active")
|
|
212
|
+
b.setAttribute("aria-pressed", "false")
|
|
213
|
+
})
|
|
214
|
+
} else {
|
|
215
|
+
tagButtons.forEach((b) => {
|
|
216
|
+
b.classList.remove("active")
|
|
217
|
+
b.setAttribute("aria-pressed", "false")
|
|
218
|
+
})
|
|
219
|
+
activeTag = tag ?? null
|
|
220
|
+
btn.classList.add("active")
|
|
221
|
+
btn.setAttribute("aria-pressed", "true")
|
|
222
|
+
}
|
|
223
|
+
filterScreens()
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
</script>
|
|
227
|
+
</Layout>
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Layout from "@/layouts/Layout.astro"
|
|
3
|
+
import LinkIcon from "@/components/LinkIcon.astro"
|
|
4
|
+
import MockPreview from "@/components/MockPreview.astro"
|
|
5
|
+
import { loadScreens } from "@/utils/loadScreens"
|
|
6
|
+
import { getApiDependencyCount } from "@/utils/impactAnalysis"
|
|
7
|
+
|
|
8
|
+
const screens = loadScreens()
|
|
9
|
+
const apiCounts = getApiDependencyCount(screens)
|
|
10
|
+
|
|
11
|
+
export function getStaticPaths() {
|
|
12
|
+
const screens = loadScreens()
|
|
13
|
+
return screens.map((screen) => ({
|
|
14
|
+
params: { id: screen.id },
|
|
15
|
+
}))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { id } = Astro.params
|
|
19
|
+
const screen = screens.find((s) => s.id === id)
|
|
20
|
+
|
|
21
|
+
if (!screen) {
|
|
22
|
+
return Astro.redirect("/")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Find related screens
|
|
26
|
+
const entryScreens = screens.filter((s) => screen.entryPoints?.includes(s.id))
|
|
27
|
+
const nextScreens = screens.filter((s) => screen.next?.includes(s.id))
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<Layout title={screen.title}>
|
|
31
|
+
<div class="container">
|
|
32
|
+
<a href="/" class="back-link">
|
|
33
|
+
<svg
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
fill="none"
|
|
36
|
+
viewBox="0 0 24 24"
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
stroke-width="2"
|
|
39
|
+
>
|
|
40
|
+
<path
|
|
41
|
+
stroke-linecap="round"
|
|
42
|
+
stroke-linejoin="round"
|
|
43
|
+
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"></path>
|
|
44
|
+
</svg>
|
|
45
|
+
Back to screens
|
|
46
|
+
</a>
|
|
47
|
+
|
|
48
|
+
<div class="detail-header">
|
|
49
|
+
<h1 class="detail-title">{screen.title}</h1>
|
|
50
|
+
<div class="detail-route">
|
|
51
|
+
<svg
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
fill="none"
|
|
54
|
+
viewBox="0 0 24 24"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
stroke-width="2"
|
|
57
|
+
>
|
|
58
|
+
<path
|
|
59
|
+
stroke-linecap="round"
|
|
60
|
+
stroke-linejoin="round"
|
|
61
|
+
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
|
|
62
|
+
></path>
|
|
63
|
+
</svg>
|
|
64
|
+
{screen.route}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="detail-content">
|
|
69
|
+
<div>
|
|
70
|
+
<!-- Information -->
|
|
71
|
+
<div class="section">
|
|
72
|
+
<h2 class="section-title">Information</h2>
|
|
73
|
+
<div class="info-list">
|
|
74
|
+
<div class="info-item">
|
|
75
|
+
<span class="info-label">ID</span>
|
|
76
|
+
<span class="info-value"><code>{screen.id}</code></span>
|
|
77
|
+
</div>
|
|
78
|
+
{
|
|
79
|
+
screen.owner && screen.owner.length > 0 && (
|
|
80
|
+
<div class="info-item">
|
|
81
|
+
<span class="info-label">Owner</span>
|
|
82
|
+
<span class="info-value">{screen.owner.join(", ")}</span>
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
{
|
|
87
|
+
screen.tags && screen.tags.length > 0 && (
|
|
88
|
+
<div class="info-item">
|
|
89
|
+
<span class="info-label">Tags</span>
|
|
90
|
+
<span class="info-value">
|
|
91
|
+
<div class="card-tags">
|
|
92
|
+
{screen.tags.map((tag) => (
|
|
93
|
+
<span class="card-tag">{tag}</span>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
</span>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
{
|
|
101
|
+
screen.description && (
|
|
102
|
+
<div class="info-item">
|
|
103
|
+
<span class="info-label">Description</span>
|
|
104
|
+
<span class="info-value">{screen.description}</span>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Dependencies -->
|
|
112
|
+
{
|
|
113
|
+
screen.dependsOn && screen.dependsOn.length > 0 && (
|
|
114
|
+
<div class="section">
|
|
115
|
+
<h2 class="section-title">Dependencies</h2>
|
|
116
|
+
<div class="dep-list">
|
|
117
|
+
{screen.dependsOn.map((dep) => {
|
|
118
|
+
const count = apiCounts.get(dep) || 1
|
|
119
|
+
const otherCount = count - 1
|
|
120
|
+
return (
|
|
121
|
+
<a
|
|
122
|
+
href={`/impact?api=${encodeURIComponent(dep)}`}
|
|
123
|
+
class="dep-item dep-item-link"
|
|
124
|
+
>
|
|
125
|
+
<div class="dep-item-main">
|
|
126
|
+
<svg
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
fill="none"
|
|
129
|
+
viewBox="0 0 24 24"
|
|
130
|
+
stroke="currentColor"
|
|
131
|
+
stroke-width="2"
|
|
132
|
+
>
|
|
133
|
+
<path
|
|
134
|
+
stroke-linecap="round"
|
|
135
|
+
stroke-linejoin="round"
|
|
136
|
+
d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"
|
|
137
|
+
/>
|
|
138
|
+
</svg>
|
|
139
|
+
<span>{dep}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="dep-item-meta">
|
|
142
|
+
{otherCount > 0 && (
|
|
143
|
+
<span class="dep-count">
|
|
144
|
+
+{otherCount} other{otherCount > 1 ? "s" : ""}
|
|
145
|
+
</span>
|
|
146
|
+
)}
|
|
147
|
+
<svg
|
|
148
|
+
class="dep-arrow"
|
|
149
|
+
fill="none"
|
|
150
|
+
viewBox="0 0 24 24"
|
|
151
|
+
stroke="currentColor"
|
|
152
|
+
stroke-width="2"
|
|
153
|
+
>
|
|
154
|
+
<path
|
|
155
|
+
stroke-linecap="round"
|
|
156
|
+
stroke-linejoin="round"
|
|
157
|
+
d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z"
|
|
158
|
+
/>
|
|
159
|
+
</svg>
|
|
160
|
+
</div>
|
|
161
|
+
</a>
|
|
162
|
+
)
|
|
163
|
+
})}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
<!-- External Links -->
|
|
170
|
+
{
|
|
171
|
+
screen.links && screen.links.length > 0 && (
|
|
172
|
+
<div class="section">
|
|
173
|
+
<h2 class="section-title">Links</h2>
|
|
174
|
+
<div class="dep-list">
|
|
175
|
+
{screen.links.map((link) => (
|
|
176
|
+
<a
|
|
177
|
+
href={link.url}
|
|
178
|
+
target="_blank"
|
|
179
|
+
rel="noopener noreferrer"
|
|
180
|
+
class="dep-item"
|
|
181
|
+
>
|
|
182
|
+
<LinkIcon type={link.type} url={link.url} />
|
|
183
|
+
{link.label}
|
|
184
|
+
</a>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
<!-- Mock Preview -->
|
|
192
|
+
{
|
|
193
|
+
screen.mock && (
|
|
194
|
+
<div class="section">
|
|
195
|
+
<h2 class="section-title">UI Wireframe</h2>
|
|
196
|
+
<MockPreview mock={screen.mock} screenId={screen.id} />
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div>
|
|
203
|
+
<!-- Entry Points -->
|
|
204
|
+
{
|
|
205
|
+
entryScreens.length > 0 && (
|
|
206
|
+
<div class="sidebar-card">
|
|
207
|
+
<h3 class="sidebar-card-title">
|
|
208
|
+
<svg
|
|
209
|
+
aria-hidden="true"
|
|
210
|
+
fill="none"
|
|
211
|
+
viewBox="0 0 24 24"
|
|
212
|
+
stroke="currentColor"
|
|
213
|
+
stroke-width="2"
|
|
214
|
+
>
|
|
215
|
+
<path
|
|
216
|
+
stroke-linecap="round"
|
|
217
|
+
stroke-linejoin="round"
|
|
218
|
+
d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
|
|
219
|
+
/>
|
|
220
|
+
</svg>
|
|
221
|
+
Entry Points
|
|
222
|
+
</h3>
|
|
223
|
+
<div class="screen-link-list">
|
|
224
|
+
{entryScreens.map((s) => (
|
|
225
|
+
<a href={`/screen/${s.id}`} class="screen-link">
|
|
226
|
+
<div class="screen-link-info">
|
|
227
|
+
<span class="screen-link-title">{s.title}</span>
|
|
228
|
+
<span class="screen-link-id">{s.id}</span>
|
|
229
|
+
</div>
|
|
230
|
+
<svg
|
|
231
|
+
class="screen-link-arrow"
|
|
232
|
+
fill="none"
|
|
233
|
+
viewBox="0 0 24 24"
|
|
234
|
+
stroke="currentColor"
|
|
235
|
+
stroke-width="2"
|
|
236
|
+
>
|
|
237
|
+
<path
|
|
238
|
+
stroke-linecap="round"
|
|
239
|
+
stroke-linejoin="round"
|
|
240
|
+
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
241
|
+
/>
|
|
242
|
+
</svg>
|
|
243
|
+
</a>
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
<!-- Next Screens -->
|
|
251
|
+
{
|
|
252
|
+
nextScreens.length > 0 && (
|
|
253
|
+
<div class="sidebar-card">
|
|
254
|
+
<h3 class="sidebar-card-title">
|
|
255
|
+
<svg
|
|
256
|
+
aria-hidden="true"
|
|
257
|
+
fill="none"
|
|
258
|
+
viewBox="0 0 24 24"
|
|
259
|
+
stroke="currentColor"
|
|
260
|
+
stroke-width="2"
|
|
261
|
+
>
|
|
262
|
+
<path
|
|
263
|
+
stroke-linecap="round"
|
|
264
|
+
stroke-linejoin="round"
|
|
265
|
+
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
|
|
266
|
+
/>
|
|
267
|
+
</svg>
|
|
268
|
+
Next Screens
|
|
269
|
+
</h3>
|
|
270
|
+
<div class="screen-link-list">
|
|
271
|
+
{nextScreens.map((s) => (
|
|
272
|
+
<a href={`/screen/${s.id}`} class="screen-link">
|
|
273
|
+
<div class="screen-link-info">
|
|
274
|
+
<span class="screen-link-title">{s.title}</span>
|
|
275
|
+
<span class="screen-link-id">{s.id}</span>
|
|
276
|
+
</div>
|
|
277
|
+
<svg
|
|
278
|
+
class="screen-link-arrow"
|
|
279
|
+
fill="none"
|
|
280
|
+
viewBox="0 0 24 24"
|
|
281
|
+
stroke="currentColor"
|
|
282
|
+
stroke-width="2"
|
|
283
|
+
>
|
|
284
|
+
<path
|
|
285
|
+
stroke-linecap="round"
|
|
286
|
+
stroke-linejoin="round"
|
|
287
|
+
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
288
|
+
/>
|
|
289
|
+
</svg>
|
|
290
|
+
</a>
|
|
291
|
+
))}
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</Layout>
|