diff --git a/pkgs/clan-app/ui/src/components/Form/TextInput.css b/pkgs/clan-app/ui/src/components/Form/TextInput.css
index e34fefa4e..dc0eefb4f 100644
--- a/pkgs/clan-app/ui/src/components/Form/TextInput.css
+++ b/pkgs/clan-app/ui/src/components/Form/TextInput.css
@@ -76,6 +76,19 @@ div.form-field {
@apply absolute left-2 top-1/2 transform -translate-y-1/2;
@apply w-[0.875rem] h-[0.875rem] pointer-events-none;
}
+
+ & > .start-component {
+ @apply absolute left-2 top-1/2 transform -translate-y-1/2;
+ }
+
+ & > .end-component {
+ @apply absolute right-2 top-1/2 transform -translate-y-1/2;
+ }
+
+ & > .start-component,
+ & > .end-component {
+ @apply size-fit;
+ }
}
&.s {
@@ -101,7 +114,7 @@ div.form-field {
}
& > .icon {
- @apply w-[0.6875rem] h-[0.6875rem] transform -translate-y-1/2;
+ @apply w-[0.6875rem] h-[0.6875rem];
}
}
}
diff --git a/pkgs/clan-app/ui/src/components/Form/TextInput.stories.tsx b/pkgs/clan-app/ui/src/components/Form/TextInput.stories.tsx
index b51411b63..ea78930d0 100644
--- a/pkgs/clan-app/ui/src/components/Form/TextInput.stories.tsx
+++ b/pkgs/clan-app/ui/src/components/Form/TextInput.stories.tsx
@@ -1,6 +1,8 @@
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import cx from "classnames";
import { TextInput, TextInputProps } from "@/src/components/Form/TextInput";
+import Icon from "../Icon/Icon";
+import { Button } from "@kobalte/core/button";
const Examples = (props: TextInputProps) => (
@@ -83,16 +85,38 @@ export const Tooltip: Story = {
},
};
-export const Icon: Story = {
+export const WithIcon: Story = {
args: {
...Tooltip.args,
- icon: "Checkmark",
+ startComponent: () =>
,
+ },
+};
+
+export const WithStartComponent: Story = {
+ args: {
+ ...Tooltip.args,
+ startComponent: (props: { inverted?: boolean }) => (
+
+ ),
+ },
+};
+
+export const WithEndComponent: Story = {
+ args: {
+ ...Tooltip.args,
+ endComponent: (props: { inverted?: boolean }) => (
+
+ ),
},
};
export const Ghost: Story = {
args: {
- ...Icon.args,
+ ...WithIcon.args,
ghost: true,
},
};
@@ -106,14 +130,14 @@ export const Invalid: Story = {
export const Disabled: Story = {
args: {
- ...Icon.args,
+ ...WithIcon.args,
disabled: true,
},
};
export const ReadOnly: Story = {
args: {
- ...Icon.args,
+ ...WithIcon.args,
readOnly: true,
defaultValue: "14/05/02",
},
diff --git a/pkgs/clan-app/ui/src/components/Form/TextInput.tsx b/pkgs/clan-app/ui/src/components/Form/TextInput.tsx
index 2cbe1c673..f0157a458 100644
--- a/pkgs/clan-app/ui/src/components/Form/TextInput.tsx
+++ b/pkgs/clan-app/ui/src/components/Form/TextInput.tsx
@@ -11,12 +11,20 @@ import "./TextInput.css";
import { PolymorphicProps } from "@kobalte/core/polymorphic";
import { FieldProps } from "./Field";
import { Orienter } from "./Orienter";
-import { splitProps } from "solid-js";
+import {
+ Component,
+ createEffect,
+ createSignal,
+ onMount,
+ splitProps,
+} from "solid-js";
export type TextInputProps = FieldProps &
TextFieldRootProps & {
icon?: IconVariant;
input?: PolymorphicProps<"input", TextFieldInputProps<"input">>;
+ startComponent?: Component
>;
+ endComponent?: Component>;
};
export const TextInput = (props: TextInputProps) => {
@@ -28,6 +36,39 @@ export const TextInput = (props: TextInputProps) => {
"ghost",
]);
+ let inputRef: HTMLInputElement | undefined;
+ let startComponentRef: HTMLDivElement | undefined;
+ let endComponentRef: HTMLDivElement | undefined;
+
+ const [startComponentSize, setStartComponentSize] = createSignal({
+ width: 0,
+ height: 0,
+ });
+ const [endComponentSize, setEndComponentSize] = createSignal({
+ width: 0,
+ height: 0,
+ });
+
+ onMount(() => {
+ if (startComponentRef) {
+ const rect = startComponentRef.getBoundingClientRect();
+ setStartComponentSize({ width: rect.width, height: rect.height });
+ }
+ if (endComponentRef) {
+ const rect = endComponentRef.getBoundingClientRect();
+ setEndComponentSize({ width: rect.width, height: rect.height });
+ }
+ });
+
+ createEffect(() => {
+ if (inputRef) {
+ const padding = props.size == "s" ? 6 : 8;
+
+ inputRef.style.paddingLeft = `${startComponentSize().width + padding * 2}px`;
+ inputRef.style.paddingRight = `${endComponentSize().width + padding * 2}px`;
+ }
+ });
+
return (
{
{...props}
/>
diff --git a/pkgs/clan-app/ui/src/workflows/InstallMachine/InstallMachine.stories.tsx b/pkgs/clan-app/ui/src/workflows/InstallMachine/InstallMachine.stories.tsx
index f50106022..dc3f313e1 100644
--- a/pkgs/clan-app/ui/src/workflows/InstallMachine/InstallMachine.stories.tsx
+++ b/pkgs/clan-app/ui/src/workflows/InstallMachine/InstallMachine.stories.tsx
@@ -75,7 +75,7 @@ const mockFetcher: Fetcher = (
{
name: "gritty.foo",
description: "Name of the gritty",
- prompt_type: "line",
+ prompt_type: "hidden",
display: {
helperText: null,
label: "(2) Password",
@@ -113,7 +113,7 @@ const mockFetcher: Fetcher = (
{
name: "gritty.foo",
description: "Name of the gritty",
- prompt_type: "line",
+ prompt_type: "hidden",
display: {
helperText: null,
label: "(5) Password",
diff --git a/pkgs/clan-app/ui/src/workflows/InstallMachine/steps/installSteps.tsx b/pkgs/clan-app/ui/src/workflows/InstallMachine/steps/installSteps.tsx
index 9dbd01aeb..de6c0312e 100644
--- a/pkgs/clan-app/ui/src/workflows/InstallMachine/steps/installSteps.tsx
+++ b/pkgs/clan-app/ui/src/workflows/InstallMachine/steps/installSteps.tsx
@@ -18,7 +18,7 @@ import {
} from "../InstallMachine";
import { TextInput } from "@/src/components/Form/TextInput";
import { Alert, AlertProps } from "@/src/components/Alert/Alert";
-import { createSignal, For, Match, Show, Switch } from "solid-js";
+import { createSignal, For, Match, Show, Switch, JSX } from "solid-js";
import { Divider } from "@/src/components/Divider/Divider";
import { Orienter } from "@/src/components/Form/Orienter";
import { Button } from "@/src/components/Button/Button";
@@ -35,6 +35,7 @@ import { useClanURI } from "@/src/hooks/clan";
import { useApiClient } from "@/src/hooks/ApiClient";
import { ProcessMessage, useNotifyOrigin } from "@/src/hooks/notify";
import { Loader } from "@/src/components/Loader/Loader";
+import { Button as KButton } from "@kobalte/core/button";
export const InstallHeader = (props: { machineName: string }) => {
return (
@@ -566,35 +567,68 @@ const PromptsFields = (props: PromptsFieldsProps) => {
- {(f, props) => (
-
- )}
+ {(f, props) => {
+ const defaultInputType =
+ fieldInfo.prompt.prompt_type.includes("hidden")
+ ? "password"
+ : "text";
+
+ const [inputType, setInputType] =
+ createSignal(defaultInputType);
+
+ let endComponent:
+ | ((props: { inverted?: boolean }) => JSX.Element)
+ | undefined = undefined;
+
+ if (defaultInputType === "password") {
+ endComponent = (props) => (
+ {
+ setInputType((type) =>
+ type === "password" ? "text" : "password",
+ );
+ }}
+ >
+
+
+ );
+ }
+
+ return (
+
+ );
+ }}
)}