Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mastodon
GitHub Repository: mastodon/joinmastodon
Path: blob/main/pages/guide.tsx
1006 views
1
import app_hero_planets from "../public/illustrations/app_hero_planets.png"
2
import app_hero_festival from "../public/illustrations/app_hero_festival.png"
3
import { withDefaultStaticProps } from "../utils/defaultStaticProps"
4
import { IconCard } from "../components/IconCard"
5
import SelectMenu from "../components/SelectMenu"
6
import { FormattedMessage, useIntl } from "react-intl"
7
import AppHero from "../components/AppHero"
8
import React, { useState } from "react"
9
import Image from "next/legacy/image"
10
import Hero from "../components/Hero"
11
import LinkButton from "../components/LinkButton"
12
import { theme, safelist } from "../tailwind.config.js"
13
import { AppCard } from "../components/AppCard"
14
import Layout from "../components/Layout"
15
16
import tusky from "../public/apps/tusky.png"
17
import SkeletonText from "../components/SkeletonText"
18
import ServerCard from "../components/ServerCard"
19
import classNames from "classnames"
20
import TwoUpFeature from "../components/TwoUpFeature"
21
22
import ProgressiveWebIcon from "../public/icons/progressive-web.svg?inline"
23
import ApiGearIcon from "../public/icons/api-gear.svg?inline"
24
25
const GuideSection = ({
26
title,
27
controls,
28
children,
29
}: {
30
title: React.ReactNode
31
controls?: React.ReactNode
32
children: React.ReactNode
33
}) => {
34
return (
35
<section className="">
36
<h2 className="h5 mb-4">{title}</h2>
37
{controls && (
38
<div className="mb-4 flex min-h-[3rem] items-center rounded border border-gray-3 bg-gray-5 px-4">
39
{controls}
40
</div>
41
)}
42
{children}
43
</section>
44
)
45
}
46
47
/** This page does not require translations */
48
function Guide(props) {
49
const intl = useIntl()
50
const [altAppHero, setAltAppHero] = useState(false)
51
const [skeletonTextSize, setSkeletonTextSize] = useState("b2")
52
const [serverCardLoading, setServerCardLoading] = useState(false)
53
54
return (
55
<Layout>
56
<Hero>
57
<h1 className="h1 mb-4">Style Guide</h1>
58
<p className="sh1">
59
A reference of components and design elements, along with their usage
60
</p>
61
</Hero>
62
63
<div className="mt-16 flex flex-col gap-16">
64
<h2 className="h2">Styles</h2>
65
66
<GuideSection title="Type Scale">
67
{Object.keys(theme.fontSize).map((name) => (
68
<div key={name} className="flex items-baseline gap-4">
69
<div className="b4 flex-0 w-4">{name}</div>
70
<div className={name}>Find your perfect community</div>
71
</div>
72
))}
73
</GuideSection>
74
<GuideSection title="Colors">
75
<div className="grid grid-cols-1 gap-8">
76
{Object.keys(theme.colors).map((color) => (
77
<div key={color}>
78
<div className="flex space-x-4">
79
<div className="w-24 shrink-0">
80
<div className="flex h-10 flex-col justify-center">
81
<div className="font-semibold">{color}</div>
82
</div>
83
</div>
84
85
<div className="flex-0 grid min-w-0 grid-cols-6 gap-x-4 gap-y-3">
86
{(typeof theme.colors[color] === "string"
87
? [""]
88
: Object.keys(theme.colors[color])
89
).map((stage) => (
90
<div key={stage} className="relative flex">
91
<div className="space-y-1.5">
92
<div
93
className={`h-10 w-full rounded bg-${color}${
94
stage === "" ? "" : `-${stage}`
95
} border-2 border-solid border-[rgba(0,0,0,0.1)] bg-clip-border`}
96
/>
97
<div className="px-0.5">
98
<div className="w-20 font-medium">
99
{stage || "-"}
100
</div>
101
<div className="font-mono lowercase text-gray-2">
102
{theme.colors[color][stage] ||
103
theme.colors[color]}
104
</div>
105
</div>
106
</div>
107
</div>
108
))}
109
</div>
110
</div>
111
</div>
112
))}
113
</div>
114
</GuideSection>
115
<GuideSection title="Icons">
116
<div className="flex flex-wrap gap-gutter">
117
{[
118
`api-gear`,
119
`api-window`,
120
`award`,
121
`decentralized`,
122
`donate-box`,
123
`donate`,
124
`feed`,
125
`money`,
126
`move-servers`,
127
`move`,
128
`open-source`,
129
`price-tag`,
130
`privacy`,
131
`progressive-web`,
132
`safe`,
133
`safety-1`,
134
`safety`,
135
`screen`,
136
`servers`,
137
].map((name) => (
138
<figure
139
key={name}
140
className="relative flex flex-col items-baseline gap-4"
141
>
142
<Image
143
src={require(`../public/icons/${name}.svg`)}
144
className="aspect-square"
145
width="120"
146
height="120"
147
alt=""
148
/>
149
<figcaption className="b2">{name}</figcaption>
150
</figure>
151
))}
152
</div>
153
</GuideSection>
154
155
<h2 className="h2">Components</h2>
156
157
<GuideSection title={<code>IconCard</code>}>
158
<div className="grid grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-gutter">
159
<IconCard
160
title="Decentralized"
161
icon="decentralized"
162
copy={
163
"Not controlled by a single website or company, Mastodon is a network of completely independent service providers forming a global, cohesive social media platform. "
164
}
165
/>
166
</div>
167
</GuideSection>
168
<GuideSection title={<code>LinkButton</code>}>
169
<h4 className="h6">Sizes</h4>
170
<div className="flex gap-gutter rounded bg-gray-5 p-4">
171
<LinkButton href="/" size="large">
172
Large
173
</LinkButton>
174
<LinkButton href="/" size="medium">
175
Medium
176
</LinkButton>
177
<LinkButton href="/" size="small">
178
Small
179
</LinkButton>
180
</div>
181
<h4 className="h6">Colors</h4>
182
<div className="flex gap-gutter rounded bg-gray-5 p-4">
183
<LinkButton href="/" size="medium">
184
Default
185
</LinkButton>
186
<LinkButton href="/" size="medium" light>
187
Light
188
</LinkButton>
189
<LinkButton href="/" size="medium" light borderless>
190
Light, Borderless
191
</LinkButton>
192
</div>
193
</GuideSection>
194
<GuideSection title={<code>SelectMenu</code>}>
195
<SelectMenu
196
label={
197
<FormattedMessage id="sorting.sort_by" defaultMessage="Sort" />
198
}
199
value="all"
200
onChange={() => {}}
201
options={[
202
{
203
label: intl.formatMessage({
204
id: "sorting.recently_added",
205
defaultMessage: "Recently Added",
206
}),
207
value: "all",
208
},
209
{
210
label: intl.formatMessage({
211
id: "sorting.free",
212
defaultMessage: "Free",
213
}),
214
value: "x",
215
},
216
{
217
label: intl.formatMessage({
218
id: "sorting.alphabetical",
219
defaultMessage: "A–Z",
220
}),
221
value: "y",
222
},
223
]}
224
/>
225
</GuideSection>
226
<GuideSection title={<code>AppCard</code>}>
227
<div className="max-w-sm">
228
<AppCard
229
{...{
230
released_on: "Mar 15, 2017",
231
name: "Tusky",
232
icon: tusky,
233
url: "https://play.google.com/store/apps/details?id=com.keylesspalace.tusky",
234
paid: false,
235
open: true,
236
source_url: "https://github.com/tuskyapp/Tusky",
237
category: "android",
238
categoryLabel: intl.formatMessage({
239
id: "browse_apps.android",
240
defaultMessage: "Android",
241
}),
242
}}
243
/>
244
</div>
245
</GuideSection>
246
<GuideSection
247
title={<code>SkeletonText</code>}
248
controls={
249
<SelectMenu
250
label="Size"
251
value={skeletonTextSize}
252
onChange={(value) => setSkeletonTextSize(value)}
253
options={Object.keys(theme.fontSize).map((typeClass) => ({
254
label: typeClass,
255
value: typeClass,
256
}))}
257
/>
258
}
259
>
260
<p className={classNames(skeletonTextSize, "max-w-[20ch]")}>
261
<SkeletonText className="w-[20ch]" />
262
<SkeletonText className="w-[20ch]" />
263
<SkeletonText className="w-[16ch]" />
264
</p>
265
</GuideSection>
266
<GuideSection title={<code>TwoUpFeature</code>}>
267
<TwoUpFeature
268
features={[
269
{
270
Icon: ProgressiveWebIcon,
271
title: (
272
<FormattedMessage
273
id="browse_apps.progressive_web_app"
274
defaultMessage="Progressive web app"
275
/>
276
),
277
copy: (
278
<FormattedMessage
279
id="browse_apps.you_can_use_it_from_desktop"
280
defaultMessage="You can always use Mastodon from the browser on your desktop or phone! It can be added to your home screen and some browsers even support push notifications, just like a native app!"
281
/>
282
),
283
cta: (
284
<FormattedMessage
285
id="browse_apps.pwa_feature.cta"
286
defaultMessage="Join a server"
287
/>
288
),
289
cta_link: "/servers",
290
},
291
{
292
Icon: ApiGearIcon,
293
title: (
294
<FormattedMessage
295
id="browse_apps.open_api"
296
defaultMessage="Open API"
297
/>
298
),
299
copy: (
300
<FormattedMessage
301
id="browse_apps.make_your_own"
302
defaultMessage="Mastodon is open-source and has an elegant, well-documented API that is available to everyone. Make your own app, or use one of the many third-party apps made by other developers!"
303
/>
304
),
305
cta: (
306
<FormattedMessage
307
id="browse_apps.api_docs"
308
defaultMessage="API documentation"
309
/>
310
),
311
cta_link: "https://docs.joinmastodon.org/client/intro/",
312
},
313
]}
314
/>
315
</GuideSection>
316
<GuideSection
317
title={<code>ServerCard</code>}
318
controls={
319
<label>
320
<input
321
type="checkbox"
322
onChange={(e) => setServerCardLoading(e.target.checked)}
323
/>{" "}
324
Loading
325
</label>
326
}
327
>
328
<div className="max-w-xs">
329
<ServerCard
330
server={
331
serverCardLoading
332
? undefined
333
: {
334
domain: "mastodon.social",
335
version: "4.5.6",
336
description:
337
"The original server maintained by the Mastodon GmbH non-profit.",
338
languages: ["en"],
339
region: "",
340
categories: ["general"],
341
proxied_thumbnail:
342
"https://proxy.joinmastodon.org/d7d02f9615184131475feeb95ab8cd01e6575448/68747470733a2f2f66696c65732e6d6173746f646f6e2e736f6369616c2f736974655f75706c6f6164732f66696c65732f3030302f3030302f3030312f6f726967696e616c2f766c63736e61702d323031382d30382d32372d31366834336d3131733132372e706e67",
343
total_users: 805131,
344
last_week_users: 88052,
345
approval_required: false,
346
language: "en",
347
category: "general",
348
}
349
}
350
/>
351
</div>
352
</GuideSection>
353
<GuideSection
354
title={<code>AppHero</code>}
355
controls={
356
<label>
357
<input
358
type="checkbox"
359
onChange={(e) => setAltAppHero(e.target.checked)}
360
checked={altAppHero}
361
/>
362
{" Alternate AppHero"}
363
</label>
364
}
365
>
366
<div>
367
{altAppHero ? (
368
<AppHero backgroundImage={app_hero_planets} />
369
) : (
370
<AppHero
371
backgroundImage={app_hero_festival}
372
backgroundImagePosition={"center left"}
373
/>
374
)}
375
</div>
376
</GuideSection>
377
</div>
378
</Layout>
379
)
380
}
381
382
export default Guide
383
384
export const getStaticProps = withDefaultStaticProps()
385
386