ui/machineTags: fix keyboard and select logic
This commit is contained in:
50
pkgs/clan-app/ui/src/components/Form/MachineTags.stories.tsx
Normal file
50
pkgs/clan-app/ui/src/components/Form/MachineTags.stories.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||||
|
import { MachineTags, MachineTagsProps } from "./MachineTags";
|
||||||
|
import { createForm, setValue } from "@modular-forms/solid";
|
||||||
|
import { Button } from "../Button/Button";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Components/MachineTags",
|
||||||
|
component: MachineTags,
|
||||||
|
} satisfies Meta<MachineTagsProps>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: () => {
|
||||||
|
const [formStore, { Field, Form }] = createForm<{ tags: string[] }>({
|
||||||
|
initialValues: { tags: ["nixos"] },
|
||||||
|
});
|
||||||
|
const handleSubmit = (values: { tags: string[] }) => {
|
||||||
|
console.log("submitting", values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const readonly = ["nixos"];
|
||||||
|
const options = ["foo"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<Field name="tags" type="string[]">
|
||||||
|
{(field, props) => (
|
||||||
|
<MachineTags
|
||||||
|
onChange={(newVal) => {
|
||||||
|
// Workaround for now, until we manage to use native events
|
||||||
|
setValue(formStore, field.name, newVal);
|
||||||
|
}}
|
||||||
|
name="Tags"
|
||||||
|
defaultOptions={options}
|
||||||
|
readonlyOptions={readonly}
|
||||||
|
readOnly={false}
|
||||||
|
defaultValue={field.value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Button type="submit" hierarchy="primary">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
import { Combobox } from "@kobalte/core/combobox";
|
import { Combobox } from "@kobalte/core/combobox";
|
||||||
import { FieldProps } from "./Field";
|
import { FieldProps } from "./Field";
|
||||||
import { ComponentProps, createSignal, For, Show, splitProps } from "solid-js";
|
import {
|
||||||
|
createEffect,
|
||||||
|
on,
|
||||||
|
createSignal,
|
||||||
|
For,
|
||||||
|
Show,
|
||||||
|
splitProps,
|
||||||
|
} from "solid-js";
|
||||||
import Icon from "../Icon/Icon";
|
import Icon from "../Icon/Icon";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Typography } from "@/src/components/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
@@ -14,22 +21,22 @@ import styles from "./MachineTags.module.css";
|
|||||||
export interface MachineTag {
|
export interface MachineTag {
|
||||||
value: string;
|
value: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
new?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MachineTagsProps = FieldProps & {
|
export type MachineTagsProps = FieldProps & {
|
||||||
name: string;
|
name: string;
|
||||||
input: ComponentProps<"select">;
|
onChange: (values: string[]) => void;
|
||||||
|
defaultValue?: string[];
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
defaultValue?: string[];
|
|
||||||
defaultOptions?: string[];
|
defaultOptions?: string[];
|
||||||
readonlyOptions?: string[];
|
readonlyOptions?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const uniqueOptions = (options: MachineTag[]) => {
|
const uniqueOptions = (options: MachineTag[]) => {
|
||||||
const record: Record<string, MachineTag> = {};
|
const record: Record<string, MachineTag> = {};
|
||||||
|
console.log("uniqueOptions", options);
|
||||||
options.forEach((option) => {
|
options.forEach((option) => {
|
||||||
// we want to preserve the first one we encounter
|
// we want to preserve the first one we encounter
|
||||||
// this allows us to prefix the default 'all' tag
|
// this allows us to prefix the default 'all' tag
|
||||||
@@ -41,11 +48,48 @@ const uniqueOptions = (options: MachineTag[]) => {
|
|||||||
const sortedOptions = (options: MachineTag[]) =>
|
const sortedOptions = (options: MachineTag[]) =>
|
||||||
options.sort((a, b) => a.value.localeCompare(b.value));
|
options.sort((a, b) => a.value.localeCompare(b.value));
|
||||||
|
|
||||||
const sortedAndUniqueOptions = (options: MachineTag[]) =>
|
const sortedAndUniqueOptions = (options: MachineTag[]) => {
|
||||||
sortedOptions(uniqueOptions(options));
|
const r = sortedOptions(uniqueOptions(options));
|
||||||
|
console.log("sortedAndUniqueOptions", r);
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
// customises how each option is displayed in the dropdown
|
export const MachineTags = (props: MachineTagsProps) => {
|
||||||
const ItemComponent =
|
const [local, rest] = splitProps(props, ["defaultValue"]);
|
||||||
|
|
||||||
|
// // convert default value string[] into MachineTag[]
|
||||||
|
const defaultValue = sortedAndUniqueOptions(
|
||||||
|
(local.defaultValue || []).map((value) => ({ value })),
|
||||||
|
);
|
||||||
|
|
||||||
|
// convert default options string[] into MachineTag[]
|
||||||
|
const [availableOptions, setAvailableOptions] = createSignal<MachineTag[]>(
|
||||||
|
sortedAndUniqueOptions([
|
||||||
|
...(props.readonlyOptions || []).map((value) => ({
|
||||||
|
value,
|
||||||
|
disabled: true,
|
||||||
|
})),
|
||||||
|
...(props.defaultOptions || []).map((value) => ({ value })),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedOptions, setSelectedOptions] =
|
||||||
|
createSignal<MachineTag[]>(defaultValue);
|
||||||
|
|
||||||
|
const handleToggle = (item: CollectionNode<MachineTag>) => () => {
|
||||||
|
setSelectedOptions((current) => {
|
||||||
|
const exists = current.find(
|
||||||
|
(option) => option.value === item.rawValue.value,
|
||||||
|
);
|
||||||
|
if (exists) {
|
||||||
|
return current.filter((option) => option.value !== item.rawValue.value);
|
||||||
|
}
|
||||||
|
return [...current, item.rawValue];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// customises how each option is displayed in the dropdown
|
||||||
|
const ItemComponent =
|
||||||
(inverted: boolean) => (props: { item: CollectionNode<MachineTag> }) => {
|
(inverted: boolean) => (props: { item: CollectionNode<MachineTag> }) => {
|
||||||
return (
|
return (
|
||||||
<Combobox.Item
|
<Combobox.Item
|
||||||
@@ -53,6 +97,7 @@ const ItemComponent =
|
|||||||
class={cx(styles.listboxItem, {
|
class={cx(styles.listboxItem, {
|
||||||
[styles.listboxItemInverted]: inverted,
|
[styles.listboxItemInverted]: inverted,
|
||||||
})}
|
})}
|
||||||
|
onClick={handleToggle(props.item)}
|
||||||
>
|
>
|
||||||
<Combobox.ItemLabel>
|
<Combobox.ItemLabel>
|
||||||
<Typography
|
<Typography
|
||||||
@@ -71,22 +116,7 @@ const ItemComponent =
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MachineTags = (props: MachineTagsProps) => {
|
let selectRef: HTMLSelectElement;
|
||||||
// convert default value string[] into MachineTag[]
|
|
||||||
const defaultValue = sortedAndUniqueOptions(
|
|
||||||
(props.defaultValue || []).map((value) => ({ value })),
|
|
||||||
);
|
|
||||||
|
|
||||||
// convert default options string[] into MachineTag[]
|
|
||||||
const [availableOptions, setAvailableOptions] = createSignal<MachineTag[]>(
|
|
||||||
sortedAndUniqueOptions([
|
|
||||||
...(props.readonlyOptions || []).map((value) => ({
|
|
||||||
value,
|
|
||||||
disabled: true,
|
|
||||||
})),
|
|
||||||
...(props.defaultOptions || []).map((value) => ({ value })),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
// react when enter is pressed inside of the text input
|
// react when enter is pressed inside of the text input
|
||||||
@@ -96,21 +126,52 @@ export const MachineTags = (props: MachineTagsProps) => {
|
|||||||
|
|
||||||
// get the current input value, exiting early if it's empty
|
// get the current input value, exiting early if it's empty
|
||||||
const input = event.currentTarget as HTMLInputElement;
|
const input = event.currentTarget as HTMLInputElement;
|
||||||
if (input.value === "") return;
|
const trimmed = input.value.trim();
|
||||||
|
if (!trimmed) return;
|
||||||
|
|
||||||
setAvailableOptions((options) => {
|
setAvailableOptions((curr) => {
|
||||||
return options.map((option) => {
|
if (curr.find((option) => option.value === trimmed)) {
|
||||||
return {
|
return curr;
|
||||||
...option,
|
}
|
||||||
new: undefined,
|
return [
|
||||||
};
|
...curr,
|
||||||
|
{
|
||||||
|
value: trimmed,
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
setSelectedOptions((curr) => {
|
||||||
|
if (curr.find((option) => option.value === trimmed)) {
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...curr,
|
||||||
|
{
|
||||||
|
value: trimmed,
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
// reset the input value
|
selectRef.dispatchEvent(
|
||||||
|
new Event("input", { bubbles: true, cancelable: true }),
|
||||||
|
);
|
||||||
|
selectRef.dispatchEvent(
|
||||||
|
new Event("change", { bubbles: true, cancelable: true }),
|
||||||
|
);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
createEffect(() => {
|
||||||
|
console.log("availableOptions", availableOptions());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notify when selected options change
|
||||||
|
createEffect(
|
||||||
|
on(selectedOptions, (options) => {
|
||||||
|
console.log("selectedOptions", options);
|
||||||
|
props.onChange(options.map((o) => o.value));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const align = () => {
|
const align = () => {
|
||||||
if (props.readOnly) {
|
if (props.readOnly) {
|
||||||
@@ -126,6 +187,7 @@ export const MachineTags = (props: MachineTagsProps) => {
|
|||||||
class={cx("form-field", styles.machineTags, props.orientation)}
|
class={cx("form-field", styles.machineTags, props.orientation)}
|
||||||
{...splitProps(props, ["defaultValue"])[1]}
|
{...splitProps(props, ["defaultValue"])[1]}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
|
value={selectedOptions()}
|
||||||
options={availableOptions()}
|
options={availableOptions()}
|
||||||
optionValue="value"
|
optionValue="value"
|
||||||
optionTextValue="value"
|
optionTextValue="value"
|
||||||
@@ -133,28 +195,8 @@ export const MachineTags = (props: MachineTagsProps) => {
|
|||||||
optionDisabled="disabled"
|
optionDisabled="disabled"
|
||||||
itemComponent={ItemComponent(props.inverted || false)}
|
itemComponent={ItemComponent(props.inverted || false)}
|
||||||
placeholder="Enter a tag name"
|
placeholder="Enter a tag name"
|
||||||
// triggerMode="focus"
|
onChange={(val) => {
|
||||||
removeOnBackspace={false}
|
console.log("Combobox onChange", val);
|
||||||
defaultFilter={() => true}
|
|
||||||
onInput={(event) => {
|
|
||||||
const input = event.target as HTMLInputElement;
|
|
||||||
|
|
||||||
// as the user types in the input box, we maintain a "new" option
|
|
||||||
// in the list of available options
|
|
||||||
setAvailableOptions((options) => {
|
|
||||||
return [
|
|
||||||
// remove the old "new" entry
|
|
||||||
...options.filter((option) => !option.new),
|
|
||||||
// add the updated "new" entry
|
|
||||||
{ value: input.value, new: true },
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
// clear the in-progress "new" option from the list of available options
|
|
||||||
setAvailableOptions((options) => {
|
|
||||||
return options.filter((option) => !option.new);
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Orienter orientation={props.orientation} align={align()}>
|
<Orienter orientation={props.orientation} align={align()}>
|
||||||
@@ -164,7 +206,12 @@ export const MachineTags = (props: MachineTagsProps) => {
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Combobox.HiddenSelect {...props.input} multiple />
|
<Combobox.HiddenSelect
|
||||||
|
multiple
|
||||||
|
ref={(el) => {
|
||||||
|
selectRef = el;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Combobox.Control<MachineTag>
|
<Combobox.Control<MachineTag>
|
||||||
class={cx(styles.control, props.orientation)}
|
class={cx(styles.control, props.orientation)}
|
||||||
@@ -187,7 +234,13 @@ export const MachineTags = (props: MachineTagsProps) => {
|
|||||||
icon={"Close"}
|
icon={"Close"}
|
||||||
size="0.5rem"
|
size="0.5rem"
|
||||||
inverted={inverted}
|
inverted={inverted}
|
||||||
onClick={() => state.remove(option)}
|
onClick={() =>
|
||||||
|
setSelectedOptions((curr) => {
|
||||||
|
return curr.filter(
|
||||||
|
(o) => o.value !== option.value,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -220,7 +273,6 @@ export const MachineTags = (props: MachineTagsProps) => {
|
|||||||
)}
|
)}
|
||||||
</Combobox.Control>
|
</Combobox.Control>
|
||||||
</Orienter>
|
</Orienter>
|
||||||
|
|
||||||
<Combobox.Portal>
|
<Combobox.Portal>
|
||||||
<Combobox.Content
|
<Combobox.Content
|
||||||
class={cx(styles.comboboxContent, {
|
class={cx(styles.comboboxContent, {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import * as v from "valibot";
|
|||||||
import { splitProps } from "solid-js";
|
import { splitProps } from "solid-js";
|
||||||
import { Typography } from "@/src/components/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import { MachineTags } from "@/src/components/Form/MachineTags";
|
import { MachineTags } from "@/src/components/Form/MachineTags";
|
||||||
|
import { setValue } from "@modular-forms/solid";
|
||||||
|
|
||||||
type Story = StoryObj<SidebarPaneProps>;
|
type Story = StoryObj<SidebarPaneProps>;
|
||||||
|
|
||||||
@@ -137,18 +138,21 @@ export const Default: Story = {
|
|||||||
console.log("saving tags", values);
|
console.log("saving tags", values);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ editing, Field }) => (
|
{({ editing, Field, formStore }) => (
|
||||||
<Field name="tags" type="string[]">
|
<Field name="tags" type="string[]">
|
||||||
{(field, input) => (
|
{(field, props) => (
|
||||||
<MachineTags
|
<MachineTags
|
||||||
{...splitProps(field, ["value"])[1]}
|
{...splitProps(field, ["value"])[1]}
|
||||||
size="s"
|
size="s"
|
||||||
|
onChange={(newVal) => {
|
||||||
|
// Workaround for now, until we manage to use native events
|
||||||
|
setValue(formStore, field.name, newVal);
|
||||||
|
}}
|
||||||
inverted
|
inverted
|
||||||
required
|
required
|
||||||
readOnly={!editing}
|
readOnly={!editing}
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
input={input}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createSignal, JSX, Show } from "solid-js";
|
|||||||
import {
|
import {
|
||||||
createForm,
|
createForm,
|
||||||
FieldValues,
|
FieldValues,
|
||||||
|
FormStore,
|
||||||
getErrors,
|
getErrors,
|
||||||
Maybe,
|
Maybe,
|
||||||
PartialValues,
|
PartialValues,
|
||||||
@@ -25,6 +26,7 @@ export interface SidebarSectionFormProps<FormValues extends FieldValues> {
|
|||||||
children: (ctx: {
|
children: (ctx: {
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
Field: ReturnType<typeof createForm<FormValues>>[1]["Field"];
|
Field: ReturnType<typeof createForm<FormValues>>[1]["Field"];
|
||||||
|
formStore: FormStore<FormValues>;
|
||||||
}) => JSX.Element;
|
}) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +53,8 @@ export function SidebarSectionForm<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit: SubmitHandler<FormValues> = async (values, event) => {
|
const handleSubmit: SubmitHandler<FormValues> = async (values, event) => {
|
||||||
|
console.log("Submitting SidebarForm", values);
|
||||||
|
|
||||||
await props.onSubmit(values);
|
await props.onSubmit(values);
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
};
|
};
|
||||||
@@ -109,7 +113,7 @@ export function SidebarSectionForm<
|
|||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
{props.children({ editing: editing(), Field })}
|
{props.children({ editing: editing(), Field, formStore })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ export const Machine = (props: RouteSectionProps) => {
|
|||||||
navigateToClan(navigate, clanURI);
|
navigateToClan(navigate, clanURI);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sections = () => {
|
const Sections = () => {
|
||||||
const machineName = useMachineName();
|
const machineName = useMachineName();
|
||||||
const machineQuery = useMachineQuery(clanURI, machineName);
|
const machineQuery = useMachineQuery(clanURI, machineName);
|
||||||
|
|
||||||
// we have to update the whole machine model rather than just the sub fields that were changed
|
// we have to update the whole machine model rather than just the sub fields that were changed
|
||||||
// for that reason we pass in this common submit handler to each machine sub section
|
// for that reason we pass in this common submit handler to each machine sub section
|
||||||
const onSubmit = async (values: Partial<MachineModel>) => {
|
const onSubmit = async (values: Partial<MachineModel>) => {
|
||||||
|
console.log("saving tags", values);
|
||||||
const call = callApi("set_machine", {
|
const call = callApi("set_machine", {
|
||||||
machine: {
|
machine: {
|
||||||
name: machineName,
|
name: machineName,
|
||||||
@@ -78,7 +79,7 @@ export const Machine = (props: RouteSectionProps) => {
|
|||||||
</Show>
|
</Show>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{sections()}
|
<Sections />
|
||||||
</SidebarPane>
|
</SidebarPane>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { SidebarSectionForm } from "@/src/components/Sidebar/SidebarSectionForm"
|
|||||||
import { pick } from "@/src/util";
|
import { pick } from "@/src/util";
|
||||||
import { UseQueryResult } from "@tanstack/solid-query";
|
import { UseQueryResult } from "@tanstack/solid-query";
|
||||||
import { MachineTags } from "@/src/components/Form/MachineTags";
|
import { MachineTags } from "@/src/components/Form/MachineTags";
|
||||||
|
import { setValue } from "@modular-forms/solid";
|
||||||
|
|
||||||
const schema = v.object({
|
const schema = v.object({
|
||||||
tags: v.pipe(v.optional(v.array(v.string()))),
|
tags: v.pipe(v.optional(v.array(v.string()))),
|
||||||
@@ -32,7 +33,7 @@ export const SectionTags = (props: SectionTags) => {
|
|||||||
|
|
||||||
const options = () => {
|
const options = () => {
|
||||||
if (!machineQuery.isSuccess) {
|
if (!machineQuery.isSuccess) {
|
||||||
return [[], []];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// these are static values or values which have been configured in nix and
|
// these are static values or values which have been configured in nix and
|
||||||
@@ -58,7 +59,7 @@ export const SectionTags = (props: SectionTags) => {
|
|||||||
onSubmit={props.onSubmit}
|
onSubmit={props.onSubmit}
|
||||||
initialValues={initialValues()}
|
initialValues={initialValues()}
|
||||||
>
|
>
|
||||||
{({ editing, Field }) => (
|
{({ editing, Field, formStore }) => (
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<Field name="tags" type="string[]">
|
<Field name="tags" type="string[]">
|
||||||
{(field, input) => (
|
{(field, input) => (
|
||||||
@@ -72,7 +73,10 @@ export const SectionTags = (props: SectionTags) => {
|
|||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
defaultOptions={options()[0]}
|
defaultOptions={options()[0]}
|
||||||
readonlyOptions={options()[1]}
|
readonlyOptions={options()[1]}
|
||||||
input={input}
|
onChange={(newVal) => {
|
||||||
|
// Workaround for now, until we manage to use native events
|
||||||
|
setValue(formStore, field.name, newVal);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { BackButton, StepLayout } from "@/src/workflows/Steps";
|
import { BackButton, StepLayout } from "@/src/workflows/Steps";
|
||||||
import * as v from "valibot";
|
import * as v from "valibot";
|
||||||
import { getStepStore, useStepper } from "@/src/hooks/stepper";
|
import { getStepStore, useStepper } from "@/src/hooks/stepper";
|
||||||
import { createForm, SubmitHandler, valiForm } from "@modular-forms/solid";
|
import {
|
||||||
|
createForm,
|
||||||
|
setValue,
|
||||||
|
SubmitHandler,
|
||||||
|
valiForm,
|
||||||
|
} from "@modular-forms/solid";
|
||||||
import {
|
import {
|
||||||
AddMachineSteps,
|
AddMachineSteps,
|
||||||
AddMachineStoreType,
|
AddMachineStoreType,
|
||||||
@@ -78,9 +83,12 @@ export const StepTags = (props: { onDone: () => void }) => {
|
|||||||
{...field}
|
{...field}
|
||||||
required
|
required
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
defaultValue={field.value}
|
defaultValue={field.value || []}
|
||||||
defaultOptions={[]}
|
defaultOptions={[]}
|
||||||
input={input}
|
onChange={(newVal) => {
|
||||||
|
// Workaround for now, until we manage to use native events
|
||||||
|
setValue(formStore, field.name, newVal);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
Reference in New Issue
Block a user