Merge pull request 'prep-ui-version2' (#4266) from prep-ui-version2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4266
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import type { Preview } from "@kachurun/storybook-solid-vite";
|
import type { Preview } from "@kachurun/storybook-solid-vite";
|
||||||
|
|
||||||
import "@/src/components/v2/index.css";
|
|
||||||
import "../src/index.css";
|
import "../src/index.css";
|
||||||
import "./preview.css";
|
import "./preview.css";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { JSX } from "solid-js";
|
|
||||||
|
|
||||||
interface FormSectionProps {
|
|
||||||
children: JSX.Element;
|
|
||||||
}
|
|
||||||
const FormSection = (props: FormSectionProps) => {
|
|
||||||
return <div class="p-2">{props.children}</div>;
|
|
||||||
};
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { splitProps, type JSX } from "solid-js";
|
|
||||||
import {
|
|
||||||
InputBase,
|
|
||||||
InputError,
|
|
||||||
InputLabel,
|
|
||||||
InputVariant,
|
|
||||||
} from "@/src/components/inputBase";
|
|
||||||
import { FieldLayout } from "./layout";
|
|
||||||
|
|
||||||
interface TextInputProps {
|
|
||||||
// Common
|
|
||||||
error?: string;
|
|
||||||
required?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
// Passed to input
|
|
||||||
value: string;
|
|
||||||
inputProps?: JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
||||||
placeholder?: string;
|
|
||||||
variant?: InputVariant;
|
|
||||||
// Passed to label
|
|
||||||
label: JSX.Element;
|
|
||||||
help?: string;
|
|
||||||
// Passed to layout
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TextInput(props: TextInputProps) {
|
|
||||||
const [layoutProps, rest] = splitProps(props, ["class"]);
|
|
||||||
return (
|
|
||||||
<FieldLayout
|
|
||||||
label={
|
|
||||||
<InputLabel
|
|
||||||
class="col-span-2"
|
|
||||||
required={props.required}
|
|
||||||
error={!!props.error}
|
|
||||||
help={props.help}
|
|
||||||
>
|
|
||||||
{props.label}
|
|
||||||
</InputLabel>
|
|
||||||
}
|
|
||||||
field={
|
|
||||||
<InputBase
|
|
||||||
variant={props.variant}
|
|
||||||
error={!!props.error}
|
|
||||||
required={props.required}
|
|
||||||
disabled={props.disabled}
|
|
||||||
placeholder={props.placeholder}
|
|
||||||
class="col-span-10"
|
|
||||||
{...props.inputProps}
|
|
||||||
value={props.value}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
error={props.error && <InputError error={props.error} />}
|
|
||||||
{...layoutProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./FormSection";
|
|
||||||
export * from "./TextInput";
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { JSX, splitProps } from "solid-js";
|
|
||||||
import cx from "classnames";
|
|
||||||
|
|
||||||
interface LayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
||||||
field?: JSX.Element;
|
|
||||||
label?: JSX.Element;
|
|
||||||
error?: JSX.Element;
|
|
||||||
}
|
|
||||||
export const FieldLayout = (props: LayoutProps) => {
|
|
||||||
const [intern, divProps] = splitProps(props, [
|
|
||||||
"field",
|
|
||||||
"label",
|
|
||||||
"error",
|
|
||||||
"class",
|
|
||||||
]);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={cx("grid grid-cols-10 items-center", intern.class)}
|
|
||||||
{...divProps}
|
|
||||||
>
|
|
||||||
<div class="col-span-5 flex items-center">{props.label}</div>
|
|
||||||
<div class="col-span-5">{props.field}</div>
|
|
||||||
{props.error && <span class="col-span-full">{props.error}</span>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
import { API } from "@/api/API";
|
|
||||||
import { Schema as Inventory } from "@/api/Inventory";
|
|
||||||
import { toast } from "solid-toast";
|
|
||||||
import {
|
|
||||||
ErrorToastComponent,
|
|
||||||
CancelToastComponent,
|
|
||||||
} from "@/src/components/toast";
|
|
||||||
|
|
||||||
type OperationNames = keyof API;
|
|
||||||
type Services = NonNullable<Inventory["services"]>;
|
|
||||||
type ServiceNames = keyof Services;
|
|
||||||
|
|
||||||
export type OperationArgs<T extends OperationNames> = API[T]["arguments"];
|
|
||||||
export type OperationResponse<T extends OperationNames> = API[T]["return"];
|
|
||||||
|
|
||||||
export type ClanServiceInstance<T extends ServiceNames> = NonNullable<
|
|
||||||
Services[T]
|
|
||||||
>[string];
|
|
||||||
|
|
||||||
export type SuccessQuery<T extends OperationNames> = Extract<
|
|
||||||
OperationResponse<T>,
|
|
||||||
{ status: "success" }
|
|
||||||
>;
|
|
||||||
export type SuccessData<T extends OperationNames> = SuccessQuery<T>["data"];
|
|
||||||
|
|
||||||
interface SendHeaderType {
|
|
||||||
logging?: { group_path: string[] };
|
|
||||||
}
|
|
||||||
interface BackendSendType<K extends OperationNames> {
|
|
||||||
body: OperationArgs<K>;
|
|
||||||
header?: SendHeaderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
||||||
interface ReceiveHeaderType {}
|
|
||||||
interface BackendReturnType<K extends OperationNames> {
|
|
||||||
body: OperationResponse<K>;
|
|
||||||
header: ReceiveHeaderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _callApi = <K extends OperationNames>(
|
|
||||||
method: K,
|
|
||||||
args: OperationArgs<K>,
|
|
||||||
backendOpts?: SendHeaderType,
|
|
||||||
): { promise: Promise<BackendReturnType<K>>; op_key: string } => {
|
|
||||||
// if window[method] does not exist, throw an error
|
|
||||||
if (!(method in window)) {
|
|
||||||
console.error(`Method ${method} not found on window object`);
|
|
||||||
// return a rejected promise
|
|
||||||
return {
|
|
||||||
promise: Promise.resolve({
|
|
||||||
body: {
|
|
||||||
status: "error",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: `Method ${method} not found on window object`,
|
|
||||||
code: "method_not_found",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
op_key: "noop",
|
|
||||||
},
|
|
||||||
header: {},
|
|
||||||
}),
|
|
||||||
op_key: "noop",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const message: BackendSendType<OperationNames> = {
|
|
||||||
body: args,
|
|
||||||
header: backendOpts,
|
|
||||||
};
|
|
||||||
|
|
||||||
const promise = (
|
|
||||||
window as unknown as Record<
|
|
||||||
OperationNames,
|
|
||||||
(
|
|
||||||
args: BackendSendType<OperationNames>,
|
|
||||||
) => Promise<BackendReturnType<OperationNames>>
|
|
||||||
>
|
|
||||||
)[method](message) as Promise<BackendReturnType<K>>;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const op_key = (promise as any)._webviewMessageId as string;
|
|
||||||
|
|
||||||
return { promise, op_key };
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = async <K extends OperationNames>(
|
|
||||||
ops_key: string,
|
|
||||||
orig_task: Promise<BackendReturnType<K>>,
|
|
||||||
) => {
|
|
||||||
console.log("Canceling operation: ", ops_key);
|
|
||||||
const { promise, op_key } = _callApi("delete_task", { task_id: ops_key });
|
|
||||||
promise.catch((error) => {
|
|
||||||
toast.custom(
|
|
||||||
(t) => (
|
|
||||||
<ErrorToastComponent
|
|
||||||
t={t}
|
|
||||||
message={"Unexpected error: " + (error?.message || String(error))}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
duration: 5000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
console.error("Unhandled promise rejection in callApi:", error);
|
|
||||||
});
|
|
||||||
const resp = await promise;
|
|
||||||
|
|
||||||
if (resp.body.status === "error") {
|
|
||||||
toast.custom(
|
|
||||||
(t) => (
|
|
||||||
<ErrorToastComponent
|
|
||||||
t={t}
|
|
||||||
message={"Failed to cancel operation: " + ops_key}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
duration: 5000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(orig_task as any).cancelled = true;
|
|
||||||
}
|
|
||||||
console.log("Cancel response: ", resp);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const callApi = <K extends OperationNames>(
|
|
||||||
method: K,
|
|
||||||
args: OperationArgs<K>,
|
|
||||||
backendOpts?: SendHeaderType,
|
|
||||||
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
|
||||||
console.log("Calling API", method, args, backendOpts);
|
|
||||||
|
|
||||||
const { promise, op_key } = _callApi(method, args, backendOpts);
|
|
||||||
promise.catch((error) => {
|
|
||||||
toast.custom(
|
|
||||||
(t) => (
|
|
||||||
<ErrorToastComponent
|
|
||||||
t={t}
|
|
||||||
message={"Unexpected error: " + (error?.message || String(error))}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
duration: 5000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
console.error("Unhandled promise rejection in callApi:", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
const toastId = toast.custom(
|
|
||||||
(
|
|
||||||
t, // t is the Toast object, t.id is the id of THIS toast instance
|
|
||||||
) => (
|
|
||||||
<CancelToastComponent
|
|
||||||
t={t}
|
|
||||||
message={"Executing " + method}
|
|
||||||
onCancel={handleCancel.bind(null, op_key, promise)}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
duration: Infinity,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const new_promise = promise.then((response) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const cancelled = (promise as any).cancelled;
|
|
||||||
if (cancelled) {
|
|
||||||
console.log("Not printing toast because operation was cancelled");
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = response.body;
|
|
||||||
if (body.status === "error" && !cancelled) {
|
|
||||||
toast.remove(toastId);
|
|
||||||
toast.custom(
|
|
||||||
(t) => (
|
|
||||||
<ErrorToastComponent
|
|
||||||
t={t}
|
|
||||||
message={"Error: " + body.errors[0].message}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
duration: Infinity,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
toast.remove(toastId);
|
|
||||||
}
|
|
||||||
return body;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { promise: new_promise, op_key: op_key };
|
|
||||||
};
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
import {
|
|
||||||
createForm,
|
|
||||||
FieldValues,
|
|
||||||
getValues,
|
|
||||||
setValue,
|
|
||||||
SubmitHandler,
|
|
||||||
} from "@modular-forms/solid";
|
|
||||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
|
||||||
import { Button } from "./components/Button/Button";
|
|
||||||
import { callApi } from "./api";
|
|
||||||
import { API } from "@/api/API";
|
|
||||||
import { createSignal, Match, Switch, For, Show } from "solid-js";
|
|
||||||
import { Typography } from "./components/Typography";
|
|
||||||
import { useQuery } from "@tanstack/solid-query";
|
|
||||||
import { makePersisted } from "@solid-primitives/storage";
|
|
||||||
import jsonSchema from "@/api/API.json";
|
|
||||||
|
|
||||||
interface APITesterForm extends FieldValues {
|
|
||||||
endpoint: string;
|
|
||||||
payload: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ACTUAL_API_ENDPOINT_NAMES: (keyof API)[] = jsonSchema.required.map(
|
|
||||||
(key) => key as keyof API,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ApiTester = () => {
|
|
||||||
const [persistedTestData, setPersistedTestData] = makePersisted(
|
|
||||||
createSignal<APITesterForm>(),
|
|
||||||
{
|
|
||||||
name: "_test_data",
|
|
||||||
storage: localStorage,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const [formStore, { Form, Field }] = createForm<APITesterForm>({
|
|
||||||
initialValues: persistedTestData() || { endpoint: "", payload: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const [endpointSearchTerm, setEndpointSearchTerm] = createSignal(
|
|
||||||
getValues(formStore).endpoint || "",
|
|
||||||
);
|
|
||||||
const [showSuggestions, setShowSuggestions] = createSignal(false);
|
|
||||||
|
|
||||||
const filteredEndpoints = () => {
|
|
||||||
const term = endpointSearchTerm().toLowerCase();
|
|
||||||
if (!term) return ACTUAL_API_ENDPOINT_NAMES;
|
|
||||||
return ACTUAL_API_ENDPOINT_NAMES.filter((ep) =>
|
|
||||||
ep.toLowerCase().includes(term),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = useQuery(() => {
|
|
||||||
const currentEndpoint = getValues(formStore).endpoint;
|
|
||||||
const currentPayload = getValues(formStore).payload;
|
|
||||||
const values = getValues(formStore);
|
|
||||||
|
|
||||||
return {
|
|
||||||
queryKey: ["api-tester", currentEndpoint, currentPayload],
|
|
||||||
queryFn: async () => {
|
|
||||||
return await callApi(
|
|
||||||
values.endpoint as keyof API,
|
|
||||||
JSON.parse(values.payload || "{}"),
|
|
||||||
).promise;
|
|
||||||
},
|
|
||||||
staleTime: Infinity,
|
|
||||||
enabled: false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSubmit: SubmitHandler<APITesterForm> = (values) => {
|
|
||||||
console.log(values);
|
|
||||||
setPersistedTestData(values);
|
|
||||||
setEndpointSearchTerm(values.endpoint);
|
|
||||||
query.refetch();
|
|
||||||
|
|
||||||
const v = getValues(formStore);
|
|
||||||
console.log(v);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div class="p-2">
|
|
||||||
<h1>API Tester</h1>
|
|
||||||
<Form onSubmit={handleSubmit}>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Field name="endpoint">
|
|
||||||
{(field, fieldProps) => (
|
|
||||||
<div class="relative">
|
|
||||||
<TextInput
|
|
||||||
label={"endpoint"}
|
|
||||||
value={field.value || ""}
|
|
||||||
inputProps={{
|
|
||||||
...fieldProps,
|
|
||||||
onInput: (e: Event) => {
|
|
||||||
if (fieldProps.onInput) {
|
|
||||||
(fieldProps.onInput as (ev: Event) => void)(e);
|
|
||||||
}
|
|
||||||
setEndpointSearchTerm(
|
|
||||||
(e.currentTarget as HTMLInputElement).value,
|
|
||||||
);
|
|
||||||
setShowSuggestions(true);
|
|
||||||
},
|
|
||||||
onBlur: (e: FocusEvent) => {
|
|
||||||
if (fieldProps.onBlur) {
|
|
||||||
(fieldProps.onBlur as (ev: FocusEvent) => void)(e);
|
|
||||||
}
|
|
||||||
setTimeout(() => setShowSuggestions(false), 150);
|
|
||||||
},
|
|
||||||
onFocus: (e: FocusEvent) => {
|
|
||||||
setEndpointSearchTerm(field.value || "");
|
|
||||||
setShowSuggestions(true);
|
|
||||||
},
|
|
||||||
onKeyDown: (e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
setShowSuggestions(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Show
|
|
||||||
when={showSuggestions() && filteredEndpoints().length > 0}
|
|
||||||
>
|
|
||||||
<ul class="absolute z-10 mt-1 max-h-60 w-full overflow-y-auto rounded border border-gray-300 bg-white shadow-lg">
|
|
||||||
<For each={filteredEndpoints()}>
|
|
||||||
{(ep) => (
|
|
||||||
<li
|
|
||||||
class="cursor-pointer p-2 hover:bg-gray-100"
|
|
||||||
onMouseDown={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setValue(formStore, "endpoint", ep);
|
|
||||||
setEndpointSearchTerm(ep);
|
|
||||||
setShowSuggestions(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ep}
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="payload">
|
|
||||||
{(field, fieldProps) => (
|
|
||||||
<div class="my-2 flex flex-col">
|
|
||||||
<label class="mb-1 font-medium" for="payload-textarea">
|
|
||||||
payload
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="payload-textarea"
|
|
||||||
class="min-h-[120px] resize-y rounded border p-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
|
|
||||||
placeholder={`{\n "key": "value"\n}`}
|
|
||||||
value={field.value || ""}
|
|
||||||
{...fieldProps}
|
|
||||||
onInput={(e) => {
|
|
||||||
fieldProps.onInput?.(e);
|
|
||||||
}}
|
|
||||||
spellcheck={false}
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Button class="m-2" disabled={query.isFetching}>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
<div>
|
|
||||||
<Typography hierarchy="title" size="default">
|
|
||||||
Result
|
|
||||||
</Typography>
|
|
||||||
<Switch>
|
|
||||||
<Match when={query.isFetching}>
|
|
||||||
<span>loading ...</span>
|
|
||||||
</Match>
|
|
||||||
<Match when={query.isFetched}>
|
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(query.data, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import { Alert, AlertProps } from "@/src/components/v2/Alert/Alert";
|
import { Alert, AlertProps } from "@/src/components/Alert/Alert";
|
||||||
import { expect, fn } from "storybook/test";
|
import { expect, fn } from "storybook/test";
|
||||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "./Alert.css";
|
import "./Alert.css";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
|
import Icon, { IconVariant } from "@/src/components/Icon/Icon";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import { Button } from "@kobalte/core/button";
|
import { Button } from "@kobalte/core/button";
|
||||||
import { Alert as KAlert } from "@kobalte/core/alert";
|
import { Alert as KAlert } from "@kobalte/core/alert";
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { useNavigate } from "@solidjs/router";
|
|
||||||
import { Button } from "./Button/Button";
|
|
||||||
import Icon from "./icon";
|
|
||||||
|
|
||||||
export const BackButton = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="s"
|
|
||||||
class="mr-2"
|
|
||||||
onClick={() => navigate(-1)}
|
|
||||||
startIcon={<Icon icon="CaretLeft" />}
|
|
||||||
></Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
@import "Button-Light.css";
|
|
||||||
@import "Button-Dark.css";
|
|
||||||
@import "Button-Ghost.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;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
.button--ghost-hover:hover {
|
|
||||||
@apply hover:bg-secondary-100 hover:text-secondary-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button--ghost-focus:focus {
|
|
||||||
@apply focus:bg-secondary-200 focus:text-secondary-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button--ghost-active:active {
|
|
||||||
@apply active:bg-secondary-200 active:text-secondary-900;
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
|
|
||||||
box-shadow: inset 2px 2px theme(backgroundColor.secondary.300);
|
|
||||||
|
|
||||||
& .button__label {
|
|
||||||
color: theme(textColor.secondary.900) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +1,115 @@
|
|||||||
import { splitProps, type JSX } from "solid-js";
|
import { splitProps, type JSX, createSignal } from "solid-js";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Typography } from "../Typography";
|
import { Typography } from "../Typography/Typography";
|
||||||
|
import { Button as KobalteButton } from "@kobalte/core/button";
|
||||||
|
|
||||||
import "./Button-Base.css";
|
import "./Button.css";
|
||||||
|
import Icon, { IconVariant } from "@/src/components/Icon/Icon";
|
||||||
|
import { Loader } from "@/src/components/Loader/Loader";
|
||||||
|
|
||||||
type Variants = "dark" | "light" | "ghost";
|
export type Size = "default" | "s";
|
||||||
type Size = "default" | "s";
|
export type Hierarchy = "primary" | "secondary";
|
||||||
|
|
||||||
const variantColors: (
|
export type Action = () => Promise<void>;
|
||||||
disabled: boolean | undefined,
|
|
||||||
) => Record<Variants, string> = (disabled) => ({
|
|
||||||
dark: cx(
|
|
||||||
"button--dark",
|
|
||||||
!disabled && "button--dark-hover", // Hover state
|
|
||||||
!disabled && "button--dark-focus", // Focus state
|
|
||||||
!disabled && "button--dark-active", // Active state
|
|
||||||
// Disabled
|
|
||||||
"disabled:bg-secondary-200 disabled:text-secondary-700 disabled:border-secondary-300",
|
|
||||||
),
|
|
||||||
light: cx(
|
|
||||||
"button--light",
|
|
||||||
|
|
||||||
!disabled && "button--light-hover", // Hover state
|
export interface ButtonProps
|
||||||
!disabled && "button--light-focus", // Focus state
|
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
!disabled && "button--light-active", // Active state
|
hierarchy?: Hierarchy;
|
||||||
),
|
|
||||||
ghost: cx(
|
|
||||||
!disabled && "button--ghost-hover", // Hover state
|
|
||||||
!disabled && "button--ghost-focus", // Focus state
|
|
||||||
!disabled && "button--ghost-active", // Active state
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const sizePaddings: Record<Size, string> = {
|
|
||||||
default: cx("button--default"),
|
|
||||||
s: cx("button button--small"), //cx("rounded-sm py-[0.375rem] px-3"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const sizeFont: Record<Size, string> = {
|
|
||||||
default: cx("text-[0.8125rem]"),
|
|
||||||
s: cx("text-[0.75rem]"),
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
||||||
variant?: Variants;
|
|
||||||
size?: Size;
|
size?: Size;
|
||||||
|
ghost?: boolean;
|
||||||
children?: JSX.Element;
|
children?: JSX.Element;
|
||||||
startIcon?: JSX.Element;
|
icon?: IconVariant;
|
||||||
endIcon?: JSX.Element;
|
startIcon?: IconVariant;
|
||||||
|
endIcon?: IconVariant;
|
||||||
class?: string;
|
class?: string;
|
||||||
|
onAction?: Action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const iconSizes: Record<Size, string> = {
|
||||||
|
default: "1rem",
|
||||||
|
s: "0.8125rem",
|
||||||
|
};
|
||||||
|
|
||||||
export const Button = (props: ButtonProps) => {
|
export const Button = (props: ButtonProps) => {
|
||||||
const [local, other] = splitProps(props, [
|
const [local, other] = splitProps(props, [
|
||||||
"children",
|
"children",
|
||||||
"variant",
|
"hierarchy",
|
||||||
"size",
|
"size",
|
||||||
|
"ghost",
|
||||||
|
"icon",
|
||||||
"startIcon",
|
"startIcon",
|
||||||
"endIcon",
|
"endIcon",
|
||||||
"class",
|
"class",
|
||||||
|
"onAction",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const buttonInvertion = (variant: Variants) => {
|
const size = local.size || "default";
|
||||||
return !(!variant || variant === "ghost" || variant === "light");
|
const hierarchy = local.hierarchy || "primary";
|
||||||
|
|
||||||
|
const [loading, setLoading] = createSignal(false);
|
||||||
|
|
||||||
|
const onClick = async () => {
|
||||||
|
if (!local.onAction) {
|
||||||
|
console.error("this should not be possible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await local.onAction();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error while executing action", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const iconSize = iconSizes[local.size || "default"];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<KobalteButton
|
||||||
class={cx(
|
class={cx(
|
||||||
local.class,
|
local.class,
|
||||||
"button", // default button class
|
"button", // default button class
|
||||||
variantColors(props.disabled)[local.variant || "dark"], // button appereance
|
size,
|
||||||
sizePaddings[local.size || "default"], // button size
|
hierarchy,
|
||||||
|
{
|
||||||
|
icon: local.icon,
|
||||||
|
loading: loading(),
|
||||||
|
ghost: local.ghost,
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
|
onClick={local.onAction ? onClick : undefined}
|
||||||
{...other}
|
{...other}
|
||||||
>
|
>
|
||||||
|
<Loader hierarchy={hierarchy} />
|
||||||
|
|
||||||
{local.startIcon && (
|
{local.startIcon && (
|
||||||
<span class="button__icon--start">{local.startIcon}</span>
|
<Icon icon={local.startIcon} class="icon-start" size={iconSize} />
|
||||||
)}
|
)}
|
||||||
{local.children && (
|
|
||||||
|
{local.icon && !local.children && (
|
||||||
|
<Icon icon={local.icon} class="icon" size={iconSize} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{local.children && !local.icon && (
|
||||||
<Typography
|
<Typography
|
||||||
class="button__label"
|
class="label"
|
||||||
hierarchy="label"
|
hierarchy="label"
|
||||||
|
family="mono"
|
||||||
size={local.size || "default"}
|
size={local.size || "default"}
|
||||||
color="inherit"
|
inverted={local.hierarchy === "primary"}
|
||||||
inverted={buttonInvertion(local.variant || "dark")}
|
weight="bold"
|
||||||
weight="medium"
|
|
||||||
tag="span"
|
tag="span"
|
||||||
>
|
>
|
||||||
{local.children}
|
{local.children}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{local.endIcon && <span class="button__icon--end">{local.endIcon}</span>}
|
|
||||||
</button>
|
{local.endIcon && (
|
||||||
|
<Icon icon={local.endIcon} class="icon-end" size={iconSize} />
|
||||||
|
)}
|
||||||
|
</KobalteButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import { Divider, DividerProps } from "@/src/components/v2/Divider/Divider";
|
import { Divider, DividerProps } from "@/src/components/Divider/Divider";
|
||||||
|
|
||||||
const meta: Meta<DividerProps> = {
|
const meta: Meta<DividerProps> = {
|
||||||
title: "Components/Divider",
|
title: "Components/Divider",
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Checkbox, CheckboxProps } from "@/src/components/v2/Form/Checkbox";
|
import { Checkbox, CheckboxProps } from "@/src/components/Form/Checkbox";
|
||||||
|
|
||||||
const Examples = (props: CheckboxProps) => (
|
const Examples = (props: CheckboxProps) => (
|
||||||
<div class="flex flex-col gap-8">
|
<div class="flex flex-col gap-8">
|
||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
CheckboxInputProps as KCheckboxInputProps,
|
CheckboxInputProps as KCheckboxInputProps,
|
||||||
CheckboxRootProps as KCheckboxRootProps,
|
CheckboxRootProps as KCheckboxRootProps,
|
||||||
} from "@kobalte/core/checkbox";
|
} from "@kobalte/core/checkbox";
|
||||||
import Icon from "@/src/components/v2/Icon/Icon";
|
import Icon from "@/src/components/Icon/Icon";
|
||||||
|
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Label } from "./Label";
|
import { Label } from "./Label";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Icon from "@/src/components/v2/Icon/Icon";
|
import Icon from "@/src/components/Icon/Icon";
|
||||||
import {
|
import {
|
||||||
Combobox as KCombobox,
|
Combobox as KCombobox,
|
||||||
ComboboxRootOptions as KComboboxRootOptions,
|
ComboboxRootOptions as KComboboxRootOptions,
|
||||||
@@ -11,9 +11,9 @@ import { Label } from "./Label";
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { FieldProps } from "./Field";
|
import { FieldProps } from "./Field";
|
||||||
import { Orienter } from "./Orienter";
|
import { Orienter } from "./Orienter";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import { Accessor, Component, For, Show, splitProps } from "solid-js";
|
import { Accessor, Component, For, Show, splitProps } from "solid-js";
|
||||||
import { Tag } from "@/src/components/v2/Tag/Tag";
|
import { Tag } from "@/src/components/Tag/Tag";
|
||||||
|
|
||||||
export type ComboboxProps<Option, OptGroup = never> = FieldProps &
|
export type ComboboxProps<Option, OptGroup = never> = FieldProps &
|
||||||
KComboboxRootOptions<Option, OptGroup> & {
|
KComboboxRootOptions<Option, OptGroup> & {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import { Fieldset, FieldsetProps } from "@/src/components/v2/Form/Fieldset";
|
import { Fieldset, FieldsetProps } from "@/src/components/Form/Fieldset";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { TextInput } from "@/src/components/v2/Form/TextInput";
|
import { TextInput } from "@/src/components/Form/TextInput";
|
||||||
import { TextArea } from "@/src/components/v2/Form/TextArea";
|
import { TextArea } from "@/src/components/Form/TextArea";
|
||||||
import { Checkbox } from "@/src/components/v2/Form/Checkbox";
|
import { Checkbox } from "@/src/components/Form/Checkbox";
|
||||||
import { FieldProps } from "./Field";
|
import { FieldProps } from "./Field";
|
||||||
|
|
||||||
const FieldsetExamples = (props: FieldsetProps) => (
|
const FieldsetExamples = (props: FieldsetProps) => (
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "./Fieldset.css";
|
import "./Fieldset.css";
|
||||||
import { JSX, splitProps } from "solid-js";
|
import { JSX, splitProps } from "solid-js";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import { FieldProps } from "./Field";
|
import { FieldProps } from "./Field";
|
||||||
|
|
||||||
export type FieldsetFieldProps = Pick<
|
export type FieldsetFieldProps = Pick<
|
||||||
@@ -12,7 +12,7 @@ div.form-label {
|
|||||||
@apply flex items-center gap-1;
|
@apply flex items-center gap-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > label[data-required] & not(label[data-readonly]) {
|
& > label[data-required]:not(label[data-readonly]) {
|
||||||
span.typography::after {
|
span.typography::after {
|
||||||
@apply fg-def-4 ml-1;
|
@apply fg-def-4 ml-1;
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Show } from "solid-js";
|
import { Show } from "solid-js";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import { Tooltip as KTooltip } from "@kobalte/core/tooltip";
|
import { Tooltip as KTooltip } from "@kobalte/core/tooltip";
|
||||||
import Icon from "@/src/components/v2/Icon/Icon";
|
import Icon from "@/src/components/Icon/Icon";
|
||||||
import { TextField } from "@kobalte/core/text-field";
|
import { TextField } from "@kobalte/core/text-field";
|
||||||
import { Checkbox } from "@kobalte/core/checkbox";
|
import { Checkbox } from "@kobalte/core/checkbox";
|
||||||
import { Combobox } from "@kobalte/core/combobox";
|
import { Combobox } from "@kobalte/core/combobox";
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { TextInput, TextInputProps } from "@/src/components/v2/Form/TextInput";
|
import { TextInput, TextInputProps } from "@/src/components/Form/TextInput";
|
||||||
|
|
||||||
const Examples = (props: TextInputProps) => (
|
const Examples = (props: TextInputProps) => (
|
||||||
<div class="flex flex-col gap-8">
|
<div class="flex flex-col gap-8">
|
||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
TextFieldInputProps,
|
TextFieldInputProps,
|
||||||
TextFieldRootProps,
|
TextFieldRootProps,
|
||||||
} from "@kobalte/core/text-field";
|
} from "@kobalte/core/text-field";
|
||||||
import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
|
import Icon, { IconVariant } from "@/src/components/Icon/Icon";
|
||||||
|
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Label } from "./Label";
|
import { Label } from "./Label";
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { type JSX } from "solid-js";
|
|
||||||
|
|
||||||
type sizes = "small" | "medium" | "large";
|
|
||||||
|
|
||||||
const gapSizes: Record<sizes, string> = {
|
|
||||||
small: "gap-2",
|
|
||||||
medium: "gap-4",
|
|
||||||
large: "gap-6",
|
|
||||||
};
|
|
||||||
|
|
||||||
interface List {
|
|
||||||
children: JSX.Element;
|
|
||||||
gapSize: sizes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const List = (props: List) => {
|
|
||||||
const { children, gapSize } = props;
|
|
||||||
|
|
||||||
return <ul class={`flex flex-col ${gapSizes[gapSize]}`}> {children}</ul>;
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { List } from "./List";
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import { Loader, LoaderProps } from "@/src/components/v2/Loader/Loader";
|
import { Loader, LoaderProps } from "@/src/components/Loader/Loader";
|
||||||
|
|
||||||
const meta: Meta<LoaderProps> = {
|
const meta: Meta<LoaderProps> = {
|
||||||
title: "Components/Loader",
|
title: "Components/Loader",
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
MachineStatus,
|
MachineStatus,
|
||||||
MachineStatusProps,
|
MachineStatusProps,
|
||||||
} from "@/src/components/v2/MachineStatus/MachineStatus";
|
} from "@/src/components/MachineStatus/MachineStatus";
|
||||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||||
|
|
||||||
const meta: Meta<MachineStatusProps> = {
|
const meta: Meta<MachineStatusProps> = {
|
||||||
@@ -4,7 +4,7 @@ import { Badge } from "@kobalte/core/badge";
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Show } from "solid-js";
|
import { Show } from "solid-js";
|
||||||
import Icon from "../Icon/Icon";
|
import Icon from "../Icon/Icon";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
|
|
||||||
export type MachineStatus =
|
export type MachineStatus =
|
||||||
| "Online"
|
| "Online"
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
import { TagProps } from "@/src/components/v2/Tag/Tag";
|
import { TagProps } from "@/src/components/Tag/Tag";
|
||||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import {
|
import { Modal, ModalContext, ModalProps } from "@/src/components/Modal/Modal";
|
||||||
Modal,
|
import { Fieldset } from "@/src/components/Form/Fieldset";
|
||||||
ModalContext,
|
import { TextInput } from "@/src/components/Form/TextInput";
|
||||||
ModalProps,
|
import { TextArea } from "@/src/components/Form/TextArea";
|
||||||
} from "@/src/components/v2/Modal/Modal";
|
import { Checkbox } from "@/src/components/Form/Checkbox";
|
||||||
import { Fieldset } from "@/src/components/v2/Form/Fieldset";
|
|
||||||
import { TextInput } from "@/src/components/v2/Form/TextInput";
|
|
||||||
import { TextArea } from "@/src/components/v2/Form/TextArea";
|
|
||||||
import { Checkbox } from "@/src/components/v2/Form/Checkbox";
|
|
||||||
import { Button } from "../Button/Button";
|
import { Button } from "../Button/Button";
|
||||||
|
|
||||||
const meta: Meta<ModalProps> = {
|
const meta: Meta<ModalProps> = {
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { List } from "@/src/components/Helpers";
|
|
||||||
import { SidebarListItem } from "../SidebarListItem";
|
|
||||||
|
|
||||||
export const SidebarFlyout = () => {
|
|
||||||
return (
|
|
||||||
<div class="sidebar__flyout">
|
|
||||||
<div class="sidebar__flyout__inner">
|
|
||||||
<List gapSize="small">
|
|
||||||
<SidebarListItem href="/clans" title="Settings" />
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { createSignal, Show } from "solid-js";
|
|
||||||
import { Typography } from "@/src/components/Typography";
|
|
||||||
import { SidebarFlyout } from "./SidebarFlyout";
|
|
||||||
import "./css/sidebar.css";
|
|
||||||
import Icon from "../icon";
|
|
||||||
|
|
||||||
interface SidebarProps {
|
|
||||||
clanName: string;
|
|
||||||
showFlyout?: () => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ClanProfile = (props: SidebarProps) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={`sidebar__profile ${props.showFlyout?.() ? "sidebar__profile--flyout" : ""}`}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
class="sidebar__profile__character"
|
|
||||||
tag="span"
|
|
||||||
hierarchy="title"
|
|
||||||
size="m"
|
|
||||||
weight="bold"
|
|
||||||
color="primary"
|
|
||||||
inverted={true}
|
|
||||||
>
|
|
||||||
{props.clanName.slice(0, 1).toUpperCase()}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ClanTitle = (props: SidebarProps) => {
|
|
||||||
return (
|
|
||||||
<Typography
|
|
||||||
tag="h3"
|
|
||||||
hierarchy="body"
|
|
||||||
size="default"
|
|
||||||
weight="medium"
|
|
||||||
color="primary"
|
|
||||||
inverted={true}
|
|
||||||
>
|
|
||||||
{props.clanName}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SidebarHeader = (props: SidebarProps) => {
|
|
||||||
const [showFlyout, toggleFlyout] = createSignal(false);
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
toggleFlyout(!showFlyout());
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<header class="sidebar__header">
|
|
||||||
<div onClick={handleClick} class="sidebar__header__inner">
|
|
||||||
{/* <ClanProfile clanName={props.clanName} showFlyout={showFlyout} /> */}
|
|
||||||
<div class="w-full pl-1 text-white">
|
|
||||||
<ClanTitle clanName={props.clanName} />
|
|
||||||
</div>
|
|
||||||
<Show
|
|
||||||
when={showFlyout}
|
|
||||||
fallback={<Icon size={12} class="text-white" icon="CaretDown" />}
|
|
||||||
>
|
|
||||||
<Icon size={12} class="text-white" icon="CaretDown" />
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
{showFlyout() && <SidebarFlyout />}
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { A } from "@solidjs/router";
|
|
||||||
import { Typography } from "@/src/components/Typography";
|
|
||||||
import "./css/sidebar.css";
|
|
||||||
|
|
||||||
interface SidebarListItem {
|
|
||||||
title: string;
|
|
||||||
href: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SidebarListItem = (props: SidebarListItem) => {
|
|
||||||
const { title, href } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li class="">
|
|
||||||
<A class="sidebar__list__link" href={href}>
|
|
||||||
<Typography
|
|
||||||
class="sidebar__list__content"
|
|
||||||
tag="span"
|
|
||||||
hierarchy="body"
|
|
||||||
size="xs"
|
|
||||||
weight="normal"
|
|
||||||
color="primary"
|
|
||||||
inverted={true}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
</A>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
SidebarNav,
|
SidebarNav,
|
||||||
SidebarNavProps,
|
SidebarNavProps,
|
||||||
} from "@/src/components/v2/Sidebar/SidebarNav";
|
} from "@/src/components/Sidebar/SidebarNav";
|
||||||
import { Suspense } from "solid-js";
|
import { Suspense } from "solid-js";
|
||||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "./SidebarNav.css";
|
import "./SidebarNav.css";
|
||||||
import { SidebarNavHeader } from "@/src/components/v2/Sidebar/SidebarNavHeader";
|
import { SidebarNavHeader } from "@/src/components/Sidebar/SidebarNavHeader";
|
||||||
import { SidebarNavBody } from "@/src/components/v2/Sidebar/SidebarNavBody";
|
import { SidebarNavBody } from "@/src/components/Sidebar/SidebarNavBody";
|
||||||
import { MachineStatus } from "@/src/components/v2/MachineStatus/MachineStatus";
|
import { MachineStatus } from "@/src/components/MachineStatus/MachineStatus";
|
||||||
|
|
||||||
export interface LinkProps {
|
export interface LinkProps {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -2,13 +2,13 @@ import "./SidebarNavBody.css";
|
|||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { Accordion } from "@kobalte/core/accordion";
|
import { Accordion } from "@kobalte/core/accordion";
|
||||||
import Icon from "../Icon/Icon";
|
import Icon from "../Icon/Icon";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import {
|
import {
|
||||||
MachineProps,
|
MachineProps,
|
||||||
SidebarNavProps,
|
SidebarNavProps,
|
||||||
} from "@/src/components/v2/Sidebar/SidebarNav";
|
} from "@/src/components/Sidebar/SidebarNav";
|
||||||
import { For } from "solid-js";
|
import { For } from "solid-js";
|
||||||
import { MachineStatus } from "@/src/components/v2/MachineStatus/MachineStatus";
|
import { MachineStatus } from "@/src/components/MachineStatus/MachineStatus";
|
||||||
|
|
||||||
const MachineRoute = (props: MachineProps) => (
|
const MachineRoute = (props: MachineProps) => (
|
||||||
<div class="flex w-full flex-col gap-2">
|
<div class="flex w-full flex-col gap-2">
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import "./SidebarNavHeader.css";
|
import "./SidebarNavHeader.css";
|
||||||
import Icon from "@/src/components/v2/Icon/Icon";
|
import Icon from "@/src/components/Icon/Icon";
|
||||||
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
|
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
import { Typography } from "../Typography/Typography";
|
import { Typography } from "../Typography/Typography";
|
||||||
import { createSignal, For } from "solid-js";
|
import { createSignal, For } from "solid-js";
|
||||||
import {
|
import { ClanLinkProps, ClanProps } from "@/src/components/Sidebar/SidebarNav";
|
||||||
ClanLinkProps,
|
|
||||||
ClanProps,
|
|
||||||
} from "@/src/components/v2/Sidebar/SidebarNav";
|
|
||||||
|
|
||||||
export interface SidebarHeaderProps {
|
export interface SidebarHeaderProps {
|
||||||
clanDetail: ClanProps;
|
clanDetail: ClanProps;
|
||||||
@@ -2,12 +2,12 @@ import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
|||||||
import {
|
import {
|
||||||
SidebarPane,
|
SidebarPane,
|
||||||
SidebarPaneProps,
|
SidebarPaneProps,
|
||||||
} from "@/src/components/v2/Sidebar/SidebarPane";
|
} from "@/src/components/Sidebar/SidebarPane";
|
||||||
import { SidebarSection } from "./SidebarSection";
|
import { SidebarSection } from "./SidebarSection";
|
||||||
import { Divider } from "@/src/components/v2/Divider/Divider";
|
import { Divider } from "@/src/components/Divider/Divider";
|
||||||
import { TextInput } from "@/src/components/v2/Form/TextInput";
|
import { TextInput } from "@/src/components/Form/TextInput";
|
||||||
import { TextArea } from "@/src/components/v2/Form/TextArea";
|
import { TextArea } from "@/src/components/Form/TextArea";
|
||||||
import { Checkbox } from "@/src/components/v2/Form/Checkbox";
|
import { Checkbox } from "@/src/components/Form/Checkbox";
|
||||||
import { Combobox } from "../Form/Combobox";
|
import { Combobox } from "../Form/Combobox";
|
||||||
|
|
||||||
const meta: Meta<SidebarPaneProps> = {
|
const meta: Meta<SidebarPaneProps> = {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { JSX } from "solid-js";
|
import { JSX } from "solid-js";
|
||||||
import "./SidebarPane.css";
|
import "./SidebarPane.css";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import Icon from "../Icon/Icon";
|
import Icon from "../Icon/Icon";
|
||||||
import { Button as KButton } from "@kobalte/core/button";
|
import { Button as KButton } from "@kobalte/core/button";
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createSignal, JSX } from "solid-js";
|
import { createSignal, JSX } from "solid-js";
|
||||||
import "./SidebarSection.css";
|
import "./SidebarSection.css";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import { Button as KButton } from "@kobalte/core/button";
|
import { Button as KButton } from "@kobalte/core/button";
|
||||||
import Icon from "../Icon/Icon";
|
import Icon from "../Icon/Icon";
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
.sidebar__flyout {
|
|
||||||
top: 0;
|
|
||||||
position: absolute;
|
|
||||||
z-index: theme(zIndex.30);
|
|
||||||
|
|
||||||
padding: theme(padding[1]);
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__flyout__inner {
|
|
||||||
position: relative;
|
|
||||||
width: inherit;
|
|
||||||
height: inherit;
|
|
||||||
|
|
||||||
padding: theme(padding.12) theme(padding.3) theme(padding.3);
|
|
||||||
background-color: var(--clr-bg-inv-4);
|
|
||||||
/* / 0.95); */
|
|
||||||
border: 1px solid var(--clr-border-inv-4);
|
|
||||||
border-radius: theme(borderRadius.lg);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
.sidebar__header {
|
|
||||||
position: relative;
|
|
||||||
padding: 1px 1px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--clr-bg-inv-3);
|
|
||||||
|
|
||||||
border-bottom: 1px solid var(--clr-border-inv-3);
|
|
||||||
border-top-left-radius: theme(borderRadius.xl);
|
|
||||||
border-top-right-radius: theme(borderRadius.xl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__header__inner {
|
|
||||||
position: relative;
|
|
||||||
z-index: theme(zIndex.40);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0 theme(gap.3);
|
|
||||||
|
|
||||||
padding: theme(padding.3) theme(padding.3);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
.sidebar__list__link {
|
|
||||||
position: relative;
|
|
||||||
cursor: theme(cursor.pointer);
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
z-index: theme(zIndex.10);
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: theme(borderRadius.md);
|
|
||||||
transform: scale(0.98);
|
|
||||||
transition: transform 0.24s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover:after {
|
|
||||||
background: var(--clr-bg-inv-acc-2);
|
|
||||||
transform: scale(theme(scale.100));
|
|
||||||
transition: transform 0.32s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.99);
|
|
||||||
transition: transform 0.12s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:after {
|
|
||||||
background: var(--clr-bg-inv-acc-3);
|
|
||||||
transform: scale(theme(scale.100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__list__link {
|
|
||||||
position: relative;
|
|
||||||
z-index: 20;
|
|
||||||
display: block;
|
|
||||||
padding: theme(padding.2) theme(padding.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__list__link.active {
|
|
||||||
&:after {
|
|
||||||
background: var(--clr-bg-inv-acc-3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__list__content {
|
|
||||||
position: relative;
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
.sidebar__profile {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: theme(width.8);
|
|
||||||
height: theme(height.8);
|
|
||||||
|
|
||||||
background: var(--clr-bg-inv-4);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__profile--flyout {
|
|
||||||
background: var(--clr-bg-def-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__profile--flyout > .sidebar__profile__character {
|
|
||||||
color: var(--clr-fg-def-1) !important;
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/* Sidebar Elements */
|
|
||||||
|
|
||||||
@import "./sidebar-header";
|
|
||||||
@import "./sidebar-flyout";
|
|
||||||
@import "./sidebar-list-item";
|
|
||||||
@import "./sidebar-profile";
|
|
||||||
|
|
||||||
/* Sidebar Structure */
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
@apply bg-inv-2 h-full border border-solid border-inv-2 min-w-72 rounded-xl;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: theme(padding.2);
|
|
||||||
padding: theme(padding.4) theme(padding.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__section {
|
|
||||||
@apply bg-primary-800/90;
|
|
||||||
|
|
||||||
padding: theme(padding.2);
|
|
||||||
border-radius: theme(borderRadius.md);
|
|
||||||
|
|
||||||
::marker {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import { For, type JSX, Show } from "solid-js";
|
|
||||||
import { RouteSectionProps } from "@solidjs/router";
|
|
||||||
import { AppRoute, routes } from "@/src";
|
|
||||||
import { SidebarHeader } from "./SidebarHeader";
|
|
||||||
import { SidebarListItem } from "./SidebarListItem";
|
|
||||||
import { Typography } from "../Typography";
|
|
||||||
import "./css/sidebar.css";
|
|
||||||
import Icon, { IconVariant } from "../icon";
|
|
||||||
import { clanMetaQuery } from "@/src/queries/clan-meta";
|
|
||||||
|
|
||||||
const SidebarSection = (props: {
|
|
||||||
title: string;
|
|
||||||
icon: IconVariant;
|
|
||||||
children: JSX.Element;
|
|
||||||
}) => {
|
|
||||||
const { title, children } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<details class="sidebar__section accordeon" open>
|
|
||||||
<summary style="display: contents;">
|
|
||||||
<div class="accordeon__header">
|
|
||||||
<Typography
|
|
||||||
class="inline-flex w-full gap-2 uppercase !tracking-wider"
|
|
||||||
tag="p"
|
|
||||||
hierarchy="body"
|
|
||||||
size="xxs"
|
|
||||||
weight="normal"
|
|
||||||
color="tertiary"
|
|
||||||
inverted={true}
|
|
||||||
>
|
|
||||||
<Icon class="opacity-90" icon={props.icon} size={13} />
|
|
||||||
{title}
|
|
||||||
<Icon icon="CaretDown" class="ml-auto" size={10} />
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
</summary>
|
|
||||||
<div class="accordeon__body">{children}</div>
|
|
||||||
</details>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Sidebar = (props: RouteSectionProps) => {
|
|
||||||
const query = clanMetaQuery();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="sidebar">
|
|
||||||
<Show
|
|
||||||
when={query.data}
|
|
||||||
fallback={<SidebarHeader clanName={"Untitled"} />}
|
|
||||||
>
|
|
||||||
{(meta) => <SidebarHeader clanName={meta().name} />}
|
|
||||||
</Show>
|
|
||||||
<div class="sidebar__body max-h-[calc(100vh-4rem)] overflow-scroll">
|
|
||||||
<For each={routes.filter((r) => !r.hidden)}>
|
|
||||||
{(route: AppRoute) => (
|
|
||||||
<Show
|
|
||||||
when={route.children}
|
|
||||||
fallback={
|
|
||||||
<SidebarListItem href={route.path} title={route.label} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{(children) => (
|
|
||||||
<SidebarSection
|
|
||||||
title={route.label}
|
|
||||||
icon={route.icon || "Paperclip"}
|
|
||||||
>
|
|
||||||
<ul class="flex flex-col gap-y-0.5">
|
|
||||||
<For each={children().filter((r) => !r.hidden)}>
|
|
||||||
{(child) => (
|
|
||||||
<SidebarListItem
|
|
||||||
href={`${route.path}${child.path}`}
|
|
||||||
title={child.label}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
</SidebarSection>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tag, TagProps } from "@/src/components/v2/Tag/Tag";
|
import { Tag, TagProps } from "@/src/components/Tag/Tag";
|
||||||
import { Meta, type StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
import { Meta, type StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||||
import { expect, fn } from "storybook/test";
|
import { expect, fn } from "storybook/test";
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "./Tag.css";
|
import "./Tag.css";
|
||||||
|
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
import { Typography } from "@/src/components/Typography/Typography";
|
||||||
import { createSignal, Show } from "solid-js";
|
import { createSignal, Show } from "solid-js";
|
||||||
import Icon, { IconVariant } from "../Icon/Icon";
|
import Icon, { IconVariant } from "../Icon/Icon";
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TagGroup, TagGroupProps } from "@/src/components/v2/TagGroup/TagGroup";
|
import { TagGroup, TagGroupProps } from "@/src/components/TagGroup/TagGroup";
|
||||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||||
|
|
||||||
const meta: Meta<TagGroupProps> = {
|
const meta: Meta<TagGroupProps> = {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "./TagGroup.css";
|
import "./TagGroup.css";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { For } from "solid-js";
|
import { For } from "solid-js";
|
||||||
import { Tag } from "@/src/components/v2/Tag/Tag";
|
import { Tag } from "@/src/components/Tag/Tag";
|
||||||
|
|
||||||
export interface TagGroupProps {
|
export interface TagGroupProps {
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
|||||||
|
|
||||||
import { Family, Hierarchy, Typography, Weight } from "./Typography";
|
import { Family, Hierarchy, Typography, Weight } from "./Typography";
|
||||||
import { Component, For, Show } from "solid-js";
|
import { Component, For, Show } from "solid-js";
|
||||||
import { AllColors } from "@/src/components/v2/colors";
|
import { AllColors } from "@/src/components/colors";
|
||||||
|
|
||||||
interface TypographyExamplesProps {
|
interface TypographyExamplesProps {
|
||||||
weights: Weight[];
|
weights: Weight[];
|
||||||
@@ -2,7 +2,7 @@ import { type JSX } from "solid-js";
|
|||||||
import { Dynamic } from "solid-js/web";
|
import { Dynamic } from "solid-js/web";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import "./Typography.css";
|
import "./Typography.css";
|
||||||
import { Color, fgClass } from "@/src/components/v2/colors";
|
import { Color, fgClass } from "@/src/components/colors";
|
||||||
|
|
||||||
export type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "div";
|
export type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "div";
|
||||||
export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser";
|
export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser";
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
.fnt-clr-primary {
|
|
||||||
color: var(--clr-fg-def-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-clr-secondary {
|
|
||||||
color: var(--clr-fg-def-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-clr-tertiary {
|
|
||||||
color: var(--clr-fg-def-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-clr-primary.fnt-clr--inverted {
|
|
||||||
color: var(--clr-fg-inv-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-clr-secondary.fnt-clr--inverted {
|
|
||||||
color: var(--clr-fg-inv-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-clr-tertiary.fnt-clr--inverted {
|
|
||||||
color: var(--clr-fg-inv-3);
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
@import "./typography-label.css";
|
|
||||||
@import "./typography-body.css";
|
|
||||||
@import "./typography-title.css";
|
|
||||||
@import "./typography-headline.css";
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
.fnt-body-default {
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 132%;
|
|
||||||
letter-spacing: 3%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-body-s {
|
|
||||||
font-size: 0.925rem;
|
|
||||||
line-height: 132%;
|
|
||||||
letter-spacing: 3%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-body-xs {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 132%;
|
|
||||||
letter-spacing: 3%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-body-xxs {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 132%;
|
|
||||||
letter-spacing: 0.00688rem;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
.fnt-headline-default {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 116%;
|
|
||||||
letter-spacing: 1%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-headline-m {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
line-height: 116%;
|
|
||||||
letter-spacing: 1%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-headline-l {
|
|
||||||
font-size: 2rem;
|
|
||||||
line-height: 116%;
|
|
||||||
letter-spacing: 1%;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
.fnt-label-default {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
line-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-label-s {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-label-xs {
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
line-height: 100%;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
.fnt-title-default {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 124%;
|
|
||||||
letter-spacing: 3%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-title-m {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 124%;
|
|
||||||
letter-spacing: 3%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-title-l {
|
|
||||||
font-size: 1.375rem;
|
|
||||||
line-height: 124%;
|
|
||||||
letter-spacing: 3%;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
@import "./typography-hierarchy/";
|
|
||||||
@import "./typography-color.css";
|
|
||||||
|
|
||||||
.fnt-weight-normal {
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-weight-medium {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-weight-bold {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-weight-normal.fnt-clr--inverted {
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-weight-medium.fnt-clr--inverted {
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fnt-weight-bold.fnt-clr--inverted {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import { type JSX } from "solid-js";
|
|
||||||
import { Dynamic } from "solid-js/web";
|
|
||||||
import cx from "classnames";
|
|
||||||
import "./css/typography.css";
|
|
||||||
|
|
||||||
type Hierarchy = "body" | "title" | "headline" | "label";
|
|
||||||
type Color = "primary" | "secondary" | "tertiary";
|
|
||||||
type Weight = "normal" | "medium" | "bold";
|
|
||||||
type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "div";
|
|
||||||
|
|
||||||
const colorMap: Record<Color, string> = {
|
|
||||||
primary: cx("fnt-clr-primary"),
|
|
||||||
secondary: cx("fnt-clr-secondary"),
|
|
||||||
tertiary: cx("fnt-clr-tertiary"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// type Size = "default" | "xs" | "s" | "m" | "l";
|
|
||||||
interface SizeForHierarchy {
|
|
||||||
label: {
|
|
||||||
default: string;
|
|
||||||
xs: string;
|
|
||||||
s: string;
|
|
||||||
};
|
|
||||||
body: {
|
|
||||||
default: string;
|
|
||||||
xs: string;
|
|
||||||
xxs: string;
|
|
||||||
s: string;
|
|
||||||
};
|
|
||||||
headline: {
|
|
||||||
default: string;
|
|
||||||
m: string;
|
|
||||||
l: string;
|
|
||||||
};
|
|
||||||
title: {
|
|
||||||
default: string;
|
|
||||||
m: string;
|
|
||||||
l: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type AllowedSizes<H extends Hierarchy> = keyof SizeForHierarchy[H];
|
|
||||||
|
|
||||||
const sizeHierarchyMap: SizeForHierarchy = {
|
|
||||||
body: {
|
|
||||||
default: cx("fnt-body-default"),
|
|
||||||
s: cx("fnt-body-s"),
|
|
||||||
xs: cx("fnt-body-xs"),
|
|
||||||
xxs: cx("fnt-body-xxs"),
|
|
||||||
},
|
|
||||||
headline: {
|
|
||||||
default: cx("fnt-headline-default"),
|
|
||||||
// xs: cx("fnt-headline-xs"),
|
|
||||||
// s: cx("fnt-headline-s"),
|
|
||||||
m: cx("fnt-headline-m"),
|
|
||||||
l: cx("fnt-headline-l"),
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
default: cx("fnt-title-default"),
|
|
||||||
// xs: cx("fnt-title-xs"),
|
|
||||||
// s: cx("fnt-title-s"),
|
|
||||||
m: cx("fnt-title-m"),
|
|
||||||
l: cx("fnt-title-l"),
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
default: cx("fnt-label-default"),
|
|
||||||
s: cx("fnt-label-s"),
|
|
||||||
xs: cx("fnt-label-xs"),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const weightMap: Record<Weight, string> = {
|
|
||||||
normal: cx("fnt-weight-normal"),
|
|
||||||
medium: cx("fnt-weight-medium"),
|
|
||||||
bold: cx("fnt-weight-bold"),
|
|
||||||
};
|
|
||||||
|
|
||||||
interface _TypographyProps<H extends Hierarchy> {
|
|
||||||
hierarchy: H;
|
|
||||||
size: AllowedSizes<H>;
|
|
||||||
children: JSX.Element;
|
|
||||||
weight?: Weight;
|
|
||||||
color?: Color | "inherit";
|
|
||||||
inverted?: boolean;
|
|
||||||
tag?: Tag;
|
|
||||||
class?: string;
|
|
||||||
classList?: Record<string, boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Typography = <H extends Hierarchy>(props: _TypographyProps<H>) => {
|
|
||||||
return (
|
|
||||||
<Dynamic
|
|
||||||
component={props.tag || "span"}
|
|
||||||
class={cx(
|
|
||||||
props.color === "inherit" && "text-inherit",
|
|
||||||
props.color !== "inherit" && colorMap[props.color || "primary"],
|
|
||||||
props.inverted && "fnt-clr--inverted",
|
|
||||||
sizeHierarchyMap[props.hierarchy][props.size] as string,
|
|
||||||
weightMap[props.weight || "normal"],
|
|
||||||
props.class,
|
|
||||||
)}
|
|
||||||
classList={props.classList}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Dynamic>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TypographyProps = _TypographyProps<Hierarchy>;
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import { Component, JSX, splitProps } from "solid-js";
|
|
||||||
import ArrowBottom from "@/icons/arrow-bottom.svg";
|
|
||||||
import ArrowLeft from "@/icons/arrow-left.svg";
|
|
||||||
import ArrowRight from "@/icons/arrow-right.svg";
|
|
||||||
import ArrowTop from "@/icons/arrow-top.svg";
|
|
||||||
import Attention from "@/icons/attention.svg";
|
|
||||||
import CaretDown from "@/icons/caret-down.svg";
|
|
||||||
import CaretLeft from "@/icons/caret-left.svg";
|
|
||||||
import CaretRight from "@/icons/caret-right.svg";
|
|
||||||
import CaretUp from "@/icons/caret-up.svg";
|
|
||||||
import Checkmark from "@/icons/checkmark.svg";
|
|
||||||
import ClanIcon from "@/icons/clan-icon.svg";
|
|
||||||
import ClanLogo from "@/icons/clan-logo.svg";
|
|
||||||
import Close from "@/icons/close.svg";
|
|
||||||
import Download from "@/icons/download.svg";
|
|
||||||
import Edit from "@/icons/edit.svg";
|
|
||||||
import Expand from "@/icons/expand.svg";
|
|
||||||
import EyeClose from "@/icons/eye-close.svg";
|
|
||||||
import EyeOpen from "@/icons/eye-open.svg";
|
|
||||||
import Filter from "@/icons/filter.svg";
|
|
||||||
import Flash from "@/icons/flash.svg";
|
|
||||||
import Folder from "@/icons/folder.svg";
|
|
||||||
import Grid from "@/icons/grid.svg";
|
|
||||||
import Info from "@/icons/info.svg";
|
|
||||||
import List from "@/icons/list.svg";
|
|
||||||
import Load from "@/icons/load.svg";
|
|
||||||
import More from "@/icons/more.svg";
|
|
||||||
import Paperclip from "@/icons/paperclip.svg";
|
|
||||||
import Plus from "@/icons/plus.svg";
|
|
||||||
import Reload from "@/icons/reload.svg";
|
|
||||||
import Report from "@/icons/report.svg";
|
|
||||||
import Search from "@/icons/search.svg";
|
|
||||||
import Settings from "@/icons/settings.svg";
|
|
||||||
import Trash from "@/icons/trash.svg";
|
|
||||||
import Update from "@/icons/update.svg";
|
|
||||||
import Warning from "@/icons/warning-filled.svg";
|
|
||||||
|
|
||||||
const icons = {
|
|
||||||
ArrowBottom,
|
|
||||||
ArrowLeft,
|
|
||||||
ArrowRight,
|
|
||||||
ArrowTop,
|
|
||||||
Attention,
|
|
||||||
CaretDown,
|
|
||||||
CaretLeft,
|
|
||||||
CaretRight,
|
|
||||||
CaretUp,
|
|
||||||
Checkmark,
|
|
||||||
ClanIcon,
|
|
||||||
ClanLogo,
|
|
||||||
Close,
|
|
||||||
Download,
|
|
||||||
Edit,
|
|
||||||
Expand,
|
|
||||||
EyeClose,
|
|
||||||
EyeOpen,
|
|
||||||
Filter,
|
|
||||||
Flash,
|
|
||||||
Folder,
|
|
||||||
Grid,
|
|
||||||
Info,
|
|
||||||
List,
|
|
||||||
Load,
|
|
||||||
More,
|
|
||||||
Paperclip,
|
|
||||||
Plus,
|
|
||||||
Reload,
|
|
||||||
Report,
|
|
||||||
Search,
|
|
||||||
Settings,
|
|
||||||
Trash,
|
|
||||||
Update,
|
|
||||||
Warning,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type IconVariant = keyof typeof icons;
|
|
||||||
|
|
||||||
interface IconProps extends JSX.SvgSVGAttributes<SVGElement> {
|
|
||||||
icon: IconVariant;
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Icon: Component<IconProps> = (props) => {
|
|
||||||
const [local, iconProps] = splitProps(props, ["icon"]);
|
|
||||||
|
|
||||||
const IconComponent = icons[local.icon];
|
|
||||||
return IconComponent ? (
|
|
||||||
<IconComponent
|
|
||||||
width={iconProps.size || 16}
|
|
||||||
height={iconProps.size || 16}
|
|
||||||
viewBox="0 0 48 48"
|
|
||||||
ref={iconProps.ref}
|
|
||||||
{...iconProps}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Icon;
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
import cx from "classnames";
|
|
||||||
import { JSX, Ref, Show, splitProps } from "solid-js";
|
|
||||||
import Icon, { IconVariant } from "../icon";
|
|
||||||
import { Typography, TypographyProps } from "../Typography";
|
|
||||||
|
|
||||||
export type InputVariant = "outlined" | "ghost";
|
|
||||||
interface InputBaseProps {
|
|
||||||
variant?: InputVariant;
|
|
||||||
value?: string;
|
|
||||||
inputProps?: JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
||||||
required?: boolean;
|
|
||||||
type?: string;
|
|
||||||
inlineLabel?: JSX.Element;
|
|
||||||
class?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
readonly?: boolean;
|
|
||||||
error?: boolean;
|
|
||||||
icon?: IconVariant;
|
|
||||||
/** Overrides the input element */
|
|
||||||
inputElem?: JSX.Element;
|
|
||||||
divRef?: Ref<HTMLDivElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const variantBorder: Record<InputVariant, string> = {
|
|
||||||
outlined: "border border-inv-3",
|
|
||||||
ghost: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const fgStateClasses = cx("aria-disabled:fg-def-4 aria-readonly:fg-def-3");
|
|
||||||
|
|
||||||
export const InputBase = (props: InputBaseProps) => {
|
|
||||||
const [internal, inputProps] = splitProps(props, ["class", "divRef"]);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
// eslint-disable-next-line tailwindcss/no-custom-classname
|
|
||||||
class={cx(
|
|
||||||
// Layout
|
|
||||||
"flex px-2 py-[0.375rem] flex-shrink-0 items-center justify-center gap-2 text-sm leading-6",
|
|
||||||
|
|
||||||
// Background
|
|
||||||
"bg-def-1 hover:bg-def-acc-1",
|
|
||||||
|
|
||||||
// Text
|
|
||||||
"fg-def-1",
|
|
||||||
fgStateClasses,
|
|
||||||
// Border
|
|
||||||
variantBorder[props.variant || "outlined"],
|
|
||||||
"rounded-sm",
|
|
||||||
"hover:border-inv-4",
|
|
||||||
"aria-disabled:border-def-2 aria-disabled:border",
|
|
||||||
// Outline
|
|
||||||
"outline-offset-1 outline-1",
|
|
||||||
"active:outline active:outline-inv-3",
|
|
||||||
"focus-visible:outline-double focus-visible:outline-int-1",
|
|
||||||
|
|
||||||
// Cursor
|
|
||||||
"aria-readonly:cursor-no-drop",
|
|
||||||
props.class,
|
|
||||||
)}
|
|
||||||
classList={{
|
|
||||||
// eslint-disable-next-line tailwindcss/no-custom-classname
|
|
||||||
[cx("!border !border-semantic-1 !outline-semantic-1")]: !!props.error,
|
|
||||||
}}
|
|
||||||
aria-invalid={props.error}
|
|
||||||
aria-disabled={props.disabled}
|
|
||||||
aria-readonly={props.readonly}
|
|
||||||
tabIndex={0}
|
|
||||||
role="textbox"
|
|
||||||
ref={internal.divRef}
|
|
||||||
>
|
|
||||||
{props.icon && (
|
|
||||||
<i
|
|
||||||
class={cx("inline-flex fg-def-2", fgStateClasses)}
|
|
||||||
aria-invalid={props.error}
|
|
||||||
aria-disabled={props.disabled}
|
|
||||||
aria-readonly={props.readonly}
|
|
||||||
>
|
|
||||||
<Icon icon={props.icon} font-size="inherit" color="inherit" />
|
|
||||||
</i>
|
|
||||||
)}
|
|
||||||
<Show when={!props.inputElem} fallback={props.inputElem}>
|
|
||||||
<input
|
|
||||||
tabIndex={-1}
|
|
||||||
class="w-full bg-transparent outline-none"
|
|
||||||
value={props.value}
|
|
||||||
type={props.type ? props.type : "text"}
|
|
||||||
readOnly={props.readonly}
|
|
||||||
placeholder={`${props.placeholder || ""}`}
|
|
||||||
required={props.required}
|
|
||||||
disabled={props.disabled}
|
|
||||||
aria-invalid={props.error}
|
|
||||||
aria-disabled={props.disabled}
|
|
||||||
aria-readonly={props.readonly}
|
|
||||||
{...inputProps}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface InputLabelProps extends JSX.LabelHTMLAttributes<HTMLLabelElement> {
|
|
||||||
description?: string;
|
|
||||||
required?: boolean;
|
|
||||||
error?: boolean;
|
|
||||||
help?: string;
|
|
||||||
labelAction?: JSX.Element;
|
|
||||||
}
|
|
||||||
export const InputLabel = (props: InputLabelProps) => {
|
|
||||||
const [labelProps, forwardProps] = splitProps(props, [
|
|
||||||
"class",
|
|
||||||
"labelAction",
|
|
||||||
]);
|
|
||||||
return (
|
|
||||||
<label
|
|
||||||
class={cx("flex items-center gap-1", labelProps.class)}
|
|
||||||
{...forwardProps}
|
|
||||||
>
|
|
||||||
<span class="flex flex-col justify-center">
|
|
||||||
<span>
|
|
||||||
<Typography
|
|
||||||
hierarchy="label"
|
|
||||||
size="default"
|
|
||||||
weight="bold"
|
|
||||||
class="inline-flex gap-1 align-middle !fg-def-1"
|
|
||||||
classList={{
|
|
||||||
[cx("!fg-semantic-info-1")]: !!props.error,
|
|
||||||
}}
|
|
||||||
aria-invalid={props.error}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Typography>
|
|
||||||
{props.required && (
|
|
||||||
<Typography
|
|
||||||
class="inline-flex px-1 align-text-top leading-[0.5] fg-def-4"
|
|
||||||
color="inherit"
|
|
||||||
hierarchy="label"
|
|
||||||
weight="bold"
|
|
||||||
size="xs"
|
|
||||||
>
|
|
||||||
<>*</>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{props.help && (
|
|
||||||
<span
|
|
||||||
class=" inline px-2"
|
|
||||||
data-tip={props.help}
|
|
||||||
style={{
|
|
||||||
"--tooltip-color": "#EFFFFF",
|
|
||||||
"--tooltip-text-color": "#0D1416",
|
|
||||||
"--tooltip-tail": "0.8125rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon class="inline fg-def-3" icon={"Info"} width={"0.8125rem"} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<Typography
|
|
||||||
hierarchy="body"
|
|
||||||
size="xs"
|
|
||||||
weight="normal"
|
|
||||||
color="secondary"
|
|
||||||
>
|
|
||||||
{props.description}
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
{props.labelAction}
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface InputErrorProps {
|
|
||||||
error: string;
|
|
||||||
typographyProps?: TypographyProps;
|
|
||||||
}
|
|
||||||
export const InputError = (props: InputErrorProps) => {
|
|
||||||
const [typoClasses, rest] = splitProps(
|
|
||||||
props.typographyProps || { class: "" },
|
|
||||||
["class"],
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Typography
|
|
||||||
hierarchy="body"
|
|
||||||
// @ts-expect-error: Dependent type is to complex to check how it is coupled to the override for now
|
|
||||||
size="xxs"
|
|
||||||
weight="medium"
|
|
||||||
class={cx("col-span-full px-1 !fg-semantic-info-4", typoClasses)}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{props.error}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,301 +0,0 @@
|
|||||||
import { toast, Toast } from "solid-toast"; // Make sure to import Toast type
|
|
||||||
import { Component, JSX, createSignal, onCleanup } from "solid-js";
|
|
||||||
|
|
||||||
// --- Icon Components ---
|
|
||||||
|
|
||||||
const ErrorIcon: Component = () => (
|
|
||||||
<svg
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
style={{ "margin-right": "10px", "flex-shrink": "0" }}
|
|
||||||
>
|
|
||||||
<circle cx="12" cy="12" r="10" fill="#FF4D4F" />
|
|
||||||
<path
|
|
||||||
d="M12 7V13"
|
|
||||||
stroke="white"
|
|
||||||
stroke-width="2.5"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<circle cx="12" cy="16.5" r="1.5" fill="white" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const InfoIcon: Component = () => (
|
|
||||||
<svg
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
style={{ "margin-right": "10px", "flex-shrink": "0" }}
|
|
||||||
>
|
|
||||||
<circle cx="12" cy="12" r="10" fill="#2196F3" />
|
|
||||||
<path
|
|
||||||
d="M12 11V17"
|
|
||||||
stroke="white"
|
|
||||||
stroke-width="2.5"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<circle cx="12" cy="8.5" r="1.5" fill="white" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const WarningIcon: Component = () => (
|
|
||||||
<svg
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
style={{ "margin-right": "10px", "flex-shrink": "0" }}
|
|
||||||
>
|
|
||||||
<path d="M12 2L22 21H2L12 2Z" fill="#FFC107" />
|
|
||||||
<path
|
|
||||||
d="M12 9V14"
|
|
||||||
stroke="#424242"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<circle cx="12" cy="16.5" r="1" fill="#424242" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- Base Props and Styles ---
|
|
||||||
|
|
||||||
interface BaseToastProps {
|
|
||||||
t: Toast;
|
|
||||||
message: string;
|
|
||||||
onCancel?: () => void; // Optional custom function on X click
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseToastStyle: JSX.CSSProperties = {
|
|
||||||
display: "flex",
|
|
||||||
"align-items": "center",
|
|
||||||
"justify-content": "space-between", // To push X to the right
|
|
||||||
gap: "10px", // Space between content and close button
|
|
||||||
background: "#FFFFFF",
|
|
||||||
color: "#333333",
|
|
||||||
padding: "12px 16px",
|
|
||||||
"border-radius": "6px",
|
|
||||||
"box-shadow": "0 2px 8px rgba(0, 0, 0, 0.12)",
|
|
||||||
"font-family":
|
|
||||||
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
||||||
"font-size": "14px",
|
|
||||||
"line-height": "1.4",
|
|
||||||
"min-width": "280px",
|
|
||||||
"max-width": "450px",
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeButtonStyle: JSX.CSSProperties = {
|
|
||||||
background: "none",
|
|
||||||
border: "none",
|
|
||||||
color: "red", // As per original example's X button
|
|
||||||
"font-size": "1.5em",
|
|
||||||
"font-weight": "bold",
|
|
||||||
cursor: "pointer",
|
|
||||||
padding: "0 0 0 10px", // Space to its left
|
|
||||||
"line-height": "1",
|
|
||||||
"align-self": "center", // Ensure vertical alignment
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Toast Component Definitions ---
|
|
||||||
|
|
||||||
// Error Toast
|
|
||||||
export const ErrorToastComponent: Component<BaseToastProps> = (props) => {
|
|
||||||
// Local state for click feedback and exit animation
|
|
||||||
let timeoutId: number | undefined;
|
|
||||||
const [clicked, setClicked] = createSignal(false);
|
|
||||||
const [exiting, setExiting] = createSignal(false);
|
|
||||||
|
|
||||||
const handleToastClick = () => {
|
|
||||||
setClicked(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setExiting(true);
|
|
||||||
timeoutId = window.setTimeout(() => {
|
|
||||||
toast.dismiss(props.t.id);
|
|
||||||
}, 300); // Match exit animation duration
|
|
||||||
}, 100); // Brief color feedback before animating out
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cleanup timeout if unmounted early
|
|
||||||
onCleanup(() => {
|
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...baseToastStyle,
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "background 0.15s, opacity 0.3s, transform 0.3s",
|
|
||||||
background: clicked()
|
|
||||||
? "#ffeaea"
|
|
||||||
: exiting()
|
|
||||||
? "#fff"
|
|
||||||
: baseToastStyle.background,
|
|
||||||
opacity: exiting() ? 0 : 1,
|
|
||||||
transform: exiting() ? "translateY(-20px)" : "none",
|
|
||||||
}}
|
|
||||||
onClick={handleToastClick}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{ display: "flex", "align-items": "center", "flex-grow": "1" }}
|
|
||||||
>
|
|
||||||
<ErrorIcon />
|
|
||||||
<span>{props.message}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
// Info Toast
|
|
||||||
export const CancelToastComponent: Component<BaseToastProps> = (props) => {
|
|
||||||
let timeoutId: number | undefined;
|
|
||||||
const [clicked, setClicked] = createSignal(false);
|
|
||||||
const [exiting, setExiting] = createSignal(false);
|
|
||||||
|
|
||||||
// Cleanup timeout if unmounted early
|
|
||||||
onCleanup(() => {
|
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleButtonClick = (e: MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (props.onCancel) props.onCancel();
|
|
||||||
toast.dismiss(props.t.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...baseToastStyle,
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "background 0.15s, opacity 0.3s, transform 0.3s",
|
|
||||||
background: clicked()
|
|
||||||
? "#eaf4ff"
|
|
||||||
: exiting()
|
|
||||||
? "#fff"
|
|
||||||
: baseToastStyle.background,
|
|
||||||
opacity: exiting() ? 0 : 1,
|
|
||||||
transform: exiting() ? "translateY(-20px)" : "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{ display: "flex", "align-items": "center", "flex-grow": "1" }}
|
|
||||||
>
|
|
||||||
<InfoIcon />
|
|
||||||
<span>{props.message}</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
setClicked(true);
|
|
||||||
handleButtonClick(e);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
...closeButtonStyle,
|
|
||||||
color: "#2196F3",
|
|
||||||
"font-size": "1em",
|
|
||||||
"font-weight": "normal",
|
|
||||||
padding: "4px 12px",
|
|
||||||
border: "1px solid #2196F3",
|
|
||||||
"border-radius": "4px",
|
|
||||||
background: clicked() ? "#bbdefb" : "#eaf4ff",
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "background 0.15s",
|
|
||||||
display: "flex",
|
|
||||||
"align-items": "center",
|
|
||||||
"justify-content": "center",
|
|
||||||
width: "70px",
|
|
||||||
height: "32px",
|
|
||||||
}}
|
|
||||||
aria-label="Cancel"
|
|
||||||
disabled={clicked()}
|
|
||||||
>
|
|
||||||
{clicked() ? (
|
|
||||||
// Simple spinner SVG
|
|
||||||
<svg
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
viewBox="0 0 50 50"
|
|
||||||
style={{ display: "block" }}
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
cx="25"
|
|
||||||
cy="25"
|
|
||||||
r="20"
|
|
||||||
fill="none"
|
|
||||||
stroke="#2196F3"
|
|
||||||
stroke-width="4"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-dasharray="31.415, 31.415"
|
|
||||||
transform="rotate(72 25 25)"
|
|
||||||
>
|
|
||||||
<animateTransform
|
|
||||||
attributeName="transform"
|
|
||||||
type="rotate"
|
|
||||||
from="0 25 25"
|
|
||||||
to="360 25 25"
|
|
||||||
dur="0.8s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
"Cancel"
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Warning Toast
|
|
||||||
const WarningToastComponent: Component<BaseToastProps> = (props) => {
|
|
||||||
let timeoutId: number | undefined;
|
|
||||||
const [clicked, setClicked] = createSignal(false);
|
|
||||||
const [exiting, setExiting] = createSignal(false);
|
|
||||||
|
|
||||||
const handleToastClick = () => {
|
|
||||||
setClicked(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setExiting(true);
|
|
||||||
timeoutId = window.setTimeout(() => {
|
|
||||||
toast.dismiss(props.t.id);
|
|
||||||
}, 300);
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cleanup timeout if unmounted early
|
|
||||||
onCleanup(() => {
|
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...baseToastStyle,
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "background 0.15s, opacity 0.3s, transform 0.3s",
|
|
||||||
background: clicked()
|
|
||||||
? "#fff8e1"
|
|
||||||
: exiting()
|
|
||||||
? "#fff"
|
|
||||||
: baseToastStyle.background,
|
|
||||||
opacity: exiting() ? 0 : 1,
|
|
||||||
transform: exiting() ? "translateY(-20px)" : "none",
|
|
||||||
}}
|
|
||||||
onClick={handleToastClick}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{ display: "flex", "align-items": "center", "flex-grow": "1" }}
|
|
||||||
>
|
|
||||||
<WarningIcon />
|
|
||||||
<span>{props.message}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import { splitProps, type JSX, createSignal } from "solid-js";
|
|
||||||
import cx from "classnames";
|
|
||||||
import { Typography } from "../Typography/Typography";
|
|
||||||
import { Button as KobalteButton } from "@kobalte/core/button";
|
|
||||||
|
|
||||||
import "./Button.css";
|
|
||||||
import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
|
|
||||||
import { Loader } from "@/src/components/v2/Loader/Loader";
|
|
||||||
|
|
||||||
export type Size = "default" | "s";
|
|
||||||
export type Hierarchy = "primary" | "secondary";
|
|
||||||
|
|
||||||
export type Action = () => Promise<void>;
|
|
||||||
|
|
||||||
export interface ButtonProps
|
|
||||||
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
||||||
hierarchy?: Hierarchy;
|
|
||||||
size?: Size;
|
|
||||||
ghost?: boolean;
|
|
||||||
children?: JSX.Element;
|
|
||||||
icon?: IconVariant;
|
|
||||||
startIcon?: IconVariant;
|
|
||||||
endIcon?: IconVariant;
|
|
||||||
class?: string;
|
|
||||||
onAction?: Action;
|
|
||||||
}
|
|
||||||
|
|
||||||
const iconSizes: Record<Size, string> = {
|
|
||||||
default: "1rem",
|
|
||||||
s: "0.8125rem",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Button = (props: ButtonProps) => {
|
|
||||||
const [local, other] = splitProps(props, [
|
|
||||||
"children",
|
|
||||||
"hierarchy",
|
|
||||||
"size",
|
|
||||||
"ghost",
|
|
||||||
"icon",
|
|
||||||
"startIcon",
|
|
||||||
"endIcon",
|
|
||||||
"class",
|
|
||||||
"onAction",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const size = local.size || "default";
|
|
||||||
const hierarchy = local.hierarchy || "primary";
|
|
||||||
|
|
||||||
const [loading, setLoading] = createSignal(false);
|
|
||||||
|
|
||||||
const onClick = async () => {
|
|
||||||
if (!local.onAction) {
|
|
||||||
console.error("this should not be possible");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await local.onAction();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error while executing action", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const iconSize = iconSizes[local.size || "default"];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KobalteButton
|
|
||||||
class={cx(
|
|
||||||
local.class,
|
|
||||||
"button", // default button class
|
|
||||||
size,
|
|
||||||
hierarchy,
|
|
||||||
{
|
|
||||||
icon: local.icon,
|
|
||||||
loading: loading(),
|
|
||||||
ghost: local.ghost,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
onClick={local.onAction ? onClick : undefined}
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
<Loader hierarchy={hierarchy} />
|
|
||||||
|
|
||||||
{local.startIcon && (
|
|
||||||
<Icon icon={local.startIcon} class="icon-start" size={iconSize} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{local.icon && !local.children && (
|
|
||||||
<Icon icon={local.icon} class="icon" size={iconSize} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{local.children && !local.icon && (
|
|
||||||
<Typography
|
|
||||||
class="label"
|
|
||||||
hierarchy="label"
|
|
||||||
family="mono"
|
|
||||||
size={local.size || "default"}
|
|
||||||
inverted={local.hierarchy === "primary"}
|
|
||||||
weight="bold"
|
|
||||||
tag="span"
|
|
||||||
>
|
|
||||||
{local.children}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{local.endIcon && (
|
|
||||||
<Icon icon={local.endIcon} class="icon-end" size={iconSize} />
|
|
||||||
)}
|
|
||||||
</KobalteButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
## Overview
|
|
||||||
|
|
||||||
We will be updating existing components and developing new components in line with the latest designs inside this
|
|
||||||
folder. As they become ready, they can be copied into the root `components` folder, replacing any existing components as
|
|
||||||
necessary.
|
|
||||||
|
|
||||||
This is to avoid merge hell and allow us to rapidly match the latest designs without the burden of integration.
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/* Material icons removed - using custom icons */
|
|
||||||
/* @import url(./components/Typography/css/typography.css); */
|
|
||||||
|
|
||||||
@config "../../../tailwind.config.ts";
|
|
||||||
|
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Archivo";
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(../../../.fonts/Archivo-Regular.woff2) format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Archivo";
|
|
||||||
font-weight: 500;
|
|
||||||
src: url(../../../.fonts/Archivo-Medium.woff2) format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Archivo";
|
|
||||||
font-weight: 600;
|
|
||||||
src: url(../../../.fonts/Archivo-SemiBold.woff2) format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Archivo SemiCondensed";
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(../../../.fonts/ArchivoSemiCondensed-Regular.woff2) format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Archivo SemiCondensed";
|
|
||||||
font-weight: 500;
|
|
||||||
src: url(../../../.fonts/ArchivoSemiCondensed-Medium.woff2) format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Archivo SemiCondensed";
|
|
||||||
font-weight: 600;
|
|
||||||
src: url(../../../.fonts/ArchivoSemiCondensed-SemiBold.woff2) format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Commit Mono";
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(../../../.fonts/CommitMono-400-Regular.otf) format("otf");
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
@apply font-sans;
|
|
||||||
overflow-x: 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 */
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
.no-scrollbar::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.no-scrollbar {
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user