Merge pull request 'fix(clan-app): Misc ui styling fixes' (#3451) from amunsen/clan-core:ui-fixes into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3451
This commit is contained in:
hsjobeki
2025-04-30 10:18:42 +00:00
25 changed files with 487 additions and 216 deletions

3
.gitignore vendored
View File

@@ -16,6 +16,9 @@ nixos.qcow2
/docs/out /docs/out
**/.local.env **/.local.env
# MacOS stuff
**/.DS_store
# dream2nix # dream2nix
.dream2nix .dream2nix

View File

@@ -3,7 +3,7 @@ import tseslint from "typescript-eslint";
import tailwind from "eslint-plugin-tailwindcss"; import tailwind from "eslint-plugin-tailwindcss";
import pluginQuery from "@tanstack/eslint-plugin-query"; import pluginQuery from "@tanstack/eslint-plugin-query";
export default tseslint.config( const config = tseslint.config(
eslint.configs.recommended, eslint.configs.recommended,
...pluginQuery.configs["flat/recommended"], ...pluginQuery.configs["flat/recommended"],
...tseslint.configs.strict, ...tseslint.configs.strict,
@@ -30,3 +30,5 @@ export default tseslint.config(
}, },
}, },
); );
export default config;

View File

@@ -0,0 +1,32 @@
import { JSX } from "solid-js";
import { Typography } from "@/src/components/Typography";
interface FieldsetProps {
legend?: string;
children: JSX.Element;
class?: string;
}
export default function Fieldset(props: FieldsetProps) {
return (
<fieldset class="flex flex-col gap-y-2.5">
{props.legend && (
<div class="px-2">
<Typography
hierarchy="body"
tag="p"
size="s"
color="primary"
weight="medium"
>
{props.legend}
</Typography>
</div>
)}
<div class="flex flex-col gap-y-3 rounded-md border border-secondary-200 bg-secondary-50 p-5">
{props.children}
</div>
</fieldset>
);
}

View File

