Compare commits

...

4 Commits

11 changed files with 278 additions and 268 deletions

View File

@@ -121,7 +121,7 @@ mkShell {
withFirefox = false;
withWebkit = true;
withChromium = false;
withChromiumHeadlessShell = false;
withChromiumHeadlessShell = true;
}
}
@@ -134,6 +134,6 @@ mkShell {
# this helps us avoid having to update the playwright js dependency everytime we update nixpkgs and vice versa
# see vitest.config.js for corresponding launch configuration
export PLAYWRIGHT_WEBKIT_EXECUTABLE=$(find -L "$PLAYWRIGHT_BROWSERS_PATH" -type f -name "pw_run.sh")
export PLAYWRIGHT_CHROMIUM_EXECUTABLE=$(find -L "$PLAYWRIGHT_BROWSERS_PATH" -type f -name "headless_shell")
'');
}

View File

@@ -4,8 +4,10 @@
importNpmLock,
clan-ts-api,
fonts,
ps,
playwright-driver,
}:
buildNpmPackage (_finalAttrs: {
buildNpmPackage (finalAttrs: {
pname = "clan-app-ui";
version = "0.0.1";
nodejs = nodejs_22;
@@ -32,36 +34,38 @@ buildNpmPackage (_finalAttrs: {
# todo figure out why this fails only inside of Nix
# Something about passing orientation in any of the Form stories is causing the browser to crash
# `npm run test-storybook-static` works fine in the devshell
#
# passthru = rec {
# storybook = buildNpmPackage {
# pname = "${finalAttrs.pname}-storybook";
# inherit (finalAttrs)
# version
# nodejs
# src
# npmDeps
# npmConfigHook
# preBuild
# ;
#
# nativeBuildInputs = finalAttrs.nativeBuildInputs ++ [
# ps
# ];
#
# npmBuildScript = "test-storybook-static";
#
# env = finalAttrs.env // {
# PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = 1;
# PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers.override {
# withChromiumHeadlessShell = true;
# }}";
# PLAYWRIGHT_HOST_PLATFORM_OVERRIDE = "ubuntu-24.04";
# };
#
# postBuild = ''
# mv storybook-static $out
# '';
# };
# };
passthru = rec {
storybook = buildNpmPackage {
pname = "${finalAttrs.pname}-storybook";
inherit (finalAttrs)
version
nodejs
src
npmDeps
npmConfigHook
;
nativeBuildInputs = finalAttrs.nativeBuildInputs ++ [
ps
];
npmBuildScript = "test-storybook-static";
env = {
PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers.override {
withChromiumHeadlessShell = true;
}}";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
};
preBuild = finalAttrs.preBuild + ''
export PLAYWRIGHT_CHROMIUM_EXECUTABLE=$(find -L "$PLAYWRIGHT_BROWSERS_PATH" -type f -name "headless_shell")
'';
postBuild = ''
mv storybook-static $out
'';
};
};
})

View File

@@ -16,9 +16,9 @@
"knip": "knip --fix",
"storybook-build": "storybook build",
"storybook-dev": "storybook dev -p 6006",
"test-storybook": "vitest run --project storybook",
"test-storybook": "vitest run --project storybook --reporter verbose",
"test-storybook-update-snapshots": "vitest run --project storybook --update",
"test-storybook-static": "npm run storybook-build && concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'http-server storybook-static --port 6006 --silent' 'wait-on tcp:127.0.0.1:6006 && npm run test-storybook'"
"test-storybook-static": "npm run storybook-build && concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'http-server storybook-static -a 127.0.0.1 -p 6006 --silent' 'wait-on tcp:127.0.0.1:6006 && npm run test-storybook'"
},
"license": "MIT",
"devDependencies": {

View File

@@ -1,7 +1,7 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Button, ButtonProps } from "./Button";
import { Component } from "solid-js";
import { expect, fn, waitFor, within } from "storybook/test";
import { expect, fn, within } from "storybook/test";
import { StoryContext } from "@kachurun/storybook-solid-vite";
const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor;

View File

@@ -11,6 +11,59 @@ import { Button } from "../Button/Button";
const meta: Meta<ModalProps> = {
title: "Components/Modal",
component: Modal,
render: (args: ModalProps) => (
<Modal
{...args}
children={
<form class="flex flex-col gap-5">
<Fieldset legend="General">
{(props: FieldsetFieldProps) => (
<>
<TextInput
{...props}
label="First Name"
size="s"
required={true}
input={{ placeholder: "Ron" }}
/>
<TextInput
{...props}
label="Last Name"
size="s"
required={true}
input={{ placeholder: "Burgundy" }}
/>
<TextArea
{...props}
label="Bio"
size="s"
input={{
placeholder: "Tell us a bit about yourself",
rows: 8,
}}
/>
<Checkbox
{...props}
size="s"
label="Accept Terms"
required={true}
/>
</>
)}
</Fieldset>
<div class="flex w-full items-center justify-end gap-4">
<Button size="s" hierarchy="secondary" onClick={close}>
Cancel
</Button>
<Button size="s" type="submit" hierarchy="primary" onClick={close}>
Save
</Button>
</div>
</form>
}
/>
),
};
export default meta;
@@ -21,50 +74,5 @@ export const Default: Story = {
args: {
title: "Example Modal",
onClose: fn(),
children: (
<form class="flex flex-col gap-5">
<Fieldset legend="General">
{(props: FieldsetFieldProps) => (
<>
<TextInput
{...props}
label="First Name"
size="s"
required={true}
input={{ placeholder: "Ron" }}
/>
<TextInput
{...props}
label="Last Name"
size="s"
required={true}
input={{ placeholder: "Burgundy" }}
/>
<TextArea
{...props}
label="Bio"
size="s"
input={{ placeholder: "Tell us a bit about yourself", rows: 8 }}
/>
<Checkbox
{...props}
size="s"
label="Accept Terms"
required={true}
/>
</>
)}
</Fieldset>
<div class="flex w-full items-center justify-end gap-4">
<Button size="s" hierarchy="secondary" onClick={close}>
Cancel
</Button>
<Button size="s" type="submit" hierarchy="primary" onClick={close}>
Save
</Button>
</div>
</form>
),
},
};

View File

@@ -7,11 +7,9 @@ import {
} from "@solidjs/router";
import { Sidebar } from "@/src/components/Sidebar/Sidebar";
import { Suspense } from "solid-js";
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query";
import { addClanURI, resetStore } from "@/src/stores/clan";
import { SolidQueryDevtools } from "@tanstack/solid-query-devtools";
import { encodeBase64 } from "@/src/hooks/clan";
import { ApiClientProvider } from "@/src/hooks/ApiClient";
import {
ApiCall,
OperationArgs,

View File

@@ -14,6 +14,7 @@ import { splitProps } from "solid-js";
import { Typography } from "@/src/components/Typography/Typography";
import { MachineTags } from "@/src/components/Form/MachineTags";
import { setValue } from "@modular-forms/solid";
import { StoryContext } from "@kachurun/storybook-solid-vite";
type Story = StoryObj<SidebarPaneProps>;
@@ -30,6 +31,13 @@ const profiles = {
const meta: Meta<SidebarPaneProps> = {
title: "Components/SidebarPane",
component: SidebarPane,
decorators: [
(
Story: StoryObj<SidebarPaneProps>,
context: StoryContext<SidebarPaneProps>,
) =>
() => <Story {...context.args} />,
],
};
export default meta;
@@ -40,133 +48,140 @@ export const Default: Story = {
onClose: () => {
console.log("closing");
},
children: (
<>
<SidebarSectionForm
title="General"
schema={v.object({
firstName: v.pipe(
v.string(),
v.nonEmpty("Please enter a first name."),
),
lastName: v.pipe(
v.string(),
v.nonEmpty("Please enter a last name."),
),
bio: v.string(),
shareProfile: v.optional(v.boolean()),
})}
initialValues={profiles.ron}
onSubmit={async () => {
console.log("saving general");
}}
>
{({ editing, Field }) => (
<div class="flex flex-col gap-3">
<Field name="firstName">
{(field, input) => (
<TextInput
{...field}
size="s"
inverted
label="First Name"
value={field.value}
required
readOnly={!editing}
orientation="horizontal"
input={input}
/>
)}
</Field>
<Divider />
<Field name="lastName">
{(field, input) => (
<TextInput
{...field}
size="s"
inverted
label="Last Name"
value={field.value}
required
readOnly={!editing}
orientation="horizontal"
input={input}
/>
)}
</Field>
<Divider />
<Field name="bio">
{(field, input) => (
<TextArea
{...field}
value={field.value}
size="s"
label="Bio"
inverted
readOnly={!editing}
orientation="horizontal"
input={{ ...input, rows: 4 }}
/>
)}
</Field>
<Field name="shareProfile" type="boolean">
{(field, input) => {
return (
<Checkbox
{...splitProps(field, ["value"])[1]}
defaultChecked={field.value}
},
// We have to provide children within a custom render function to ensure we aren't creating any reactivity outside the
// solid-js scope.
render: (args: SidebarPaneProps) => (
<SidebarPane
{...args}
children={
<>
<SidebarSectionForm
title="General"
schema={v.object({
firstName: v.pipe(
v.string(),
v.nonEmpty("Please enter a first name."),
),
lastName: v.pipe(
v.string(),
v.nonEmpty("Please enter a last name."),
),
bio: v.string(),
shareProfile: v.optional(v.boolean()),
})}
initialValues={profiles.ron}
onSubmit={async () => {
console.log("saving general");
}}
>
{({ editing, Field }) => (
<div class="flex flex-col gap-3">
<Field name="firstName">
{(field, input) => (
<TextInput
{...field}
size="s"
label="Share"
inverted
label="First Name"
value={field.value}
required
readOnly={!editing}
orientation="horizontal"
input={input}
/>
);
}}
</Field>
</div>
)}
</SidebarSectionForm>
<SidebarSectionForm
title="Tags"
schema={v.object({
tags: v.pipe(v.array(v.string()), v.nonEmpty()),
})}
initialValues={profiles.ron}
onSubmit={async (values) => {
console.log("saving tags", values);
}}
>
{({ editing, Field, formStore }) => (
<Field name="tags" type="string[]">
{(field, props) => (
<MachineTags
{...splitProps(field, ["value"])[1]}
size="s"
onChange={(newVal) => {
// Workaround for now, until we manage to use native events
setValue(formStore, field.name, newVal);
)}
</Field>
<Divider />
<Field name="lastName">
{(field, input) => (
<TextInput
{...field}
size="s"
inverted
label="Last Name"
value={field.value}
required
readOnly={!editing}
orientation="horizontal"
input={input}
/>
)}
</Field>
<Divider />
<Field name="bio">
{(field, input) => (
<TextArea
{...field}
value={field.value}
size="s"
label="Bio"
inverted
readOnly={!editing}
orientation="horizontal"
input={{ ...input, rows: 4 }}
/>
)}
</Field>
<Field name="shareProfile" type="boolean">
{(field, input) => {
return (
<Checkbox
{...splitProps(field, ["value"])[1]}
defaultChecked={field.value}
size="s"
label="Share"
inverted
readOnly={!editing}
orientation="horizontal"
input={input}
/>
);
}}
inverted
required
readOnly={!editing}
orientation="horizontal"
defaultValue={field.value}
/>
)}
</Field>
)}
</SidebarSectionForm>
<SidebarSection title="Simple">
<Typography tag="h2" hierarchy="title" size="m" inverted>
Static Content
</Typography>
<Typography hierarchy="label" size="s" inverted>
This is a non-form section with static content
</Typography>
</SidebarSection>
</>
),
},
</Field>
</div>
)}
</SidebarSectionForm>
<SidebarSectionForm
title="Tags"
schema={v.object({
tags: v.pipe(v.array(v.string()), v.nonEmpty()),
})}
initialValues={profiles.ron}
onSubmit={async (values) => {
console.log("saving tags", values);
}}
>
{({ editing, Field, formStore }) => (
<Field name="tags" type="string[]">
{(field, props) => (
<MachineTags
{...splitProps(field, ["value"])[1]}
size="s"
onChange={(newVal) => {
// Workaround for now, until we manage to use native events
setValue(formStore, field.name, newVal);
}}
inverted
required
readOnly={!editing}
orientation="horizontal"
defaultValue={field.value}
/>
)}
</Field>
)}
</SidebarSectionForm>
<SidebarSection title="Simple">
<Typography tag="h2" hierarchy="title" size="m" inverted>
Static Content
</Typography>
<Typography hierarchy="label" size="s" inverted>
This is a non-form section with static content
</Typography>
</SidebarSection>
</>
}
/>
),
};

View File

@@ -1,6 +1,5 @@
import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Toolbar, ToolbarProps } from "@/src/components/Toolbar/Toolbar";
import { Divider } from "@/src/components/Divider/Divider";
import { ToolbarButton } from "./ToolbarButton";
const meta: Meta<ToolbarProps> = {
@@ -13,61 +12,35 @@ export default meta;
type Story = StoryObj<ToolbarProps>;
export const Default: Story = {
args: {
children: (
<>
<ToolbarButton
name="select"
icon="Cursor"
description="Select my thing"
/>
<ToolbarButton
name="new-machine"
icon="NewMachine"
description="Select this thing"
/>
<Divider orientation="vertical" />
<ToolbarButton
name="modules"
icon="Modules"
selected={true}
description="Add service"
/>
<ToolbarButton name="ai" icon="AI" description="Call your AI Manager" />
</>
),
},
};
export const WithTooltip: Story = {
// We have to specify children inside a render function to avoid issues with reactivity outside a solid-js context.
// @ts-expect-error: args in storybook is not typed correctly. This is a storybook issue.
render: (args) => (
<div class="flex h-[80vh]">
<div class="mt-auto">
<Toolbar {...args} />
<Toolbar
{...args}
children={
<>
<ToolbarButton name="select" icon="Cursor" description="Select" />
<ToolbarButton
name="new-machine"
icon="NewMachine"
description="Select"
/>
<ToolbarButton
name="modules"
icon="Modules"
selected={true}
description="Select"
/>
<ToolbarButton name="ai" icon="AI" description="Select" />
</>
}
/>
</div>
</div>
),
args: {
children: (
<>
<ToolbarButton name="select" icon="Cursor" description="Select" />
<ToolbarButton
name="new-machine"
icon="NewMachine"
description="Select"
/>
<ToolbarButton
name="modules"
icon="Modules"
selected={true}
description="Select"
/>
<ToolbarButton name="ai" icon="AI" description="Select" />
</>
),
},
};

View File

@@ -1,7 +1,6 @@
import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Tooltip, TooltipProps } from "@/src/components/Tooltip/Tooltip";
import { Typography } from "@/src/components/Typography/Typography";
import { Button } from "@/src/components/Button/Button";
const meta: Meta<TooltipProps> = {
title: "Components/Tooltip",
@@ -13,6 +12,23 @@ const meta: Meta<TooltipProps> = {
</div>
),
],
render: (args: TooltipProps) => (
<div class="p-16">
<Tooltip
{...args}
children={
<Typography
hierarchy="body"
size="xs"
inverted={true}
weight="medium"
>
Your Clan is being created
</Typography>
}
/>
</div>
),
};
export default meta;
@@ -23,12 +39,6 @@ export const Default: Story = {
args: {
placement: "top",
inverted: false,
trigger: <Button hierarchy="primary">Trigger</Button>,
children: (
<Typography hierarchy="body" size="xs" inverted={true} weight="medium">
Your Clan is being created
</Typography>
),
},
};

View File

@@ -1,7 +1,6 @@
import { getStepStore, useStepper } from "@/src/hooks/stepper";
import {
createForm,
getError,
SubmitHandler,
valiForm,
} from "@modular-forms/solid";

View File

@@ -13,6 +13,8 @@ const dirname =
import viteConfig from "./vite.config";
const browser = process.env.BROWSER || "chromium";
export default mergeConfig(
viteConfig,
defineConfig({
@@ -42,9 +44,10 @@ export default mergeConfig(
provider: "playwright",
instances: [
{
browser: "webkit",
browser: "chromium",
launch: {
executablePath: process.env.PLAYWRIGHT_WEBKIT_EXECUTABLE,
// we specify this explicitly to avoid the version matching that playwright tries to do
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE,
},
},
],