diff --git a/pkgs/clan-app/ui/.storybook/preview.ts b/pkgs/clan-app/ui/.storybook/preview.ts index 4a5a284aa..f1c047986 100644 --- a/pkgs/clan-app/ui/.storybook/preview.ts +++ b/pkgs/clan-app/ui/.storybook/preview.ts @@ -1,5 +1,7 @@ import type { Preview } from "@kachurun/storybook-solid"; +import "../src/index.css"; + export const preview: Preview = { tags: ["autodocs"], parameters: { diff --git a/pkgs/clan-app/ui/src/components/Button/Button.stories.tsx b/pkgs/clan-app/ui/src/components/Button/Button.stories.tsx index ff2b579a0..c270a936a 100644 --- a/pkgs/clan-app/ui/src/components/Button/Button.stories.tsx +++ b/pkgs/clan-app/ui/src/components/Button/Button.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@kachurun/storybook-solid"; import { Button, ButtonProps } from "./Button"; -import FlashIcon from "@/icons/flash.svg"; +import Icon from "../icon"; const meta: Meta = { title: "Components/Button", @@ -12,12 +12,10 @@ export default meta; type Story = StoryObj; const children = "click me"; -const startIcon = ; export const Default: Story = { args: { children, - startIcon, }, }; @@ -41,3 +39,17 @@ export const Ghost: Story = { variant: "ghost", }, }; + +export const StartIcon: Story = { + args: { + ...Default.args, + startIcon: , + }, +}; + +export const EndIcon: Story = { + args: { + ...Default.args, + endIcon: , + }, +}; diff --git a/pkgs/clan-app/ui/src/components/TagList/TagList.css b/pkgs/clan-app/ui/src/components/TagList/TagList.css new file mode 100644 index 000000000..261c86336 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/TagList/TagList.css @@ -0,0 +1,7 @@ +div.tag-list { + @apply flex flex-wrap gap-2; + + span.tag { + @apply w-fit rounded-full px-3 py-2 bg-inv-4 fg-inv-1; + } +} diff --git a/pkgs/clan-app/ui/src/components/TagList/TagList.stories.tsx b/pkgs/clan-app/ui/src/components/TagList/TagList.stories.tsx new file mode 100644 index 000000000..43d286a01 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/TagList/TagList.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from "@kachurun/storybook-solid"; +import { TagList, TagListProps } from "./TagList"; + +const meta: Meta = { + title: "Components/TagList", + component: TagList, + decorators: [ + // wrap in a fixed width div so we can check that it wraps + (Story) => { + return ( +
+ +
+ ); + }, + ], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + values: [ + "Titan", + "Enceladus", + "Mimas", + "Dione", + "Iapetus", + "Tethys", + "Hyperion", + "Epimetheus", + ], + }, +}; diff --git a/pkgs/clan-app/ui/src/components/TagList/TagList.tsx b/pkgs/clan-app/ui/src/components/TagList/TagList.tsx new file mode 100644 index 000000000..ff039ceaa --- /dev/null +++ b/pkgs/clan-app/ui/src/components/TagList/TagList.tsx @@ -0,0 +1,21 @@ +import { Component, For } from "solid-js"; +import { Typography } from "@/src/components/Typography"; +import "./TagList.css"; + +export interface TagListProps { + values: string[]; +} + +export const TagList: Component = (props) => { + return ( +
+ + {(tag) => ( + + {tag} + + )} + +
+ ); +}; diff --git a/pkgs/clan-app/ui/src/components/inputBase/index.tsx b/pkgs/clan-app/ui/src/components/inputBase/index.tsx index 5142ee625..d3540181d 100644 --- a/pkgs/clan-app/ui/src/components/inputBase/index.tsx +++ b/pkgs/clan-app/ui/src/components/inputBase/index.tsx @@ -137,7 +137,7 @@ export const InputLabel = (props: InputLabelProps) => { weight="bold" size="xs" > - {"∗"} + <>* )} {props.help && ( diff --git a/pkgs/clan-app/ui/src/routes/machines/details.tsx b/pkgs/clan-app/ui/src/routes/machines/details.tsx index 4a377d13c..5ca253732 100644 --- a/pkgs/clan-app/ui/src/routes/machines/details.tsx +++ b/pkgs/clan-app/ui/src/routes/machines/details.tsx @@ -32,6 +32,7 @@ import { FileSelectorField, } from "@/src/components/fileSelect"; import { useClanContext } from "@/src/contexts/clan"; +import { TagList } from "@/src/components/TagList/TagList"; type MachineFormInterface = MachineData & { sshKey?: File; @@ -77,10 +78,12 @@ const LoadingBar = () => ( function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } + interface InstallMachineProps { name?: string; machine: MachineData; } + const InstallMachine = (props: InstallMachineProps) => { const { activeClanURI } = useClanContext(); @@ -381,6 +384,7 @@ const InstallMachine = (props: InstallMachineProps) => { interface MachineDetailsProps { initialData: MachineData; } + const MachineForm = (props: MachineDetailsProps) => { const [formStore, { Form, Field }] = // TODO: retrieve the correct initial values from API @@ -595,49 +599,47 @@ const MachineForm = (props: MachineDetailsProps) => { {(field, props) => ( -
- +
+ Tags{" "} - - {(tag) => ( - - - {tag} - - - )} - +
+ {/* alphabetically sort the tags */} + +
)} -
- - {(field, props) => ( - Hardware Configuration} - field={{field.value || "None"}} - /> - )} - -
- - {(field, props) => ( - <> + +
+ + {(field, props) => ( Disk schema} + label={Hardware Configuration} field={{field.value || "None"}} /> - - )} - -
+ )} +
+
+ + {(field, props) => ( + <> + Disk schema} + field={{field.value || "None"}} + /> + + )} + +
+