@@ -368,10 +368,10 @@ export function ListValueDisplay<T extends FieldValues, R extends ResponseData>(
const bottomMost = () => props.idx === 0; const bottomMost = () => props.idx === 0;
return ( return (
<div class="w-full border-l-4 border-gray-300"> <div class="w-full border-b border-secondary-200 px-2 pb-4">
<div class="flex w-full items-end gap-2 px-4"> <div class="flex w-full items-center gap-2">
{props.children} {props.children}
<div class="ml-4 min-w-fit pb-4"> <div class="ml-4 min-w-fit">
<Button <Button
variant="ghost" variant="ghost"
size="s" size="s"
@@ -541,7 +541,6 @@ export function ArrayFields<T extends FieldValues, R extends ResponseData>(
)} )}
</Field> </Field>
</Match> </Match>
<Match <Match
when={ when={
itemsSchema().type === "string" || itemsSchema().type === "string" ||
@@ -629,9 +628,11 @@ export function ArrayFields<T extends FieldValues, R extends ResponseData>(
</ListValueDisplay> </ListValueDisplay>
)} )}
</For> </For>
<span class=" font-bold text-error-700"> <Show when={fieldArray.error}>
{fieldArray.error} <span class="font-bold text-error-700">
</span> {fieldArray.error}
</span>
</Show>
{/* Add new item */} {/* Add new item */}
<DynForm <DynForm
@@ -651,14 +652,16 @@ export function ArrayFields<T extends FieldValues, R extends ResponseData>(
// Button for adding new items // Button for adding new items
components={{ components={{
before: ( before: (
<Button <div class="flex w-full justify-end pb-2">
variant="ghost" <Button
type="submit" variant="ghost"
endIcon={<Icon icon={"Plus"} />} type="submit"
class="capitalize" endIcon={<Icon size={14} icon={"Plus"} />}
> class="capitalize"
Add {itemsSchema().title} >
</Button> Add {itemsSchema().title}
</Button>
</div>
), ),
}} }}
// Add the new item to the FieldArray // Add the new item to the FieldArray

View File

@@ -110,18 +110,18 @@ export const MachineListItem = (props: MachineListItemProps) => {
setUpdating(false); setUpdating(false);
}; };
return ( return (
<div class="border rounded-lg border-def-2 p-3 m-2 w-64"> <div class="m-2 w-64 rounded-lg border p-3 border-def-2">
<figure class="h-fit rounded-xl border bg-def-2 border-def-5"> <figure class="h-fit rounded-xl border bg-def-2 border-def-5">
<RndThumbnail name={name} width={220} height={120} /> <RndThumbnail name={name} width={220} height={120} />
</figure> </figure>
<div class="flex-row justify-between gap-4 pt-2 px-2"> <div class="flex-row justify-between gap-4 px-2 pt-2">
<div class="flex flex-col"> <div class="flex flex-col">
<A href={`/machines/${name}`}> <A href={`/machines/${name}`}>
<Typography hierarchy="title" size="m" weight="bold"> <Typography hierarchy="title" size="m" weight="bold">
{name} {name}
</Typography> </Typography>
</A> </A>
<div class="text-slate-600 flex justify-between"> <div class="flex justify-between text-slate-600">
<div class="flex flex-nowrap"> <div class="flex flex-nowrap">
<span class="h-4"> <span class="h-4">
<Icon icon="Flash" class="h-4" font-size="inherit" /> <Icon icon="Flash" class="h-4" font-size="inherit" />
@@ -138,7 +138,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
popoverid={`menu-${props.name}`} popoverid={`menu-${props.name}`}
label={<Icon icon={"More"} />} label={<Icon icon={"More"} />}
> >
<ul class="z-[1] w-64 p-2 shadow bg-white "> <ul class="z-[1] w-64 bg-white p-2 shadow ">
<li> <li>
<Button <Button
variant="ghost" variant="ghost"

View File

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

View File

@@ -1,4 +1,4 @@
.sidebar__list__item { .sidebar__list__link {
position: relative; position: relative;
cursor: theme(cursor.pointer); cursor: theme(cursor.pointer);
@@ -19,12 +19,12 @@
&:hover:after { &:hover:after {
background: var(--clr-bg-inv-acc-2); background: var(--clr-bg-inv-acc-2);
transform: scale(theme(scale.100)); transform: scale(theme(scale.100));
transition: transform 0.24s ease-in-out; transition: transform 0.32s ease-in-out;
} }
&:active { &:active {
transform: scale(0.99); transform: scale(0.99);
transition: transform 0.08s ease-in-out; transition: transform 0.12s ease-in-out;
} }
&:active:after { &:active:after {
@@ -37,7 +37,13 @@
position: relative; position: relative;
z-index: 20; z-index: 20;
display: block; display: block;
padding: theme(padding.3); padding: theme(padding.2) theme(padding.3);
}
.sidebar__list__link.active {
&:after {
background: var(--clr-bg-inv-acc-3);
}
} }
.sidebar__list__content { .sidebar__list__content {

View File

@@ -9,6 +9,8 @@
.sidebar { .sidebar {
@apply bg-inv-2 h-full border border-solid border-inv-2 min-w-72 rounded-xl; @apply bg-inv-2 h-full border border-solid border-inv-2 min-w-72 rounded-xl;
display: flex;
flex-direction: column;
} }
.sidebar__body { .sidebar__body {
@@ -19,9 +21,9 @@
} }
.sidebar__section { .sidebar__section {
padding: theme(padding.2);
/* background-color: rgba(var(--clr-bg-inv-3) / 0.9); */
@apply bg-primary-800/90; @apply bg-primary-800/90;
padding: theme(padding.2);
border-radius: theme(borderRadius.md); border-radius: theme(borderRadius.md);
::marker { ::marker {

View File

@@ -19,20 +19,22 @@ export const SidebarSection = (props: {
return ( return (
<details class="sidebar__section accordeon" open> <details class="sidebar__section accordeon" open>
<summary class="accordeon__header"> <summary style="display: contents;">
<Typography <div class="accordeon__header">
class="inline-flex w-full gap-2 uppercase" <Typography
tag="p" class="inline-flex w-full gap-2 uppercase !tracking-wider"
hierarchy="body" tag="p"
size="xs" hierarchy="body"
weight="normal" size="xxs"
color="tertiary" weight="normal"
inverted={true} color="tertiary"
> inverted={true}
<Icon icon={props.icon} /> >
{title} <Icon class="opacity-90" icon={props.icon} size={13} />
<Icon icon="CaretDown" class="ml-auto" /> {title}
</Typography> <Icon icon="CaretDown" class="ml-auto" size={10} />
</Typography>
</div>
</summary> </summary>
<div class="accordeon__body">{children}</div> <div class="accordeon__body">{children}</div>
</details> </details>
@@ -60,7 +62,7 @@ export const Sidebar = (props: RouteSectionProps) => {
})); }));
return ( return (
<div class="sidebar opacity-95"> <div class="sidebar">
<Show <Show
when={query.data} when={query.data}
fallback={<SidebarHeader clanName={"Untitled"} />} fallback={<SidebarHeader clanName={"Untitled"} />}
@@ -81,7 +83,7 @@ export const Sidebar = (props: RouteSectionProps) => {
title={route.label} title={route.label}
icon={route.icon || "Paperclip"} icon={route.icon || "Paperclip"}
> >
<ul> <ul class="flex flex-col gap-y-0.5">
<For each={children().filter((r) => !r.hidden)}> <For each={children().filter((r) => !r.hidden)}>
{(child) => ( {(child) => (
<SidebarListItem <SidebarListItem

View File

@@ -17,7 +17,7 @@
} }
.fnt-body-xxs { .fnt-body-xxs {
font-size: 0.6875rem; font-size: 0.75rem;
line-height: 132%; line-height: 132%;
letter-spacing: 0.00688rem; letter-spacing: 0.00688rem;
} }

View File

@@ -0,0 +1,10 @@
.accordion {
@apply flex flex-col gap-y-5;
}
.accordion__title {
@apply flex h-5 cursor-pointer items-center justify-end gap-x-0.5 px-1 font-medium;
}
.accordion__body {
}

View File

@@ -0,0 +1,45 @@
import { createSignal, JSX, Show } from "solid-js";
import Icon from "../icon";
import { Button } from "../button";
import cx from "classnames";
import "./accordion.css";
interface AccordionProps {
title: string;
children: JSX.Element;
class?: string;
initiallyOpen?: boolean;
}
export default function Accordion(props: AccordionProps) {
const [isOpen, setIsOpen] = createSignal(props.initiallyOpen ?? false);
return (
<div class={cx(`accordion`, props.class)} tabindex="0">
<div onClick={() => setIsOpen(!isOpen())} class="accordion__title">
<Show
when={isOpen()}
fallback={
<Button
endIcon={<Icon size={12} icon={"CaretDown"} />}
variant="light"
size="s"
>
{props.title}
</Button>
}
>
<Button
endIcon={<Icon size={12} icon={"CaretUp"} />}
variant="dark"
size="s"
>
{props.title}
</Button>
</Show>
</div>
<Show when={isOpen()}>
<div class="accordion__body">{props.children}</div>
</Show>
</div>
);
}

View File

@@ -0,0 +1,31 @@
/* button DARK and states */
.button--dark {
@apply border border-solid border-secondary-950 bg-primary-800 text-white;
box-shadow: inset 1px 1px theme(backgroundColor.secondary.700);
&:disabled {
@apply disabled:bg-secondary-200 disabled:text-secondary-700 disabled:border-secondary-300;
}
& .button__icon {
color: theme(textColor.secondary.200);
}
}
.button--dark-hover:hover {
@apply hover:bg-secondary-900;
}
.button--dark-focus:focus {
@apply focus:border-secondary-900;
}
.button--dark-active:active {
@apply focus:border-secondary-900;
}
.button--dark-active:active {
@apply active:border-secondary-900 active:shadow-inner-primary-active;
}

View File

@@ -0,0 +1,37 @@
/* button LIGHT and states */
.button--light {
@apply border border-solid border-secondary-400 bg-secondary-100 text-secondary-950;
box-shadow: inset 1px 1px theme(backgroundColor.white);
&:disabled {
@apply disabled:bg-secondary-50 disabled:text-secondary-200 disabled:border-secondary-700;
}
& .button__icon {
color: theme(textColor.secondary.900);
}
}
.button--light-hover:hover {
@apply hover:bg-secondary-200;
}
.button--light-focus:focus {
@apply focus:bg-secondary-200;
& .button__label {
color: theme(textColor.secondary.900) !important;
}
}
.button--light-active:active {
@apply active:bg-secondary-200 border-secondary-600 active:text-secondary-900 active:shadow-inner-primary-active;
box-shadow: inset 2px 2px theme(backgroundColor.secondary.300);
& .button__label {
color: theme(textColor.secondary.900) !important;
}
}

View File

@@ -0,0 +1,54 @@
@import "./button-light.css";
@import "./button-dark.css";
.button {
@apply inline-flex items-center flex-shrink gap-1 justify-center p-4 font-semibold;
letter-spacing: 0.0275rem;
}
/* button SIZES */
.button--default {
padding: theme(padding.2) theme(padding.4);
height: theme(height.9);
border-radius: theme(borderRadius.DEFAULT);
&:has(> .button__icon--start):has(> .button__label) {
padding-left: theme(padding[2.5]);
}
&:has(> .button__icon--end):has(> .button__label) {
padding-right: theme(padding[2.5]);
}
}
.button--small {
padding: theme(padding[1.5]) theme(padding[3]);
height: theme(height.8);
border-radius: 3px;
&:has(> .button__icon--start):has(> .button__label) {
padding-left: theme(padding.2);
}
&:has(> .button__label):has(> .button__icon--end) {
padding-right: theme(padding.2);
}
}
/* button group */
.button-group .button:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.button-group .button:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.button-group .button:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

View File

@@ -1,6 +1,8 @@
import { splitProps, type JSX } from "solid-js"; import { splitProps, type JSX } from "solid-js";
import cx from "classnames"; import cx from "classnames";
import { Typography } from "../Typography"; import { Typography } from "../Typography";
//import './css/index.css'
import "./css/index.css";
type Variants = "dark" | "light" | "ghost"; type Variants = "dark" | "light" | "ghost";
type Size = "default" | "s"; type Size = "default" | "s";
@@ -9,50 +11,31 @@ const variantColors: (
disabled: boolean | undefined, disabled: boolean | undefined,
) => Record<Variants, string> = (disabled) => ({ ) => Record<Variants, string> = (disabled) => ({
dark: cx( dark: cx(
"border border-solid", "button--dark",
"border-secondary-950 bg-primary-900 text-white", !disabled && "button--dark-hover", // Hover state
"shadow-inner-primary", !disabled && "button--dark-focus", // Focus state
// Hover state !disabled && "button--dark-active", // Active state
// Focus state
// Active state
!disabled && "hover:border-secondary-900 hover:bg-secondary-700",
!disabled && "focus:border-secondary-900",
!disabled &&
"active:border-secondary-900 active:shadow-inner-primary-active",
// Disabled // Disabled
"disabled:bg-secondary-200 disabled:text-secondary-700 disabled:border-secondary-300", "disabled:bg-secondary-200 disabled:text-secondary-700 disabled:border-secondary-300",
), ),
light: cx( light: cx(
"border border-solid", "button--light",
"border-secondary-800 bg-secondary-100 text-secondary-800",
"shadow-inner-secondary", !disabled && "button--light-hover", // Hover state
// Hover state !disabled && "button--light-focus", // Focus state
// Focus state !disabled && "button--light-active", // Active state
// Active state
!disabled && "hover:bg-secondary-200 hover:text-secondary-900",
!disabled && "focus:bg-secondary-200 focus:text-secondary-900",
!disabled &&
"active:bg-secondary-200 active:text-secondary-950 active:shadow-inner-secondary-active",
// Disabled
"disabled:bg-secondary-50 disabled:text-secondary-200 disabled:border-secondary-700",
), ),
ghost: cx( ghost: cx(
// "shadow-inner-secondary", // "shadow-inner-secondary",
// Hover state !disabled && "hover:bg-secondary-200 hover:text-secondary-900", // Hover state
// Focus state !disabled && "focus:bg-secondary-200 focus:text-secondary-900", // Focus state
// Active state !disabled && "button--light-active", // Active state
!disabled && "hover:bg-secondary-200 hover:text-secondary-900",
!disabled && "focus:bg-secondary-200 focus:text-secondary-900",
!disabled &&
"active:bg-secondary-200 active:text-secondary-950 active:shadow-inner-secondary-active",
// Disabled
"disabled:bg-secondary-50 disabled:text-secondary-200 disabled:border-secondary-700",
), ),
}); });
const sizePaddings: Record<Size, string> = { const sizePaddings: Record<Size, string> = {
default: cx("rounded-[0.1875rem] px-4 py-2"), default: cx("button--default"),
s: cx("rounded-sm py-[0.375rem] px-3"), s: cx("button button--small"), //cx("rounded-sm py-[0.375rem] px-3"),
}; };
const sizeFont: Record<Size, string> = { const sizeFont: Record<Size, string> = {
@@ -77,26 +60,38 @@ export const Button = (props: ButtonProps) => {
"endIcon", "endIcon",
"class", "class",
]); ]);
const buttonInvertion = (variant: Variants) => {
return !(!variant || variant === "ghost" || variant === "light");
};
return ( return (
<button <button
class={cx( class={cx(
local.class, local.class,
// Layout "button", // default button class
"inline-flex items-center flex-shrink gap-2 justify-center", variantColors(props.disabled)[local.variant || "dark"], // button appereance
// Styles sizePaddings[local.size || "default"], // button size
"p-4",
sizePaddings[local.size || "default"],
// Colors
variantColors(props.disabled)[local.variant || "dark"],
//Font
"leading-none font-semibold",
sizeFont[local.size || "default"],
)} )}
{...other} {...other}
> >
{local.startIcon && <span class="h-4">{local.startIcon}</span>} {local.startIcon && (
{local.children && <span>{local.children}</span>} <span class="button__icon--start">{local.startIcon}</span>
{local.endIcon && <span class="h-4">{local.endIcon}</span>} )}
{local.children && (
<Typography
class="button__label"
hierarchy="label"
size={local.size || "default"}
color="inherit"
inverted={buttonInvertion(local.variant || "dark")}
weight="medium"
tag="span"
>
{local.children}
</Typography>
)}
{local.endIcon && <span class="button__icon--end">{local.endIcon}</span>}
</button> </button>
); );
}; };

View File

@@ -77,6 +77,7 @@ export type IconVariant = keyof typeof icons;
interface IconProps extends JSX.SvgSVGAttributes<SVGElement> { interface IconProps extends JSX.SvgSVGAttributes<SVGElement> {
icon: IconVariant; icon: IconVariant;
size?: number;
} }
const Icon: Component<IconProps> = (props) => { const Icon: Component<IconProps> = (props) => {
@@ -85,8 +86,8 @@ const Icon: Component<IconProps> = (props) => {
const IconComponent = icons[local.icon]; const IconComponent = icons[local.icon];
return IconComponent ? ( return IconComponent ? (
<IconComponent <IconComponent
width={16} width={iconProps.size || 16}
height={16} height={iconProps.size || 16}
viewBox="0 0 48 48" viewBox="0 0 48 48"
// @ts-expect-error: dont know, fix this type nit later // @ts-expect-error: dont know, fix this type nit later
ref={iconProps.ref} ref={iconProps.ref}

View File

@@ -11,11 +11,13 @@
font-weight: 400; font-weight: 400;
src: url(../.fonts/ArchivoSemiCondensed-Regular.woff2) format("woff2"); src: url(../.fonts/ArchivoSemiCondensed-Regular.woff2) format("woff2");
} }
@font-face { @font-face {
font-family: "Archivo"; font-family: "Archivo";
font-weight: 500; font-weight: 500;
src: url(../.fonts/ArchivoSemiCondensed-Medium.woff2) format("woff2"); src: url(../.fonts/ArchivoSemiCondensed-Medium.woff2) format("woff2");
} }
@font-face { @font-face {
font-family: "Archivo"; font-family: "Archivo";
font-weight: 600; font-weight: 600;
@@ -30,7 +32,7 @@
:root { :root {
--clr-bg-def-1: theme(colors.white); --clr-bg-def-1: theme(colors.white);
--clr-bg-def-2: theme(colors.secondary.50); --clr-bg-def-2: theme(colors.primary.50);
--clr-bg-def-3: theme(colors.secondary.100); --clr-bg-def-3: theme(colors.secondary.100);
--clr-bg-def-4: theme(colors.secondary.200); --clr-bg-def-4: theme(colors.secondary.200);
--clr-bg-def-5: theme(colors.secondary.300); --clr-bg-def-5: theme(colors.secondary.300);
@@ -72,6 +74,15 @@ html {
@apply font-sans; @apply font-sans;
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden; overflow-y: hidden;
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* Internet Explorer/Edge */
user-select: none;
/* Standard */
} }
.accordeon { .accordeon {
@@ -81,7 +92,7 @@ html {
} }
.accordeon__header { .accordeon__header {
padding: theme(padding.2) theme(padding[1.5]); padding: theme(padding.2) theme(padding[1.5]) theme(padding.1);
cursor: pointer; cursor: pointer;
} }
@@ -90,5 +101,4 @@ html {
} }
.accordeon__body { .accordeon__body {
padding: theme(padding.2) 0 theme(padding.1);
} }

View File

@@ -9,7 +9,7 @@ interface HeaderProps {
} }
export const Header = (props: HeaderProps) => { export const Header = (props: HeaderProps) => {
return ( return (
<div class="flex border-b px-6 py-4 border-def-3"> <div class="sticky top-0 z-20 flex items-center border-b bg-white/80 px-6 py-4 backdrop-blur-md border-def-3">
<div class="flex-none"> <div class="flex-none">
{props.showBack && <BackButton />} {props.showBack && <BackButton />}
<span class=" lg:hidden" data-tip="Menu"> <span class=" lg:hidden" data-tip="Menu">

View File

@@ -17,19 +17,11 @@ export const Layout: Component<RouteSectionProps> = (props) => {
return ( return (
<div class="h-screen w-full p-4 bg-def-2"> <div class="h-screen w-full p-4 bg-def-2">
<div class="h-full flex"> <div class="flex size-full flex-row-reverse">
<div <div class="my-2 ml-8 flex-1 overflow-x-hidden overflow-y-scroll rounded-lg border bg-def-1 border-def-3">
class="z-40 h-full overflow-hidden"
classList={{
hidden:
props.location.pathname === "welcome" || clanList().length === 0,
}}
>
<Sidebar {...props} />
</div>
<div class="w-full my-2 ml-8 overflow-x-hidden overflow-y-scroll rounded-lg border bg-def-1 border-def-3">
{props.children} {props.children}
</div> </div>
<Sidebar {...props} />
</div> </div>
</div> </div>
); );

View File

@@ -11,6 +11,9 @@ import { Match, Switch } from "solid-js";
import toast from "solid-toast"; import toast from "solid-toast";
import { MachineAvatar } from "./avatar"; import { MachineAvatar } from "./avatar";
import { DynForm } from "@/src/Form/form"; import { DynForm } from "@/src/Form/form";
import { Typography } from "@/src/components/Typography";
import Fieldset from "@/src/Form/fieldset";
import Accordion from "@/src/components/accordion";
type CreateMachineForm = OperationArgs<"create_machine">; type CreateMachineForm = OperationArgs<"create_machine">;
@@ -72,44 +75,80 @@ export function CreateMachine() {
<> <>
<Header title="Create Machine" /> <Header title="Create Machine" />
<div class="flex w-full p-4"> <div class="flex w-full p-4">
<div class="mt-4 w-full self-stretch px-2"> <div class="mt-4 w-full self-stretch px-8">
<Form onSubmit={handleSubmit} class="gap-2 flex flex-col"> <Form
onSubmit={handleSubmit}
class="mx-auto flex w-full max-w-2xl flex-col gap-y-6"
>
<Field <Field
name="opts.machine.name" name="opts.machine.name"
validate={[required("This field is required")]} validate={[required("This field is required")]}
> >
{(field, props) => ( {(field, props) => (
<> <>
<div class="flex justify-center mb-4 pb-4 border-b"> <div class="mb-4 flex justify-center border-b pb-4">
<MachineAvatar name={field.value} /> <MachineAvatar name={field.value} />
</div> </div>
<TextInput
inputProps={props}
value={`${field.value}`}
label={"name"}
error={field.error}
required
placeholder="New_machine"
/>
</> </>
)} )}
</Field> </Field>
<Field name="opts.machine.description"> <Fieldset legend="General">
{(field, props) => ( <Field
<TextInput name="opts.machine.name"
inputProps={props} validate={[required("This field is required")]}
value={`${field.value}`} >
label={"description"} {(field, props) => (
error={field.error} <>
placeholder="My awesome machine" <TextInput
/> inputProps={props}
)} value={`${field.value}`}
</Field> label={"name"}
<div class=" " tabindex="0"> error={field.error}
<input type="checkbox" /> required
<div class=" font-medium ">Deployment Settings</div> placeholder="New_machine"
<div class=""> />
</>
)}
</Field>
<Field name="opts.machine.description">
{(field, props) => (
<TextInput
inputProps={props}
value={`${field.value}`}
label={"description"}
error={field.error}
placeholder="My awesome machine"
/>
)}
</Field>
</Fieldset>
<Fieldset legend="Tags">
<Field name="opts.machine.tags" type="string[]">
{(field, props) => (
<div class="p-2">
<DynForm
initialValues={{ tags: ["all"] }}
schema={{
type: "object",
properties: {
tags: {
type: "array",
items: {
title: "Tag",
type: "string",
},
uniqueItems: true,
},
},
}}
/>
</div>
)}
</Field>
</Fieldset>
<Accordion title="Advanced">
<Fieldset>
<Field name="opts.machine.deploy.targetHost"> <Field name="opts.machine.deploy.targetHost">
{(field, props) => ( {(field, props) => (
<> <>
@@ -123,9 +162,10 @@ export function CreateMachine() {
</> </>
)} )}
</Field> </Field>
</div> </Fieldset>
</div> </Accordion>
<div class="mt-12 flex justify-end">
<footer class="flex justify-end gap-y-3 border-t border-secondary-200 pt-5">
<Button <Button
type="submit" type="submit"
disabled={formStore.submitting} disabled={formStore.submitting}
@@ -141,7 +181,7 @@ export function CreateMachine() {
<Match when={!formStore.submitting}>Create</Match> <Match when={!formStore.submitting}>Create</Match>
</Switch> </Switch>
</Button> </Button>
</div> </footer>
</Form> </Form>
</div> </div>
</div> </div>

View File

@@ -82,37 +82,31 @@ export const MachineListView: Component = () => {
size="s" size="s"
onClick={() => refresh()} onClick={() => refresh()}
startIcon={<Icon icon="Update" />} startIcon={<Icon icon="Update" />}
></Button> />
</span> </span>
<div class="border border-def-3"> <div class="button-group">
<span class="" data-tip="List View">
<Button
onclick={() => setView("list")}
variant={view() == "list" ? "dark" : "light"}
size="s"
startIcon={<Icon icon="List" />}
></Button>
</span>
<span class="" data-tip="Grid View">
<Button
onclick={() => setView("grid")}
variant={view() == "grid" ? "dark" : "light"}
size="s"
startIcon={<Icon icon="Grid" />}
></Button>
</span>
</div>
<span class="" data-tip="New Machine">
<Button <Button
onClick={() => navigate("create")} onclick={() => setView("list")}
variant={view() == "list" ? "dark" : "light"}
size="s" size="s"
variant="light" startIcon={<Icon icon="List" />}
startIcon={<Icon icon="Plus" />} />
> <Button
New Machine onclick={() => setView("grid")}
</Button> variant={view() == "grid" ? "dark" : "light"}
</span> size="s"
startIcon={<Icon icon="Grid" />}
/>
</div>
<Button
onClick={() => navigate("create")}
size="s"
variant="light"
startIcon={<Icon size={14} icon="Plus" />}
>
New Machine
</Button>
</> </>
} }
/> />

View File

@@ -19,9 +19,9 @@ interface CategoryProps {
} }
const Categories = (props: CategoryProps) => { const Categories = (props: CategoryProps) => {
return ( return (
<span class="ml-6 inline-flex h-full align-middle"> <span class="inline-flex h-full align-middle">
{props.categories.map((category) => ( {props.categories.map((category) => (
<span class="">{category}</span> <span class="text-sm font-normal">{category}</span>
))} ))}
</span> </span>
); );
@@ -32,10 +32,10 @@ interface RolesProps {
} }
const Roles = (props: RolesProps) => { const Roles = (props: RolesProps) => {
return ( return (
<div> <div class="flex flex-wrap items-center gap-2">
<span> <span>
<Typography hierarchy="body" size="xs"> <Typography hierarchy="body" size="xs">
Service Typography{" "} Service
</Typography> </Typography>
</span> </span>
{props.roles.map((role) => ( {props.roles.map((role) => (
@@ -54,9 +54,14 @@ const ModuleItem = (props: {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<div class={cx("rounded-lg shadow-md", props.class)}> <div
<div class="text-primary-800"> class={cx(
<div class=""> "col-span-1 flex flex-col gap-3 border-b border-secondary-200 pb-4",
props.class,
)}
>
{/* <div class="stat-figure text-primary-800">
<div class="join">
<Menu popoverid={`menu-${props.name}`} label={<Icon icon={"More"} />}> <Menu popoverid={`menu-${props.name}`} label={<Icon icon={"More"} />}>
<ul class="z-[1] w-52 p-2 shadow"> <ul class="z-[1] w-52 p-2 shadow">
<li> <li>
@@ -71,20 +76,26 @@ const ModuleItem = (props: {
</ul> </ul>
</Menu> </Menu>
</div> </div>
</div> </div> */}
<A href={`/modules/details/${name}`}> <header class="flex flex-col gap-4">
<div class="underline"> <A href={`/modules/details/${name}`}>
{name} <div class="">
<Categories categories={info.categories} /> <div class="flex flex-col">
<Categories categories={info.categories} />
<Typography hierarchy="title" size="m" weight="medium">
{name}
</Typography>
</div>
</div>
</A>
<div class="w-full">
<Typography hierarchy="body" size="xs">
{info.description}
</Typography>
</div> </div>
</A> </header>
<div class="w-full">
<Typography hierarchy="body" size="default">
{info.description}
</Typography>
</div>
<Roles roles={info.roles || []} /> <Roles roles={info.roles || []} />
</div> </div>
); );
@@ -113,38 +124,33 @@ export const ModuleList = () => {
title="Modules" title="Modules"
toolbar={ toolbar={
<> <>
<span class="" data-tip="Reload"> <Button
<Button variant="light"
variant="light" size="s"
size="s" onClick={() => refresh()}
onClick={() => refresh()} startIcon={<Icon icon="Update" />}
startIcon={<Icon icon="Update" />} />
></Button>
</span>
<div class="border border-def-3"> <div class="button-group">
<span class="" data-tip="List View"> <Button
<Button onclick={() => setView("list")}
onclick={() => setView("list")} variant={view() == "list" ? "dark" : "light"}
variant={view() == "list" ? "dark" : "light"} size="s"
size="s" startIcon={<Icon icon="List" />}
startIcon={<Icon icon="List" />} />
></Button>
</span> <Button
<span class="" data-tip="Grid View"> onclick={() => setView("grid")}
<Button variant={view() == "grid" ? "dark" : "light"}
onclick={() => setView("grid")} size="s"
variant={view() == "grid" ? "dark" : "light"} startIcon={<Icon icon="Grid" />}
size="s" />
startIcon={<Icon icon="Grid" />}
></Button>
</span>
</div> </div>
<span class="" data-tip="New Machine"> <span class="" data-tip="New Machine">
<Button <Button
size="s" size="s"
variant="light" variant="light"
startIcon={<Icon icon="CaretUp" />} startIcon={<Icon size={14} icon="CaretUp" />}
> >
Import Module Import Module
</Button> </Button>
@@ -156,10 +162,10 @@ export const ModuleList = () => {
<Match when={modulesQuery.isFetching}>Loading....</Match> <Match when={modulesQuery.isFetching}>Loading....</Match>
<Match when={modulesQuery.data}> <Match when={modulesQuery.data}>
<div <div
class="my-4 flex flex-wrap gap-6 px-3 py-2" class="grid gap-6 p-6"
classList={{ classList={{
"flex-col": view() === "list", "grid-cols-1": view() === "list",
"": view() === "grid", "grid-cols-2": view() === "grid",
}} }}
> >
<For each={modulesQuery.data}> <For each={modulesQuery.data}>

View File

@@ -0,0 +1,6 @@
module.exports = {
extends: ["stylelint-config-standard", "stylelint-config-tailwindcss"],
rules: {
// You can adjust rules here
},
};

View File

@@ -284,7 +284,7 @@ export default plugin.withOptions(
"inner-primary": "inner-primary":
"2px 2px 0px 0px var(--clr-bg-inv-acc-3, #415E63) inset", "2px 2px 0px 0px var(--clr-bg-inv-acc-3, #415E63) inset",
"inner-primary-active": "inner-primary-active":
"0px 0px 0px 1px #FFF, 0px 0px 0px 2px var(--clr-bg-inv-acc-4, #203637), -2px -2px 0px 0px var(--clr-bg-inv-acc-1, #7B9B9F) inset", "0px 0px 0px 1px #FFF, 0px 0px 0px 2px var(--clr-bg-inv-acc-4, #203637)",
"inner-secondary": "inner-secondary":
"-2px -2px 0px 0px #CEDFE2 inset, 2px 2px 0px 0px white inset", "-2px -2px 0px 0px #CEDFE2 inset, 2px 2px 0px 0px white inset",
"inner-secondary-active": "inner-secondary-active":