Merge pull request 'theme-ui' (#2443) from hsjobeki/clan-core:theme-ui into main

This commit is contained in:
clan-bot
2024-11-19 14:39:24 +00:00
38 changed files with 729 additions and 37 deletions

View File

@@ -219,7 +219,7 @@ export function SelectInput(props: SelectInputpProps) {
<span class="label-text text-neutral">{props.helperText}</span>
)}
{props.error && (
<span class="label-text-alt font-bold text-error">
<span class="label-text-alt font-bold text-error-700">
{props.error}
</span>
)}

View File

@@ -60,7 +60,9 @@ export function TextInput(props: TextInputProps) {
<span class="label-text text-neutral">{props.helperText}</span>
)}
{props.error && (
<span class="label-text-alt font-bold text-error">{props.error}</span>
<span class="label-text-alt font-bold text-error-700">
{props.error}
</span>
)}
</div>
</label>

View File

@@ -132,7 +132,7 @@ interface UnsupportedProps {
const Unsupported = (props: UnsupportedProps) => (
<div>
{props.error && <div class="font-bold text-error">{props.error}</div>}
{props.error && <div class="font-bold text-error-700">{props.error}</div>}
<span>
Invalid or unsupported schema entry of type:{" "}
<b>{JSON.stringify(props.schema.type)}</b>
@@ -193,7 +193,7 @@ export function StringField<T extends FieldValues, R extends ResponseData>(
props.schema.type !== "integer"
) {
return (
<span class="text-error">
<span class="text-error-700">
Error cannot render the following as String input.
<Unsupported schema={props.schema} />
</span>
@@ -339,7 +339,9 @@ interface OptionSchemaProps {
}
export function OptionSchema(props: OptionSchemaProps) {
return (
<Switch fallback={<option class="text-error">Item spec unhandled</option>}>
<Switch
fallback={<option class="text-error-700">Item spec unhandled</option>}
>
<Match when={typeof props.itemSpec === "string" && props.itemSpec}>
{(o) => <option>{o()}</option>}
</Match>
@@ -444,7 +446,7 @@ export function ArrayFields<T extends FieldValues, R extends ResponseData>(
) {
if (props.schema.type !== "array") {
return (
<span class="text-error">
<span class="text-error-700">
Error cannot render the following as array.
<Unsupported schema={props.schema} />
</span>
@@ -621,7 +623,7 @@ export function ArrayFields<T extends FieldValues, R extends ResponseData>(
</ListValueDisplay>
)}
</For>
<span class="label-text-alt font-bold text-error">
<span class="label-text-alt font-bold text-error-700">
{fieldArray.error}
</span>
@@ -703,7 +705,7 @@ export function ObjectFields<T extends FieldValues, R extends ResponseData>(
) {
if (props.schema.type !== "object") {
return (
<span class="text-error">
<span class="text-error-700">
Error cannot render the following as Object
<Unsupported schema={props.schema} />
</span>
@@ -748,7 +750,7 @@ export function ObjectFields<T extends FieldValues, R extends ResponseData>(
/>
)}
{typeof propSchema === "boolean" && (
<span class="text-error">
<span class="text-error-700">
Schema: Object of Boolean not supported
</span>
)}

View File

@@ -19,7 +19,7 @@ export const Sidebar = (props: RouteSectionProps) => {
}));
return (
<aside class="w-80 rounded-xl border border-slate-900 bg-slate-800 pb-10">
<aside class="w-80 rounded-xl border border-slate-900 bg-slate-800 pb-10">
<div class="m-4 flex flex-col text-center capitalize text-white">
<span class="text-lg">{clanQuery.data?.name}</span>
<span class="text-sm">{clanQuery.data?.description}</span>

View File

@@ -92,7 +92,9 @@ export function FileInput(props: FileInputProps) {
aria-errormessage={`${props.name}-error`}
/>
{props.error && (
<span class="label-text-alt font-bold text-error">{props.error}</span>
<span class="label-text-alt font-bold text-error-700">
{props.error}
</span>
)}
</div>
</div>

View File

@@ -0,0 +1,20 @@
import { type JSX } from "solid-js";
type sizes = "small" | "medium" | "large";
const gapSizes: { [size in sizes]: string } = {
small: "gap-2",
medium: "gap-4",
large: "gap-6",
};
interface List {
children: JSX.Element;
gapSize: sizes;
}
export const List = (props: List) => {
const { children, gapSize } = props;
return <ul class={`flex flex-col ${gapSizes[gapSize]}`}> {children}</ul>;
};

View File

@@ -0,0 +1 @@
export { List } from "./List";

View File

@@ -103,7 +103,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
};
return (
<li>
<div class="card card-side m-2 bg-base-100">
<div class="card card-side m-2">
<figure class="pl-2">
<span
class="material-icons content-center text-5xl"

View File

@@ -1,4 +1,4 @@
import { children, Component, createSignal, type JSX } from "solid-js";
import { children, createSignal, type JSX } from "solid-js";
import { useFloating } from "@/src/floating";
import {
autoUpdate,

View File

@@ -47,7 +47,9 @@ export function SelectInput<T extends FieldValues, R extends ResponseData>(
</select>
{props.error && (
<span class="label-text-alt font-bold text-error">{props.error}</span>
<span class="label-text-alt font-bold text-error-700">
{props.error}
</span>
)}
</label>
);

View File

@@ -0,0 +1,14 @@
import { List } from "@/src/components/Helpers";
import { SidebarListItem } from "../SidebarListItem";
export const SidebarFlyout = () => {
return (
<div class="sidebar__flyout">
<div class="sidebar__flyout__inner">
<List gapSize="small">
<SidebarListItem href="/settings" title="Settings" />
</List>
</div>
</div>
);
};

View File

@@ -0,0 +1,60 @@
import { createSignal, Show } from "solid-js";
import { Typography } from "@/src/components/Typography";
import { SidebarFlyout } from "./SidebarFlyout";
interface SidebarHeader {
clanName: string;
}
export const SidebarHeader = (props: SidebarHeader) => {
const { clanName } = props;
const [showFlyout, toggleFlyout] = createSignal(false);
function handleClick() {
toggleFlyout(!showFlyout());
}
const renderClanProfile = () => (
<div
class={`sidebar__profile ${showFlyout() ? "sidebar__profile--flyout" : ""}`}
>
<Typography
classes="sidebar__profile__character"
tag="span"
hierarchy="title"
size="m"
weight="bold"
color="primary"
inverted={true}
>
{clanName.slice(0, 1)}
</Typography>
</div>
);
const renderClanTitle = () => (
<Typography
classes="sidebar__title"
tag="h3"
hierarchy="body"
size="default"
weight="medium"
color="primary"
inverted={true}
>
{clanName}
</Typography>
);
return (
<header class="sidebar__header">
<div onClick={handleClick} class="sidebar__header__inner">
{renderClanProfile()}
{renderClanTitle()}
</div>
{showFlyout() && <SidebarFlyout />}
</header>
);
};

View File

@@ -0,0 +1,30 @@
import { A } from "@solidjs/router";
import { Typography } from "@/src/components/Typography";
interface SidebarListItem {
title: string;
href: string;
}
export const SidebarListItem = (props: SidebarListItem) => {
const { title, href } = props;
return (
<li class="sidebar__list__item">
<A class="sidebar__list__link" href={href}>
<Typography
classes="sidebar__list__content"
tag="span"
hierarchy="body"
size="s"
weight="normal"
color="primary"
inverted={true}
>
{title}
</Typography>
</A>
</li>
);
};

View File

@@ -0,0 +1,20 @@
.sidebar__flyout {
top: 0;
position: absolute;
z-index: theme(zIndex.30);
padding: theme(padding[1]);
width: 100%;
height: auto;
}
.sidebar__flyout__inner {
position: relative;
width: inherit;
height: inherit;
padding: theme(padding.12) theme(padding.3) theme(padding.3);
background-color: rgba(var(--clr-bg-inv-4) / 0.95);
border: 1px solid rgb(var(--clr-border-inv-4));
border-radius: theme(borderRadius.lg);
}

View File

@@ -0,0 +1,30 @@
.sidebar__header {
position: relative;
padding: 1px 1px 0;
cursor: pointer;
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgb(var(--clr-bg-inv-3));
border-bottom: 1px solid var(--clr-border-inv-3);
border-top-left-radius: theme(borderRadius.xl);
border-top-right-radius: theme(borderRadius.xl);
}
}
.sidebar__header__inner {
position: relative;
z-index: theme(zIndex.40);
display: flex;
align-items: center;
gap: 0 theme(gap.3);
padding: theme(padding.3) theme(padding.3);
}

View File

@@ -0,0 +1,46 @@
.sidebar__list__item {
position: relative;
cursor: theme(cursor.pointer);
&:after {
content: "";
position: absolute;
z-index: theme(zIndex.10);
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: theme(borderRadius.md);
transform: scale(0.98);
transition: transform 0.24s ease-in-out;
}
&:hover:after {
background: rgb(var(--clr-bg-inv-acc-2));
transform: scale(theme(scale.100));
transition: transform 0.24s ease-in-out;
}
&:active {
transform: scale(0.99);
transition: transform 0.08s ease-in-out;
}
&:active:after {
background: rgb(var(--clr-bg-inv-acc-3));
transform: scale(theme(scale.100));
}
}
.sidebar__list__link {
position: relative;
z-index: 20;
display: block;
padding: theme(padding.3);
}
.sidebar__list__content {
position: relative;
z-index: 20;
}

View File

@@ -0,0 +1,19 @@
.sidebar__profile {
display: flex;
justify-content: center;
align-items: center;
width: theme(width.8);
height: theme(height.8);
background: rgb(var(--clr-bg-inv-4));
border-radius: 50%;
}
.sidebar__profile--flyout {
background: rgb(var(--clr-bg-def-2));
}
.sidebar__profile--flyout > .sidebar__profile__character {
color: rgb(var(--clr-fg-def-1)) !important;
}

View File

@@ -0,0 +1,29 @@
/* Sidebar Elements */
@import "./sidebar-header";
@import "./sidebar-flyout";
@import "./sidebar-list-item";
@import "./sidebar-profile";
/* Sidebar Structure */
.sidebar {
min-width: theme(width.72);
height: 100%;
background-color: rgb(var(--clr-bg-inv-2));
border: 1px solid rgb(var(--clr-border-inv-2));
border-radius: theme(borderRadius.xl);
}
.sidebar__body {
display: flex;
flex-direction: column;
gap: theme(padding.2);
padding: theme(padding.4) theme(padding.2);
}
.sidebar__section {
padding: theme(padding.2);
background-color: rgba(var(--clr-bg-inv-3) / 0.9);
border-radius: theme(borderRadius.md);
}

View File

@@ -0,0 +1,93 @@
import { For, createEffect, Show, type JSX, children } from "solid-js";
import { A, RouteSectionProps } from "@solidjs/router";
import { activeURI } from "@/src/App";
import { createQuery } from "@tanstack/solid-query";
import { callApi } from "@/src/api";
import { AppRoute, routes } from "@/src/index";
import { List } from "../Helpers";
import { SidebarHeader } from "./SidebarHeader";
import { SidebarListItem } from "./SidebarListItem";
import "./css/sidebar.css";
import { Typography } from "../Typography";
export const SidebarSection = (props: {
title: string;
children: JSX.Element;
}) => {
const { title, children } = props;
return (
<details class="sidebar__section accordeon" open>
<summary class="accordeon__header">
<Typography
classes="uppercase"
tag="p"
hierarchy="body"
size="xs"
weight="normal"
color="tertiary"
inverted={true}
>
{title}
</Typography>
</summary>
<div class="accordeon__body">{children}</div>
</details>
);
};
export const Sidebar = (props: RouteSectionProps) => {
createEffect(() => {
console.log("machines");
console.log(routes);
});
const query = createQuery(() => ({
queryKey: [activeURI(), "meta"],
queryFn: async () => {
const curr = activeURI();
if (curr) {
const result = await callApi("show_clan_meta", { uri: curr });
if (result.status === "error") throw new Error("Failed to fetch data");
return result.data;
}
},
}));
return (
<div class="sidebar bg-transparent/95">
<Show
when={query.data}
fallback={<SidebarHeader clanName={"Untitled"} />}
>
{(meta) => <SidebarHeader clanName={meta().name} />}
</Show>
<div class="sidebar__body">
<For each={routes.filter((r) => !r.hidden && r.path != "/clans")}>
{(route: AppRoute) => (
<Show when={route.children}>
{(children) => (
<SidebarSection title={route.label}>
<ul>
<For each={children().filter((r) => !r.hidden)}>
{(child) => (
<SidebarListItem
href={`${route.path}${child.path}`}
title={child.label}
/>
)}
</For>
</ul>
</SidebarSection>
)}
</Show>
)}
</For>
</div>
</div>
);
};

View File

@@ -63,7 +63,9 @@ export function TextInput<T extends FieldValues, R extends ResponseData>(
</Show>
</div>
{props.error && (
<span class="label-text-alt font-bold text-error">{props.error}</span>
<span class="label-text-alt font-bold text-error-700">
{props.error}
</span>
)}
</label>
);

View File

@@ -0,0 +1,23 @@
.fnt-clr-primary {
color: (rgb(--clr-fg-def-1));
}
.fnt-clr-secondary {
color: rgb(var(--clr-fg-def-2));
}
.fnt-clr-tertiary {
color: rgb(var(--clr-fg-def-3));
}
.fnt-clr-primary.fnt-clr--inverted {
color: rgb(var(--clr-fg-inv-1));
}
.fnt-clr-secondary.fnt-clr--inverted {
color: rgb(var(--clr-fg-inv-2));
}
.fnt-clr-tertiary.fnt-clr--inverted {
color: rgb(var(--clr-fg-inv-3));
}

View File

@@ -0,0 +1,3 @@
@import "./typography-body.css";
@import "./typography-title.css";
@import "./typography-headline.css";

View File

@@ -0,0 +1,17 @@
.fnt-body-default {
font-size: 1rem;
line-height: 132%;
letter-spacing: 3%;
}
.fnt-body-s {
font-size: 0.925rem;
line-height: 132%;
letter-spacing: 3%;
}
.fnt-body-xs {
font-size: 0.875rem;
line-height: 132%;
letter-spacing: 3%;
}

View File

@@ -0,0 +1,17 @@
.fnt-headline-default {
font-size: 1.5rem;
line-height: 116%;
letter-spacing: 1%;
}
.fnt-headline-m {
font-size: 1.75rem;
line-height: 116%;
letter-spacing: 1%;
}
.fnt-headline-l {
font-size: 2rem;
line-height: 116%;
letter-spacing: 1%;
}

View File

@@ -0,0 +1,17 @@
.fnt-title-default {
font-size: 1.125rem;
line-height: 124%;
letter-spacing: 3%;
}
.fnt-title-m {
font-size: 1.25rem;
line-height: 124%;
letter-spacing: 3%;
}
.fnt-title-l {
font-size: 1.375rem;
line-height: 124%;
letter-spacing: 3%;
}

View File

@@ -0,0 +1,26 @@
@import "./typography-hierarchy/";
@import "./typography-color.css";
.fnt-weight-normal {
font-weight: normal;
}
.fnt-weight-medium {
font-weight: medium;
}
.fnt-weight-bold {
font-weight: bold;
}
.fnt-weight-normal.fnt-clr--inverted {
font-weight: light;
}
.fnt-weight-medium.fnt-clr--inverted {
font-weight: normal;
}
.fnt-weight-bold.fnt-clr--inverted {
font-weight: 600;
}

View File

@@ -0,0 +1,96 @@
import { type JSX } from "solid-js";
import { Dynamic } from "solid-js/web";
import cx from "classnames";
import "./css/typography.css";
type Hierarchy = "body" | "title" | "headline";
type Color = "primary" | "secondary" | "tertiary";
type Weight = "normal" | "medium" | "bold";
type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4";
const colorMap: Record<Color, string> = {
primary: cx("fnt-clr-primary"),
secondary: cx("fnt-clr-secondary"),
tertiary: cx("fnt-clr-tertiary"),
};
// type Size = "default" | "xs" | "s" | "m" | "l";
interface SizeForHierarchy {
body: {
default: string;
xs: string;
s: string;
};
headline: {
default: string;
m: string;
l: string;
};
title: {
default: string;
m: string;
l: string;
};
}
type AllowedSizes<H extends Hierarchy> = keyof SizeForHierarchy[H];
const sizeHierarchyMap: SizeForHierarchy = {
body: {
default: cx("fnt-body-default"),
xs: cx("fnt-body-xs"),
s: cx("fnt-body-s"),
// m: cx("fnt-body-m"),
// l: cx("fnt-body-l"),
},
headline: {
default: cx("fnt-headline-default"),
// xs: cx("fnt-headline-xs"),
// s: cx("fnt-headline-s"),
m: cx("fnt-headline-m"),
l: cx("fnt-headline-l"),
},
title: {
default: cx("fnt-title-default"),
// xs: cx("fnt-title-xs"),
// s: cx("fnt-title-s"),
m: cx("fnt-title-m"),
l: cx("fnt-title-l"),
},
};
const weightMap: Record<Weight, string> = {
normal: cx("fnt-weight-normal"),
medium: cx("fnt-weight-medium"),
bold: cx("fnt-weight-bold"),
};
interface TypographyProps<H extends Hierarchy> {
hierarchy: H;
weight: Weight;
color: Color;
inverted: boolean;
size: AllowedSizes<H>;
tag: Tag;
children: JSX.Element;
classes?: string;
}
export const Typography = <H extends Hierarchy>(props: TypographyProps<H>) => {
const { size, color, inverted, hierarchy, weight, tag, children, classes } =
props;
return (
<Dynamic
component={tag}
class={cx(
classes,
colorMap[color],
inverted && "fnt-clr--inverted",
sizeHierarchyMap[hierarchy][size] as string,
weightMap[weight],
)}
>
{children}
</Dynamic>
);
};

View File

@@ -1,5 +1,6 @@
@import "material-icons/iconfont/filled.css";
/* List of icons: https://marella.me/material-icons/demo/ */
/* @import url(./components/Typography/css/typography.css); */
@tailwind base;
@tailwind components;
@@ -21,8 +22,67 @@
src: url(../.fonts/Archiv0-Bold.otf) format("opentype");
}
:root {
--clr-bg-def-1: theme(colors.white);
--clr-bg-def-2: theme(colors.secondary.50);
--clr-bg-def-3: theme(colors.secondary.100);
--clr-bg-def-4: theme(colors.secondary.200);
--clr-bg-def-5: theme(colors.secondary.300);
--clr-border-def-1: theme(colors.secondary.50);
--clr-border-def-2: theme(colors.secondary.100);
--clr-border-def-3: theme(colors.secondary.200);
--clr-border-def-4: theme(colors.secondary.300);
--clr-border-def-5: theme(colors.secondary.400);
--clr-bg-inv-1: theme(colors.primary.600);
--clr-bg-inv-2: theme(colors.primary.700);
--clr-bg-inv-3: theme(colors.primary.800);
--clr-bg-inv-4: theme(colors.primary.900);
--clr-bg-inv-5: theme(colors.primary.950);
--clr-border-inv-1: theme(colors.secondary.800);
--clr-border-inv-2: theme(colors.secondary.900);
--clr-border-inv-3: theme(colors.secondary.900);
--clr-border-inv-4: theme(colors.secondary.950);
--clr-border-inv-5: theme(colors.black);
--clr-bg-inv-acc-1: theme(colors.secondary.500);
--clr-bg-inv-acc-2: theme(colors.secondary.600);
--clr-bg-inv-acc-3: theme(colors.secondary.700);
--clr-fg-def-1: theme(colors.secondary.950);
--clr-fg-def-2: theme(colors.secondary.900);
--clr-fg-def-3: theme(colors.secondary.700);
--clr-fg-def-4: theme(colors.secondary.500);
--clr-fg-inv-1: theme(colors.white);
--clr-fg-inv-2: theme(colors.secondary.100);
--clr-fg-inv-3: theme(colors.secondary.300);
--clr-fg-inv-4: theme(colors.secondary.400);
}
html {
@apply font-sans;
overflow-x: hidden;
overflow-y: scroll;
}
.accordeon {
display: flex;
flex-direction: column;
gap: theme(gap.3);
}
.accordeon__header {
padding: theme(padding.2) theme(padding[1.5]);
cursor: pointer;
}
.accordeon__header::-webkit-details-marker {
display: none;
}
.accordeon__body {
padding: theme(padding.2) 0 theme(padding.1);
}

View File

@@ -59,7 +59,7 @@ export const Header = (props: HeaderProps) => {
<span class="flex flex-col">
<Show when={query.data}>
{(meta) => [
<span class="text-primary">{meta().name}</span>,
<span class="text-primary-800">{meta().name}</span>,
<span class="text-neutral">{meta()?.description}</span>,
]}
</Show>

View File

@@ -1,8 +1,8 @@
import { Component, createEffect, Show } from "solid-js";
import { Header } from "./header";
import { Sidebar } from "../Sidebar";
import { Sidebar } from "@/src/components/Sidebar";
import { activeURI, clanList } from "../App";
import { redirect, RouteSectionProps, useNavigate } from "@solidjs/router";
import { RouteSectionProps, useNavigate } from "@solidjs/router";
export const Layout: Component<RouteSectionProps> = (props) => {
const navigate = useNavigate();
@@ -16,9 +16,10 @@ export const Layout: Component<RouteSectionProps> = (props) => {
navigate("/welcome");
}
});
return (
<div class="h-screen bg-base-100 p-4">
<div class="drawer lg:drawer-open ">
<div class="drawer h-full lg:drawer-open ">
<input
id="toplevel-drawer"
type="checkbox"

View File

@@ -103,7 +103,7 @@ export const CreateClan = () => {
{(field, props) => (
<label class="form-control w-full">
<div class="label">
<span class="label-text block after:ml-0.5 after:text-primary after:content-['*']">
<span class="label-text block after:ml-0.5 after:text-primary-800 after:content-['*']">
Name
</span>
</div>

View File

@@ -74,8 +74,8 @@ const EditClanForm = (props: EditClanFormProps) => {
<>
<figure class="p-1">
<div class="flex flex-col items-center">
<div class="text-3xl text-primary">{curr_name()}</div>
<div class="text-secondary">Wide settings</div>
<div class="text-3xl text-primary-800">{curr_name()}</div>
<div class="text-secondary-800">Wide settings</div>
</div>
</figure>
<figure>
@@ -100,7 +100,7 @@ const EditClanForm = (props: EditClanFormProps) => {
)}
</Field>
<div class="card-body">
<span class="text-xl text-primary">General</span>
<span class="text-xl text-primary-800">General</span>
<Field
name="name"
validate={[required("Please enter a unique name for the clan.")]}
@@ -108,7 +108,7 @@ const EditClanForm = (props: EditClanFormProps) => {
{(field, props) => (
<label class="form-control w-full">
<div class="label">
<span class="label-text block after:ml-0.5 after:text-primary after:content-['*']">
<span class="label-text block after:ml-0.5 after:text-primary-800 after:content-['*']">
Name
</span>
</div>
@@ -226,9 +226,9 @@ const AdminModuleForm = (props: AdminModuleFormProps) => {
return (
<Form onSubmit={handleSubmit}>
<div class="card-body">
<span class="text-xl text-primary">Administration</span>
<span class="text-xl text-primary-800">Administration</span>
<div class="grid grid-cols-12 gap-2">
<span class="col-span-12 text-lg text-neutral">
<span class="col-span-12 text-lg text-neutral-800">
Each of the following keys can be used to authenticate on machines
</span>
<For each={keys()}>

View File

@@ -59,7 +59,7 @@ const ClanItem = (props: ClanItemProps) => {
return (
<div class="stat">
<div class="stat-figure text-primary">
<div class="stat-figure text-primary-800">
<div class="join">
<button
class="join-item btn-sm"

View File

@@ -431,7 +431,7 @@ const MachineForm = (props: MachineDetailsProps) => {
</div>
</figure>
<div class="card-body">
<span class="text-xl text-primary">General</span>
<span class="text-xl text-primary-800">General</span>
{/*
<Field name="machine.tags" type="string[]">
{(field, props) => field.value}
@@ -536,14 +536,14 @@ const MachineForm = (props: MachineDetailsProps) => {
{(module) => (
<>
<div class="divider"></div>
<span class="text-xl text-primary">{module.name}</span>
<span class="text-xl text-primary-800">{module.name}</span>
{module.component}
</>
)}
</For>
<div class="divider"></div>
<span class="text-xl text-primary">Actions</span>
<span class="text-xl text-primary-800">Actions</span>
<div class="my-4 flex flex-col gap-6">
<span class="max-w-md text-neutral">
Installs the system for the first time. Used to bootstrap the remote

View File

@@ -14,7 +14,7 @@ const ModuleListItem = (props: { name: string; info: ModuleInfo }) => {
return (
<div class="stat">
<div class="stat-figure text-primary">
<div class="stat-figure text-primary-800">
<div class="join">more</div>
</div>

View File

@@ -18,7 +18,7 @@ export const Welcome = () => {
Build your own
</button>
<button
class="link w-full text-right text-primary"
class="link w-full text-right text-primary-800"
onClick={async () => {
const uri = await registerClan();
if (uri) {

View File

@@ -5,9 +5,7 @@ import core from "./tailwind/core-plugin";
/** @type {import('tailwindcss').Config} */
const config = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
theme: {},
daisyui: {
themes: [
{
@@ -25,7 +23,7 @@ const config = {
},
],
},
plugins: [typography, daisyui, core],
plugins: [typography, core, daisyui],
};
export default config;

View File

@@ -1,6 +1,10 @@
import plugin from "tailwindcss/plugin";
import { typography } from "./typography";
import theme from "tailwindcss/defaultTheme";
// @ts-expect-error: lib of tailwind has no types
import { parseColor } from "tailwindcss/lib/util/color";
/* Converts HEX color to RGB */
const toRGB = (value: string) => parseColor(value).color.join(" ");
export default plugin.withOptions(
(_options = {}) =>
@@ -10,6 +14,64 @@ export default plugin.withOptions(
// add configuration which is merged with the final config
() => ({
theme: {
extend: {
colors: {
white: toRGB("#ffffff"),
black: toRGB("#000000"),
primary: {
50: toRGB("#f4f9f9"),
100: toRGB("#dbeceb"),
200: toRGB("#b6d9d6"),
300: toRGB("#8abebc"),
400: toRGB("#478585"),
500: toRGB("#526f6f"),
600: toRGB("#4b6667"),
700: toRGB("#345253"),
800: toRGB("#2a4647"),
900: toRGB("#1f3536"),
950: toRGB("#162324"),
},
secondary: {
50: toRGB("#f7f9f9"),
100: toRGB("#e7f2f4"),
200: toRGB("#d7e8ea"),
300: toRGB("#afc6ca"),
400: toRGB("#8fb2b6"),
500: toRGB("#7b9a9e"),
600: toRGB("#4f747a"),
700: toRGB("#415e63"),
800: toRGB("#445f64"),
900: toRGB("#2b4347"),
950: toRGB("#0d1415"),
},
info: {
50: toRGB("#eff9ff"),
100: toRGB("#dff2ff"),
200: toRGB("#b8e8ff"),
300: toRGB("#78d6ff"),
400: toRGB("#2cc0ff"),
500: toRGB("#06aaf1"),
600: toRGB("#006ca7"),
700: toRGB("#006ca7"),
800: toRGB("#025b8a"),
900: toRGB("#084c72"),
950: toRGB("#06304b"),
},
error: {
50: toRGB("#fcf3f8"),
100: toRGB("#f9eaf4"),
200: toRGB("#f5d5e9"),
300: toRGB("#ea9ecb"),
400: toRGB("#e383ba"),
500: toRGB("#d75d9f"),
600: toRGB("#c43e81"),
700: toRGB("#a82e67"),
800: toRGB("#8c2855"),
900: toRGB("#75264a"),
950: toRGB("#461129"),
},
},
},
...typography,
},
}),