⭐ feat(infra): Update infra and add support for teams to SST (#186)
## Description - [x] Adds support for AWS SSO, which makes us (the team) able to use SST and update the components independently - [x] Splits the webpage into the landing page (Qwik), and Astro (the console) in charge of playing. This allows us to pass in Environment Variables to the console - ~Migrates the docs from Nuxt to Nextjs, and connects them to SST. This allows us to use Fumadocs _citation needed_ that's much more beautiful, and supports OpenApi~ - Cloudflare pages with github integration is not working on our new CF account. So we will have to push the pages deployment manually with Github actions - [x] Moves the current set up from my personal CF and AWS accounts to dedicated Nestri accounts - ## Related Issues <!-- List any related issues (e.g., "Closes #123", "Fixes #456") --> ## Type of Change - [ ] Bug fix (non-breaking change) - [x] New feature (non-breaking change) - [ ] Breaking change (fix or feature that changes existing functionality) - [x] Documentation update - [ ] Other (please describe): ## Checklist - [x] I have updated relevant documentation - [x] My code follows the project's coding style - [x] My changes generate no new warnings/errors ## Notes for Reviewers <!-- Point out areas you'd like reviewers to focus on, questions you have, or decisions that need discussion --> Please approve my PR 🥹 ## Screenshots/Demo <!-- If applicable, add screenshots or a GIF demo of your changes (especially for UI changes) --> ## Additional Context <!-- Add any other context about the pull request here -->
147
packages/www/src/App.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import '@fontsource-variable/mona-sans';
|
||||
import '@fontsource-variable/geist-mono';
|
||||
import '@fontsource/geist-sans/400.css';
|
||||
import '@fontsource/geist-sans/500.css';
|
||||
import '@fontsource/geist-sans/600.css';
|
||||
import '@fontsource/geist-sans/700.css';
|
||||
import '@fontsource/geist-sans/800.css';
|
||||
import '@fontsource/geist-sans/900.css';
|
||||
import { TeamCreate } from './pages/new';
|
||||
import { styled } from "@macaron-css/solid";
|
||||
import { useStorage } from './providers/account';
|
||||
import { darkClass, lightClass, theme } from './ui/theme';
|
||||
import { AuthProvider, useAuth } from './providers/auth';
|
||||
import { Navigate, Route, Router } from "@solidjs/router";
|
||||
import { globalStyle, macaron$ } from "@macaron-css/core";
|
||||
import { Component, createSignal, Match, onCleanup, Switch } from 'solid-js';
|
||||
|
||||
const Root = styled("div", {
|
||||
base: {
|
||||
inset: 0,
|
||||
lineHeight: 1,
|
||||
fontSynthesis: "none",
|
||||
color: theme.color.d1000.gray,
|
||||
fontFamily: theme.font.family.body,
|
||||
textRendering: "optimizeLegibility",
|
||||
WebkitFontSmoothing: "antialised",
|
||||
backgroundColor: theme.color.background.d100,
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle("html", {
|
||||
fontSize: 16,
|
||||
fontWeight: 400,
|
||||
// Hardcode colors
|
||||
"@media": {
|
||||
"(prefers-color-scheme: light)": {
|
||||
backgroundColor: "hsla(0,0%,98%)",
|
||||
},
|
||||
"(prefers-color-scheme: dark)": {
|
||||
backgroundColor: "hsla(0,0%,0%)",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle("h1, h2, h3, h4, h5, h6, p", {
|
||||
margin: 0,
|
||||
});
|
||||
|
||||
macaron$(() =>
|
||||
["::placeholder", ":-ms-input-placeholder"].forEach((selector) =>
|
||||
globalStyle(selector, {
|
||||
opacity: 1,
|
||||
color: theme.color.d1000.gray,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
globalStyle("body", {
|
||||
cursor: "default",
|
||||
});
|
||||
|
||||
globalStyle("*", {
|
||||
boxSizing: "border-box",
|
||||
});
|
||||
|
||||
export const App: Component = () => {
|
||||
const [theme, setTheme] = createSignal<string>(
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light",
|
||||
);
|
||||
|
||||
const darkMode = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const setColorScheme = (e: MediaQueryListEvent) => {
|
||||
setTheme(e.matches ? "dark" : "light");
|
||||
};
|
||||
darkMode.addEventListener("change", setColorScheme);
|
||||
onCleanup(() => {
|
||||
darkMode.removeEventListener("change", setColorScheme);
|
||||
});
|
||||
|
||||
const storage = useStorage();
|
||||
|
||||
return (
|
||||
<Root class={theme() === "light" ? lightClass : darkClass} id="styled">
|
||||
<Router>
|
||||
<Route
|
||||
path="*"
|
||||
component={(props) => (
|
||||
<AuthProvider>
|
||||
{props.children}
|
||||
</AuthProvider>
|
||||
// <CommandBar>
|
||||
// <ReplicacheStatusProvider>
|
||||
// <DummyProvider>
|
||||
// <DummyConfigProvider>
|
||||
// <FlagsProvider>
|
||||
// <RealtimeProvider />
|
||||
// <LocalProvider>
|
||||
// <LocalLogsProvider>
|
||||
// <GlobalCommands />
|
||||
// {props.children}
|
||||
// </LocalLogsProvider>
|
||||
// </LocalProvider>
|
||||
// </FlagsProvider>
|
||||
// </DummyConfigProvider>
|
||||
// </DummyProvider>
|
||||
// </ReplicacheStatusProvider>
|
||||
// </AuthProvider>
|
||||
// </CommandBar>
|
||||
)}
|
||||
>
|
||||
{/* <Route path="local" component={Local} />
|
||||
<Route path="debug" component={DebugRoute} />
|
||||
<Route path="design" component={Design} />
|
||||
<Route path="workspace" component={WorkspaceCreate} />
|
||||
<Route path=":workspaceSlug">{WorkspaceRoute}</Route> */}
|
||||
<Route path="new" component={TeamCreate} />
|
||||
<Route
|
||||
path="/"
|
||||
component={() => {
|
||||
const auth = useAuth();
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={auth.current.teams.length > 0}>
|
||||
<Navigate
|
||||
href={`/${(
|
||||
auth.current.teams.find(
|
||||
(w) => w.id === storage.value.team,
|
||||
) || auth.current.teams[0]
|
||||
).slug
|
||||
}`}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<Navigate href={`/new`} />
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{/* <Route path="*" component={() => <NotFound />} /> */}
|
||||
</Route>
|
||||
</Router>
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
BIN
packages/www/src/assets/fonts/Geist.ttf
Normal file
BIN
packages/www/src/assets/fonts/GeistMono.ttf
Normal file
BIN
packages/www/src/assets/fonts/MonaSansVF-Regular.ttf
Normal file
BIN
packages/www/src/assets/fonts/MonaSansVF-Regular.woff
Normal file
BIN
packages/www/src/assets/fonts/MonaSansVF-Regular.woff2
Normal file
BIN
packages/www/src/assets/seo/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 618 B |
BIN
packages/www/src/assets/seo/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/www/src/assets/seo/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 625 B |
BIN
packages/www/src/assets/seo/banner.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
9
packages/www/src/assets/seo/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/icons/mstile-150x150.png"/>
|
||||
<TileColor>#ffede5</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
packages/www/src/assets/seo/code.avif
Normal file
|
After Width: | Height: | Size: 546 KiB |
BIN
packages/www/src/assets/seo/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 573 B |
BIN
packages/www/src/assets/seo/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 608 B |
BIN
packages/www/src/assets/seo/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
packages/www/src/assets/seo/image.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
packages/www/src/assets/seo/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 696 B |
19
packages/www/src/assets/seo/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M398 4232 l-48 -3 -1 -42 c-2 -188 2 -861 6 -865 2 -3 997 -5 2210
|
||||
-6 l2205 -1 -2 458 -3 459 -2160 1 c-1188 1 -2181 1 -2207 -1z"/>
|
||||
<path d="M350 2984 c0 -19 0 -198 0 -399 0 -201 0 -391 0 -424 l0 -58 2210 0
|
||||
2210 0 0 457 0 457 -2210 0 -2210 0 0 -33z"/>
|
||||
<path d="M354 1799 c-3 -6 -7 -613 -5 -844 l1 -70 2197 2 c1209 1 2204 2 2211
|
||||
3 18 0 18 910 0 911 -81 4 -4401 2 -4404 -2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
18
packages/www/src/assets/seo/site.webmanifest
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Nestri",
|
||||
"short_name": "Nestri",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#fafafa",
|
||||
"background_color": "#fafafa",
|
||||
"display": "standalone"}
|
||||
26
packages/www/src/common/context.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ParentProps, Show, createContext, useContext } from "solid-js";
|
||||
|
||||
export function createInitializedContext<
|
||||
Name extends string,
|
||||
T extends { ready: boolean }
|
||||
>(name: Name, cb: () => T) {
|
||||
const ctx = createContext<T>();
|
||||
|
||||
return {
|
||||
use: () => {
|
||||
const context = useContext(ctx);
|
||||
if (!context) throw new Error(`No ${name} context`);
|
||||
return context;
|
||||
},
|
||||
provider: (props: ParentProps) => {
|
||||
const value = cb();
|
||||
return (
|
||||
<Show when={value.ready}>
|
||||
<ctx.Provider value={value} {...props}>
|
||||
{props.children}
|
||||
</ctx.Provider>
|
||||
</Show>
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
27
packages/www/src/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
/* @refresh reload */
|
||||
import { render } from "solid-js/web";
|
||||
// import posthog from "posthog-js";
|
||||
// posthog.init("phc_M0b2lW4smpsGIufiTBZ22USKwCy0fyqljMOGufJc79p", {
|
||||
// api_host: "https://telemetry.ion.sst.dev",
|
||||
// });
|
||||
|
||||
import "modern-normalize/modern-normalize.css";
|
||||
import { App } from "./App";
|
||||
import { StorageProvider } from "./providers/account";
|
||||
|
||||
const root = document.getElementById("root");
|
||||
|
||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
throw new Error(
|
||||
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got mispelled?"
|
||||
);
|
||||
}
|
||||
|
||||
render(
|
||||
() => (
|
||||
<StorageProvider>
|
||||
<App />
|
||||
</StorageProvider>
|
||||
),
|
||||
root!
|
||||
);
|
||||
7
packages/www/src/pages/default-state.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function DefaultState() {
|
||||
return (
|
||||
<div>
|
||||
We are logging you in
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
packages/www/src/pages/new.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Container, FullScreen } from "@nestri/www/ui/layout";
|
||||
import { Text } from "@nestri/www/ui/text";
|
||||
|
||||
export function TeamCreate() {
|
||||
return (
|
||||
<FullScreen>
|
||||
<Container flow="column" >
|
||||
<Text align="center" spacing="lg" size="4xl" weight="semibold">
|
||||
Your first deploy is just a sign-up away.
|
||||
</Text>
|
||||
|
||||
</Container>
|
||||
</FullScreen>
|
||||
)
|
||||
}
|
||||
34
packages/www/src/providers/account.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createStore } from "solid-js/store";
|
||||
import { makePersisted } from "@solid-primitives/storage";
|
||||
import { ParentProps, createContext, useContext } from "solid-js";
|
||||
|
||||
type Context = ReturnType<typeof init>;
|
||||
const context = createContext<Context>();
|
||||
|
||||
function init() {
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore({
|
||||
account: "",
|
||||
team: "",
|
||||
dummy: "",
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
value: store,
|
||||
set: setStore,
|
||||
};
|
||||
}
|
||||
|
||||
export function StorageProvider(props: ParentProps) {
|
||||
const ctx = init();
|
||||
return <context.Provider value={ctx}>{props.children}</context.Provider>;
|
||||
}
|
||||
|
||||
export function useStorage() {
|
||||
const ctx = useContext(context);
|
||||
if (!ctx) {
|
||||
throw new Error("No storage context");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
222
packages/www/src/providers/auth.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
import { type Team } from "@nestri/core/team/index";
|
||||
import { makePersisted } from "@solid-primitives/storage";
|
||||
import { useLocation, useNavigate } from "@solidjs/router";
|
||||
import { createClient } from "@openauthjs/openauth/client";
|
||||
import { createInitializedContext } from "../common/context";
|
||||
import { createEffect, createMemo, onMount } from "solid-js";
|
||||
import { createStore, produce, reconcile } from "solid-js/store";
|
||||
|
||||
interface AccountInfo {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
access: string;
|
||||
refresh: string;
|
||||
avatarUrl: string;
|
||||
teams: Team.Info[];
|
||||
discriminator: number;
|
||||
polarCustomerID: string | null;
|
||||
}
|
||||
|
||||
interface Storage {
|
||||
accounts: Record<string, AccountInfo>;
|
||||
current?: string;
|
||||
}
|
||||
|
||||
export const client = createClient({
|
||||
issuer: import.meta.env.VITE_AUTH_URL,
|
||||
clientID: "web",
|
||||
});
|
||||
|
||||
export const { use: useAuth, provider: AuthProvider } =
|
||||
createInitializedContext("AuthContext", () => {
|
||||
const [store, setStore] = makePersisted(
|
||||
createStore<Storage>({
|
||||
accounts: {},
|
||||
}),
|
||||
{
|
||||
name: "radiant.auth",
|
||||
},
|
||||
);
|
||||
const location = useLocation();
|
||||
const params = createMemo(
|
||||
() => new URLSearchParams(location.hash.substring(1)),
|
||||
);
|
||||
const accessToken = createMemo(() => params().get("access_token"));
|
||||
const refreshToken = createMemo(() => params().get("refresh_token"));
|
||||
|
||||
|
||||
createEffect(async () => {
|
||||
// if (!result.current && Object.keys(store.accounts).length) {
|
||||
// result.switch(Object.keys(store.accounts)[0])
|
||||
// navigate("/")
|
||||
// }
|
||||
})
|
||||
|
||||
createEffect(async () => {
|
||||
if (accessToken()) return;
|
||||
if (Object.keys(store.accounts).length) return;
|
||||
const redirect = await client.authorize(window.location.origin, "token");
|
||||
window.location.href = redirect.url
|
||||
});
|
||||
|
||||
createEffect(async () => {
|
||||
const current = store.current;
|
||||
const accounts = store.accounts;
|
||||
if (!current) return;
|
||||
const match = accounts[current];
|
||||
if (match) return;
|
||||
const keys = Object.keys(accounts);
|
||||
if (keys.length) {
|
||||
setStore("current", keys[0]);
|
||||
navigate("/");
|
||||
return
|
||||
}
|
||||
const redirect = await client.authorize(window.location.origin, "token");
|
||||
window.location.href = redirect.url
|
||||
});
|
||||
|
||||
async function refresh() {
|
||||
for (const account of [...Object.values(store.accounts)]) {
|
||||
if (!account.refresh) continue;
|
||||
const result = await client.refresh(account.refresh, {
|
||||
access: account.access,
|
||||
})
|
||||
if (result.err) {
|
||||
if ("id" in account)
|
||||
setStore(produce((state) => {
|
||||
delete state.accounts[account.id];
|
||||
}))
|
||||
continue
|
||||
};
|
||||
const tokens = result.tokens || {
|
||||
access: account.access,
|
||||
refresh: account.refresh,
|
||||
}
|
||||
fetch(import.meta.env.VITE_API_URL + "/account", {
|
||||
headers: {
|
||||
authorization: `Bearer ${tokens.access}`,
|
||||
},
|
||||
}).then(async (response) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
const info = await result.data;
|
||||
|
||||
setStore(
|
||||
"accounts",
|
||||
info.id,
|
||||
reconcile({
|
||||
...info,
|
||||
...tokens,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok)
|
||||
setStore(
|
||||
produce((state) => {
|
||||
delete state.accounts[account.id];
|
||||
}),
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (refreshToken() && accessToken()) {
|
||||
const result = await fetch(import.meta.env.VITE_API_URL + "/account", {
|
||||
headers: {
|
||||
authorization: `Bearer ${accessToken()}`,
|
||||
},
|
||||
}).catch(() => { })
|
||||
if (result?.ok) {
|
||||
const response = await result.json();
|
||||
const info = await response.data;
|
||||
setStore(
|
||||
"accounts",
|
||||
info.id,
|
||||
reconcile({
|
||||
...info,
|
||||
access: accessToken(),
|
||||
refresh: refreshToken(),
|
||||
}),
|
||||
);
|
||||
setStore("current", info.id);
|
||||
}
|
||||
window.location.hash = "";
|
||||
}
|
||||
|
||||
await refresh();
|
||||
})
|
||||
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// const bar = useCommandBar()
|
||||
|
||||
// bar.register("auth", async () => {
|
||||
// return [
|
||||
// {
|
||||
// category: "Account",
|
||||
// title: "Logout",
|
||||
// icon: IconLogout,
|
||||
// run: async (bar) => {
|
||||
// result.logout();
|
||||
// setStore("current", undefined);
|
||||
// navigate("/");
|
||||
// bar.hide()
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// category: "Add Account",
|
||||
// title: "Add Account",
|
||||
// icon: IconUserAdd,
|
||||
// run: async () => {
|
||||
// const redir = await client.authorize(window.location.origin, "token");
|
||||
// window.location.href = redir.url
|
||||
// bar.hide()
|
||||
// },
|
||||
// },
|
||||
// ...result.all()
|
||||
// .filter((item) => item.id !== result.current.id)
|
||||
// .map((item) => ({
|
||||
// category: "Account",
|
||||
// title: "Switch to " + item.email,
|
||||
// icon: IconUser,
|
||||
// run: async () => {
|
||||
// result.switch(item.id);
|
||||
// navigate("/");
|
||||
// bar.hide()
|
||||
// },
|
||||
// })),
|
||||
// ]
|
||||
// })
|
||||
|
||||
const result = {
|
||||
get current() {
|
||||
return store.accounts[store.current!]!;
|
||||
},
|
||||
switch(accountID: string) {
|
||||
setStore("current", accountID);
|
||||
},
|
||||
all() {
|
||||
return Object.values(store.accounts);
|
||||
},
|
||||
refresh,
|
||||
logout() {
|
||||
setStore(
|
||||
produce((state) => {
|
||||
if (!state.current) return;
|
||||
delete state.accounts[state.current];
|
||||
state.current = Object.keys(state.accounts)[0];
|
||||
}),
|
||||
);
|
||||
},
|
||||
get ready() {
|
||||
return Boolean(!accessToken() && store.current);
|
||||
},
|
||||
};
|
||||
return result;
|
||||
});
|
||||
12
packages/www/src/sst-env.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/// <reference types="vite/client" />
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_API_URL: string
|
||||
readonly VITE_AUTH_URL: string
|
||||
readonly VITE_STAGE: string
|
||||
}
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
48
packages/www/src/ui/layout.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { theme } from "./theme";
|
||||
import { styled } from "@macaron-css/solid";
|
||||
|
||||
export const FullScreen = styled("div", {
|
||||
base: {
|
||||
inset: 0,
|
||||
zIndex: 0,
|
||||
display: "flex",
|
||||
position: "fixed",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.color.background.d200,
|
||||
},
|
||||
variants: {
|
||||
inset: {
|
||||
none: {},
|
||||
header: {
|
||||
top: theme.headerHeight.root,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const Container = styled("div", {
|
||||
base: {
|
||||
backgroundColor: theme.color.background.d100,
|
||||
borderColor: theme.color.gray.d400,
|
||||
padding: "64px 80px 48px",
|
||||
justifyContent: "center",
|
||||
borderStyle: "solid",
|
||||
position: "relative",
|
||||
borderRadius: 12,
|
||||
alignItems: "center",
|
||||
maxWidth: 550,
|
||||
borderWidth: 1,
|
||||
display: "flex",
|
||||
},
|
||||
variants: {
|
||||
flow: {
|
||||
column: {
|
||||
flexDirection: "column"
|
||||
},
|
||||
row: {
|
||||
flexDirection: "row"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
167
packages/www/src/ui/text.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import { theme } from "./theme";
|
||||
import { styled } from "@macaron-css/solid";
|
||||
import { utility } from "./utility";
|
||||
import { CSSProperties } from "@macaron-css/core";
|
||||
|
||||
export const Text = styled("span", {
|
||||
base: {
|
||||
textWrap: "balance"
|
||||
},
|
||||
variants: {
|
||||
leading: {
|
||||
base: {
|
||||
lineHeight: 1,
|
||||
},
|
||||
normal: {
|
||||
lineHeight: "normal",
|
||||
},
|
||||
loose: {
|
||||
lineHeight: theme.font.lineHeight,
|
||||
},
|
||||
},
|
||||
align: {
|
||||
left: {
|
||||
textAlign: "left"
|
||||
},
|
||||
center: {
|
||||
textAlign: "center"
|
||||
}
|
||||
},
|
||||
spacing: {
|
||||
none: {
|
||||
letterSpacing: 0
|
||||
},
|
||||
xs: {
|
||||
letterSpacing: -0.96
|
||||
},
|
||||
sm: {
|
||||
letterSpacing: -0.96
|
||||
},
|
||||
md: {
|
||||
letterSpacing: -1.28
|
||||
},
|
||||
lg: {
|
||||
letterSpacing: -1.28
|
||||
}
|
||||
},
|
||||
code: {
|
||||
true: {
|
||||
fontFamily: theme.font.family.code,
|
||||
},
|
||||
},
|
||||
capitalize: {
|
||||
true: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
},
|
||||
uppercase: {
|
||||
true: {
|
||||
letterSpacing: 0.5,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
},
|
||||
weight: {
|
||||
regular: {
|
||||
fontWeight: theme.font.weight.regular,
|
||||
},
|
||||
medium: {
|
||||
fontWeight: theme.font.weight.medium,
|
||||
},
|
||||
semibold: {
|
||||
fontWeight: theme.font.weight.semibold,
|
||||
},
|
||||
},
|
||||
center: {
|
||||
true: {
|
||||
textAlign: "center",
|
||||
},
|
||||
},
|
||||
line: {
|
||||
true: {
|
||||
...utility.text.line,
|
||||
},
|
||||
},
|
||||
disableSelect: {
|
||||
true: {
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
true: {
|
||||
whiteSpace: "pre-wrap",
|
||||
overflowWrap: "anywhere",
|
||||
},
|
||||
},
|
||||
underline: {
|
||||
true: {
|
||||
textUnderlineOffset: 2,
|
||||
textDecoration: "underline",
|
||||
},
|
||||
},
|
||||
label: {
|
||||
true: {
|
||||
fontWeight: 500,
|
||||
letterSpacing: 0.5,
|
||||
textTransform: "uppercase",
|
||||
fontFamily: theme.font.family.code,
|
||||
},
|
||||
},
|
||||
break: {
|
||||
true: {
|
||||
wordBreak: "break-all",
|
||||
},
|
||||
false: {},
|
||||
},
|
||||
size: (() => {
|
||||
const result = {} as Record<`${keyof typeof theme.font.size}`, any>;
|
||||
for (const [key, value] of Object.entries(theme.font.size)) {
|
||||
result[key as keyof typeof theme.font.size] = {
|
||||
fontSize: value,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
})(),
|
||||
color: (() => {
|
||||
const record = {} as Record<keyof typeof theme.color.text, CSSProperties>;
|
||||
for (const [key, _value] of Object.entries(theme.color.text)) {
|
||||
record[key as keyof typeof record] = {};
|
||||
}
|
||||
return record;
|
||||
})(),
|
||||
on: (() => {
|
||||
const record = {} as Record<
|
||||
keyof typeof theme.color.text.primary,
|
||||
CSSProperties
|
||||
>;
|
||||
for (const [key, _value] of Object.entries(theme.color.text.primary)) {
|
||||
record[key as keyof typeof record] = {};
|
||||
}
|
||||
return record;
|
||||
})(),
|
||||
},
|
||||
compoundVariants: (() => {
|
||||
const result: any[] = [];
|
||||
for (const [color, ons] of Object.entries(theme.color.text)) {
|
||||
for (const [on, value] of Object.entries(ons)) {
|
||||
result.push({
|
||||
variants: {
|
||||
color,
|
||||
on,
|
||||
},
|
||||
style: {
|
||||
color: value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})(),
|
||||
defaultVariants: {
|
||||
on: "base",
|
||||
size: "base",
|
||||
color: "primary",
|
||||
spacing: "none",
|
||||
weight: "regular",
|
||||
},
|
||||
});
|
||||
426
packages/www/src/ui/theme.ts
Normal file
@@ -0,0 +1,426 @@
|
||||
import { createTheme } from "@macaron-css/core";
|
||||
|
||||
const constants = {
|
||||
colorFadeDuration: "0.15s",
|
||||
borderRadius: "4px",
|
||||
textBoldWeight: "600",
|
||||
iconOpacity: "0.85",
|
||||
modalWidth: {
|
||||
sm: "480px",
|
||||
md: "640px",
|
||||
lg: "800px",
|
||||
},
|
||||
headerHeight: {
|
||||
root: "68px",
|
||||
stage: "52px",
|
||||
},
|
||||
};
|
||||
|
||||
const space = {
|
||||
px: "1px",
|
||||
0: "0px",
|
||||
0.5: "0.125rem",
|
||||
1: "0.25rem",
|
||||
1.5: "0.375rem",
|
||||
2: "0.5rem",
|
||||
2.5: "0.625rem",
|
||||
3: "0.75rem",
|
||||
3.5: "0.875rem",
|
||||
4: "1rem",
|
||||
5: "1.25rem",
|
||||
6: "1.5rem",
|
||||
7: "1.75rem",
|
||||
8: "2rem",
|
||||
9: "2.25rem",
|
||||
10: "2.5rem",
|
||||
11: "2.75rem",
|
||||
12: "3rem",
|
||||
14: "3.5rem",
|
||||
16: "4rem",
|
||||
20: "5rem",
|
||||
24: "6rem",
|
||||
28: "7rem",
|
||||
32: "8rem",
|
||||
36: "9rem",
|
||||
40: "10rem",
|
||||
44: "11rem",
|
||||
48: "12rem",
|
||||
52: "13rem",
|
||||
56: "14rem",
|
||||
60: "15rem",
|
||||
64: "16rem",
|
||||
72: "18rem",
|
||||
80: "20rem",
|
||||
96: "24rem",
|
||||
};
|
||||
|
||||
const font = {
|
||||
lineHeight: "1.6",
|
||||
family: {
|
||||
heading: '"Mona Sans Variable", sans-serif',
|
||||
body: "'Geist Sans', sans-serif",
|
||||
code: '"Geist Mono Variable", monospace',
|
||||
},
|
||||
weight: {
|
||||
regular: "400",
|
||||
medium: "500",
|
||||
semibold: "600",
|
||||
bold: "700",
|
||||
extrabold: "800"
|
||||
},
|
||||
size: {
|
||||
mono_xs: "0.6875rem",
|
||||
xs: "0.75rem",
|
||||
mono_sm: "0.8125rem",
|
||||
sm: "0.875rem",
|
||||
mono_base: "0.9375rem",
|
||||
base: "1rem",
|
||||
mono_lg: "1.0625rem",
|
||||
lg: "1.125rem",
|
||||
mono_xl: "1.1875rem",
|
||||
xl: "1.25rem",
|
||||
mono_2xl: "1.375rem",
|
||||
"2xl": "1.5rem",
|
||||
"3xl": "1.875rem",
|
||||
"4xl": "2.25rem",
|
||||
"5xl": "3rem",
|
||||
"6xl": "3.75rem",
|
||||
"7xl": "4.5rem",
|
||||
"8xl": "6rem",
|
||||
"9xl": "8rem",
|
||||
},
|
||||
};
|
||||
|
||||
const light = (() => {
|
||||
const gray = {
|
||||
d100: 'hsla(0,0%,95%)',
|
||||
d200: 'hsla(0,0%,92%)',
|
||||
d300: 'hsla(0,0%,90%)',
|
||||
d400: 'hsla(0,0%,92%)',
|
||||
d500: 'hsla(0,0%,79%)',
|
||||
d600: 'hsla(0,0%,66%)',
|
||||
d700: 'hsla(0,0%,56%)',
|
||||
d800: 'hsla(0,0%,49%)',
|
||||
d900: 'hsla(0,0%,40%)',
|
||||
};
|
||||
|
||||
const blue = {
|
||||
d100: 'hsla(212,100%,97%)',
|
||||
d200: 'hsla(210,100%,96%)',
|
||||
d300: 'hsla(210,100%,94%)',
|
||||
d400: 'hsla(209,100%,90%)',
|
||||
d500: 'hsla(209,100%,80%)',
|
||||
d600: 'hsla(208,100%,66%)',
|
||||
d700: 'hsla(212,100%,48%)',
|
||||
d800: 'hsla(212,100%,41%)',
|
||||
d900: 'hsla(211,100%,42%)',
|
||||
};
|
||||
|
||||
const red = {
|
||||
d100: "hsla(0,100%,97%)",
|
||||
d200: "hsla(0,100%,96%)",
|
||||
d300: "hsla(0,100%,95%)",
|
||||
d400: "hsla(0,90%,92%)",
|
||||
d500: "hsla(0,82%,85%)",
|
||||
d600: "hsla(359,90%,71%)",
|
||||
d700: "hsla(358,75%,59%)",
|
||||
d800: "hsla(358,70%,52%)",
|
||||
d900: "hsla(358,66%,48%)",
|
||||
};
|
||||
const amber = {
|
||||
d100: "hsla(39,100%,95%)",
|
||||
d200: "hsla(44,100%,92%)",
|
||||
d300: "hsla( 43,96%,90%)",
|
||||
d400: "hsla(42,100%,78%)",
|
||||
d500: "hsla(38,100%,71%)",
|
||||
d600: "hsla( 36,90%,62%)",
|
||||
d700: "hsla(39,100%,57%)",
|
||||
d800: "hsla(35,100%,52%)",
|
||||
d900: "hsla(30,100%,32%)",
|
||||
};
|
||||
const green = {
|
||||
d100: "hsla(120,60%,96%)",
|
||||
d200: "hsla(120,60%,95%)",
|
||||
d300: "hsla(120,60%,91%)",
|
||||
d400: "hsla(122,60%,86%)",
|
||||
d500: "hsla(124,60%,75%)",
|
||||
d600: "hsla(125,60%,64%)",
|
||||
d700: "hsla(131,41%,46%)",
|
||||
d800: "hsla(132,43%,39%)",
|
||||
d900: "hsla(133,50%,32%)",
|
||||
};
|
||||
const teal = {
|
||||
d100: "hsla(169,70%,96%)",
|
||||
d200: "hsla(167,70%,94%)",
|
||||
d300: "hsla(168,70%,90%)",
|
||||
d400: "hsla(170,70%,85%)",
|
||||
d500: "hsla(170,70%,72%)",
|
||||
d600: "hsla(170,70%,57%)",
|
||||
d700: "hsla(173,80%,36%)",
|
||||
d800: "hsla(173,83%,30%)",
|
||||
d900: "hsla(174,91%,25%)",
|
||||
};
|
||||
|
||||
const purple = {
|
||||
d100: "hsla(276,100%,97%)",
|
||||
d200: "hsla(277,87%,97%)",
|
||||
d300: "hsla(274,78%,95%)",
|
||||
d400: "hsla(276,71%,92%)",
|
||||
d500: "hsla(274,70%,82%)",
|
||||
d600: "hsla(273,72%,73%)",
|
||||
d700: "hsla(272,51%,54%)",
|
||||
d800: "hsla(272,47%,45%)",
|
||||
d900: "hsla(274,71%,43%)",
|
||||
};
|
||||
|
||||
const pink = {
|
||||
d100: "hsla(330,100%,96%)",
|
||||
d200: "hsla(340,90%,96%)",
|
||||
d300: "hsla(340,82%,94%)",
|
||||
d400: "hsla(341,76%,91%)",
|
||||
d500: "hsla(340,75%,84%)",
|
||||
d600: "hsla(341,75%,73%)",
|
||||
d700: "hsla(336,80%,58%)",
|
||||
d800: "hsla(336,74%,51%)",
|
||||
d900: "hsla(336,65%,45%)",
|
||||
};
|
||||
|
||||
const grayAlpha = {
|
||||
d100: "rgba(0,0,0,0.05)",
|
||||
d200: "hsla(0,0%,0%,0.08)",
|
||||
d300: "hsla(0,0%,0%,0.1)",
|
||||
d400: "hsla(0,0%,0%,0.08)",
|
||||
d500: "hsla(0,0%,0%,0.21)",
|
||||
d600: "hsla(0,0%,0%,0.34)",
|
||||
d700: "hsla(0,0%,0%,0.44)",
|
||||
d800: "hsla(0,0%,0%,0.51)",
|
||||
d900: "hsla(0,0%,0%,0.61)",
|
||||
};
|
||||
|
||||
const d1000 = {
|
||||
gray: 'hsla(0,0%,9%)',
|
||||
blue: 'hsla(211,100%,15%)',
|
||||
red: "hsla(355,49%,15%)",
|
||||
amber: "hsla(20,79%,17%)",
|
||||
green: "hsla(128,29%,15%)",
|
||||
teal: "hsla(171,80%,13%)",
|
||||
purple: "hsla(276,100%,15)",
|
||||
pink: "hsla(333,74%,15%)",
|
||||
grayAlpha: " hsla(0,0%,0%,0.91)"
|
||||
}
|
||||
|
||||
const background = {
|
||||
d100: 'hsla(0,0%,100%)',
|
||||
d200: 'hsla(0,0%,98%)'
|
||||
};
|
||||
|
||||
const contrastFg = '#ffffff';
|
||||
const focusBorder = `0 0 0 1px ${grayAlpha.d600}, 0px 0px 0px 4px rgba(0,0,0,0.16)`;
|
||||
const focusColor = blue.d700
|
||||
|
||||
const text = {
|
||||
primary: {
|
||||
base: d1000.gray,
|
||||
surface: gray.d900,
|
||||
},
|
||||
info: {
|
||||
base: d1000.amber,
|
||||
surface: amber.d900,
|
||||
},
|
||||
danger: {
|
||||
base: d1000.red,
|
||||
surface: red.d900,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
gray,
|
||||
blue,
|
||||
red,
|
||||
amber,
|
||||
green,
|
||||
teal,
|
||||
purple,
|
||||
pink,
|
||||
grayAlpha,
|
||||
background,
|
||||
contrastFg,
|
||||
focusBorder,
|
||||
focusColor,
|
||||
d1000,
|
||||
text
|
||||
};
|
||||
})()
|
||||
|
||||
const dark = (() => {
|
||||
const gray = {
|
||||
d100: "hsla(0,0%,10%)",
|
||||
d200: "hsla(0,0%,12%)",
|
||||
d300: "hsla(0,0%,16%)",
|
||||
d400: "hsla(0,0%,18%)",
|
||||
d500: "hsla(0,0%,27%)",
|
||||
d600: "hsla(0,0%,53%)",
|
||||
d700: "hsla(0,0%,56%)",
|
||||
d800: "hsla(0,0%,49%)",
|
||||
d900: "hsla(0,0%,63%)",
|
||||
};
|
||||
|
||||
const blue = {
|
||||
d100: "hsla(216,50%,12%)",
|
||||
d200: "hsla(214,59%,15%)",
|
||||
d300: "hsla(213,71%,20%)",
|
||||
d400: "hsla(212,78%,23%)",
|
||||
d500: "hsla(211,86%,27%)",
|
||||
d600: "hsla(206,100%,50%)",
|
||||
d700: "hsla(212,100%,48%)",
|
||||
d800: "hsla(212,100%,41%)",
|
||||
d900: "hsla(210,100%,66%)",
|
||||
};
|
||||
|
||||
const red = {
|
||||
d200: "hsla(357,46%,16%)",
|
||||
d100: "hsla(357,37%,12%)",
|
||||
d300: "hsla(356,54%,22%)",
|
||||
d400: "hsla(357,55%,26%)",
|
||||
d500: "hsla(357,60%,32%)",
|
||||
d600: "hsla(358,75%,59%)",
|
||||
d700: "hsla(358,75%,59%)",
|
||||
d800: "hsla(358,69%,52%)",
|
||||
d900: "hsla(358,100%,69%)",
|
||||
};
|
||||
const amber = {
|
||||
d100: "hsla(35,100%,8%)",
|
||||
d200: "hsla(32,100%,10%)",
|
||||
d300: "hsla(33,100%,15%)",
|
||||
d400: "hsla(35,100%,17%)",
|
||||
d500: "hsla(35,91%,22%)",
|
||||
d600: "hsla(39,85%,49%)",
|
||||
d700: "hsla(39,100%,57%)",
|
||||
d800: "hsla(35,100%,52%)",
|
||||
d900: "hsla(39,90%,50%)",
|
||||
};
|
||||
const green = {
|
||||
d100: "hsla(136,50%,9%)",
|
||||
d200: "hsla(137,50%,12%)",
|
||||
d300: "hsla(136,50%,14%)",
|
||||
d400: "hsla(135,70%,16%)",
|
||||
d500: "hsla(135,70%,23%)",
|
||||
d600: "hsla(135,70%,34%)",
|
||||
d700: "hsla(131,41%,46%)",
|
||||
d800: "hsla(132,43%,39%)",
|
||||
d900: "hsla(131,43%,57%)",
|
||||
};
|
||||
const teal = {
|
||||
d100: "hsla(169,78%,7%)",
|
||||
d200: "hsla(170,74%,9%)",
|
||||
d300: "hsla(171,75%,13%)",
|
||||
d400: "hsla(171,85%,13%)",
|
||||
d500: "hsla(172,85%,20%)",
|
||||
d600: "hsla(172,85%,32%)",
|
||||
d700: "hsla(173,80%,36%)",
|
||||
d800: "hsla(173,83%,30%)",
|
||||
d900: "hsla(174,90%,41%)",
|
||||
};
|
||||
const purple = {
|
||||
d100: "hsla(283,30%,12%)",
|
||||
d200: "hsla(281,38%,16%)",
|
||||
d300: "hsla(279,44%,23%)",
|
||||
d400: "hsla(277,46%,28%)",
|
||||
d500: "hsla(274,49%,35%)",
|
||||
d600: "hsla(272,51%,54%)",
|
||||
d700: "hsla(272,51%,54%)",
|
||||
d800: "hsla(272,47%,45%)",
|
||||
d900: "hsla(275,80%,71%)",
|
||||
};
|
||||
const pink = {
|
||||
d100: "hsla(335,32%,12%)",
|
||||
d200: "hsla(335,43%,16%)",
|
||||
d300: "hsla(335,47%,21%)",
|
||||
d400: "hsla(335,51%,22%)",
|
||||
d500: "hsla(335,57%,27%)",
|
||||
d600: "hsla(336,75%,40%)",
|
||||
d700: "hsla(336,80%,58%)",
|
||||
d800: "hsla(336,74%,51%)",
|
||||
d900: "hsla(341,90%,67%)",
|
||||
};
|
||||
|
||||
const grayAlpha = {
|
||||
d100: "rgba(255,255,255,0.06)",
|
||||
d200: "hsla(0,0%,100%,0.09)",
|
||||
d300: "hsla(0,0%,100%,0.13)",
|
||||
d400: "hsla(0,0%,100%,0.14)",
|
||||
d500: "hsla(0,0%,100%,0.24)",
|
||||
d600: "hsla(0,0%,100%,0.51)",
|
||||
d700: "hsla(0,0%,100%,0.54)",
|
||||
d800: "hsla(0,0%,100%,0.47)",
|
||||
d900: "hsla(0,0%,100%,0.61)",
|
||||
};
|
||||
|
||||
const d1000 = {
|
||||
gray: 'hsla(0,0%,93%)',
|
||||
blue: 'hsla( 206,100%,96%)',
|
||||
red: "hsla( 353,90%,96%)",
|
||||
amber: "hsla( 40,94%,93%))",
|
||||
green: "hsla(136,73%,94%)",
|
||||
teal: "hsla(166,71%,93%)",
|
||||
purple: "hsla(281,73%,96%)",
|
||||
pink: "hsla( 333,90%,96%)",
|
||||
grayAlpha: "hsla(0,0%,100%,0.92)"
|
||||
}
|
||||
|
||||
const background = {
|
||||
d100: 'hsla(0,0%,4%)',
|
||||
d200: 'hsla(0,0%,0%)'
|
||||
};
|
||||
const contrastFg = '#ffffff';
|
||||
const focusBorder = `0 0 0 1px ${grayAlpha.d600}, 0px 0px 0px 4px rgba(255,255,255,0.24)`;
|
||||
const focusColor = blue.d900
|
||||
|
||||
const text = {
|
||||
primary: {
|
||||
base: d1000.gray,
|
||||
surface: gray.d900,
|
||||
},
|
||||
info: {
|
||||
base: d1000.amber,
|
||||
surface: amber.d900,
|
||||
},
|
||||
danger: {
|
||||
base: d1000.red,
|
||||
surface: red.d900,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
gray,
|
||||
blue,
|
||||
red,
|
||||
amber,
|
||||
green,
|
||||
teal,
|
||||
purple,
|
||||
pink,
|
||||
grayAlpha,
|
||||
background,
|
||||
contrastFg,
|
||||
focusBorder,
|
||||
focusColor,
|
||||
d1000,
|
||||
text
|
||||
};
|
||||
})()
|
||||
|
||||
export const [lightClass, theme] = createTheme({
|
||||
...constants,
|
||||
space,
|
||||
font,
|
||||
color: light,
|
||||
});
|
||||
|
||||
export const darkClass = createTheme(theme, {
|
||||
...theme,
|
||||
...constants,
|
||||
space,
|
||||
font,
|
||||
color: dark,
|
||||
});
|
||||
42
packages/www/src/ui/utility.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { theme } from "./theme";
|
||||
|
||||
export const utility = {
|
||||
textLine() {
|
||||
return {
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
} as any;
|
||||
},
|
||||
stack(space: keyof (typeof theme)["space"]) {
|
||||
return {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.space[space],
|
||||
} as any;
|
||||
},
|
||||
row(space: keyof (typeof theme)["space"]) {
|
||||
return {
|
||||
display: "flex",
|
||||
gap: theme.space[space],
|
||||
} as any;
|
||||
},
|
||||
|
||||
text: {
|
||||
line: {
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
} as any,
|
||||
label: {
|
||||
fontWeight: 500,
|
||||
letterSpacing: 0.5,
|
||||
textTransform: "uppercase",
|
||||
fontFamily: theme.font.family.code,
|
||||
} as any,
|
||||
pre: {
|
||||
whiteSpace: "pre-wrap",
|
||||
overflowWrap: "anywhere",
|
||||
} as any,
|
||||
},
|
||||
};
|
||||