Add events to be tracked by the Storybook while interacting with the components

This commit is contained in:
Arnaud 2024-08-22 20:04:24 +02:00
parent b99008ba3d
commit c4d5c6e864
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
17 changed files with 211 additions and 33 deletions

View File

@ -23,6 +23,10 @@ type Props = {
onClick?: () => unknown | Promise<unknown>;
onMouseEnter?: () => unknown | Promise<unknown>;
onMouseLeave?: () => unknown | Promise<unknown>;
label: string;
/**
@ -67,6 +71,8 @@ export function Button({
label,
className = "",
Icon,
onMouseEnter,
onMouseLeave,
fetching = false,
disabled = false,
style,
@ -76,6 +82,8 @@ export function Button({
return (
<button
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={style}
className={`button ${className} button--${variant}`}
{...attributes({

View File

@ -15,6 +15,10 @@ type Props = {
onClick?: () => void;
onMouseEnter?: () => unknown | Promise<unknown>;
onMouseLeave?: () => unknown | Promise<unknown>;
disabled?: boolean;
/**
@ -32,12 +36,16 @@ export function ButtonIcon({
Icon,
onClick,
style,
onMouseEnter,
onMouseLeave,
disabled = false,
variant = "big",
}: Props) {
return (
<button
className={`buttonIcon buttonIcon--${variant}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={onClick}
style={style}
{...attributes({ disabled: disabled, "aria-disabled": disabled })}

View File

@ -52,6 +52,14 @@ type Props = {
*/
onSelected?: (o: DropdownOption) => void;
onBlur?: () => void;
onFocus?: () => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
/**
* Apply custom css variables.
* --codex-dropdown-panel-background
@ -65,6 +73,10 @@ export function Dropdown({
placeholder,
style,
options,
onMouseEnter,
onMouseLeave,
onFocus,
onBlur,
onChange,
onSelected,
value = "",
@ -78,11 +90,17 @@ export function Dropdown({
);
const [focused, setFocused] = useState(false);
const onFocus = () => setFocused(true);
const onInternalFocus = () => {
setFocused(true);
onFocus?.();
};
const onBlur = () => () => window.setTimeout(() => setFocused(false), 150);
const onInternalBlur = () => {
onBlur?.();
window.setTimeout(() => setFocused(false), 150);
};
const onClick = (o: DropdownOption) => {
const onSelect = (o: DropdownOption) => {
onSelected?.(o);
setFocused(false);
};
@ -98,12 +116,14 @@ export function Dropdown({
<Input
className="dropdown-input"
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
onFocus={onInternalFocus}
onBlur={onInternalBlur}
placeholder={placeholder}
value={value}
label={""}
id={""}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
<div className="dropdown-panel" {...attr}>
@ -111,7 +131,7 @@ export function Dropdown({
filtered.map((o) => (
<div
className="dropdown-option"
onClick={() => onClick(o)}
onClick={() => onSelect(o)}
key={o.title}
>
{o.Icon && <o.Icon />}

View File

@ -27,6 +27,12 @@ type Props = {
onBlur?: () => unknown | Promise<unknown>;
onClick?: (() => void) | undefined;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
placeholder?: string;
value?: string;
@ -74,6 +80,9 @@ export function Input({
onFocus,
placeholder,
onChange,
onMouseEnter,
onMouseLeave,
onClick,
className,
style,
Icon,
@ -95,6 +104,9 @@ export function Input({
</div>
)}
<input
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={classnames(
["input"],
["input-icon-input", !!Icon],

View File

@ -43,6 +43,14 @@ type Props = {
*/
onGroupChange?: (e: ChangeEvent<HTMLSelectElement>) => void;
onBlur?: () => void;
onFocus?: () => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
id: string;
step?: string;
@ -63,9 +71,13 @@ export function InputGroup({
className,
onChange,
onGroupChange,
onMouseEnter,
onMouseLeave,
onFocus,
onBlur,
id,
step,
value = "",
value = undefined,
groupValue = "",
}: Props) {
return (
@ -81,6 +93,10 @@ export function InputGroup({
type={type}
value={value}
step={step}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onFocus={onFocus}
onBlur={onBlur}
/>
</div>

View File

@ -26,7 +26,7 @@ select.inputGroup-select {
border-top-right-radius: var(--codex-border-radius);
border-bottom-right-radius: var(--codex-border-radius);
background-color: var(--codex-border-color);
padding: 0.75rem;
padding: calc(0.5rem + 0.5px);
}
.inputGroup-inputContainer {

View File

@ -24,6 +24,14 @@ type Props = {
*/
onChange?: (e: ChangeEvent<HTMLSelectElement>) => void | Promise<void>;
onFocus?: () => void | Promise<void>;
onBlur?: () => unknown | Promise<unknown>;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
/**
* Apply custom css variables.
*/
@ -39,6 +47,10 @@ export function Select({
id,
options,
onChange,
onBlur,
onFocus,
onMouseEnter,
onMouseLeave,
style,
className,
defaultValue,
@ -53,6 +65,10 @@ export function Select({
id={id}
className={`select ${className}`}
onChange={onChange}
onBlur={onBlur}
onFocus={onFocus}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={style}
defaultValue={defaultValue}
>

View File

@ -1,4 +1,3 @@
import { ReactNode } from "react";
import "./Table.css";
import { CellRender } from "./CellRender";

View File

@ -32,12 +32,23 @@ type Props = {
*/
onSuccess?: (cid: string) => void;
/*
* Event triggered when the user selected files to upload.
*/
onFileChange?: (e: ChangeEvent<HTMLInputElement>) => void;
/**
* Event triggered when a file is deleted.
* The id is generated after the file are selected by the user.
*/
onDeleteItem?: (id: string) => void;
onClick?: (() => void) | undefined;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
/**
* Allow to override the previous file(s).
* If false, the user cannot upload a new file(s) until he deletes the previous file(s).
@ -86,6 +97,10 @@ const defaultProvider = () =>
);
export function Upload({
onMouseEnter,
onMouseLeave,
onClick,
onFileChange,
multiple = true,
editable = true,
onDeleteItem,
@ -109,7 +124,7 @@ export function Upload({
uploadFiles(e.dataTransfer.files);
};
const onFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const onInternalFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
uploadFiles(e.target.files);
}
@ -117,6 +132,8 @@ export function Upload({
if (input.current) {
input.current.value = "";
}
onFileChange?.(e);
};
const onClose = (id: string) => {
@ -124,17 +141,22 @@ export function Upload({
onDeleteItem?.(id);
};
const onClick = () => input.current?.click();
const onInternalClick = () => {
onClick?.();
input.current?.click();
};
return (
<>
<div
className={classnames(["upload"], ["upload-warning", !!warning])}
tabIndex={1}
onClick={onClick}
onClick={onInternalClick}
onDragOver={onDragPrevents}
onDragEnter={onDragPrevents}
onDrop={onDrop}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<ButtonIcon Icon={multiple ? FileStack : UploadIcon}></ButtonIcon>
<div className="upload-text">
@ -152,7 +174,7 @@ export function Upload({
type="file"
hidden
ref={input}
onChange={onFileChange}
onChange={onInternalFileChange}
{...attributes({ multiple: multiple })}
/>

View File

@ -20,7 +20,11 @@ const meta = {
},
},
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onClick: fn() },
args: {
onClick: fn(),
onMouseEnter: fn(),
onMouseLeave: fn(),
},
} satisfies Meta<typeof Button>;
export default meta;

View File

@ -15,7 +15,7 @@ const meta = {
control: { type: "select" },
},
},
args: { onClick: fn() },
args: { onClick: fn(), onMouseEnter: fn(), onMouseLeave: fn() },
} satisfies Meta<typeof ButtonIcon>;
export default meta;

View File

@ -3,6 +3,7 @@ import { Dropdown, DropdownOption } from "../src/components/Dropdown/Dropdown";
import { PdfIcon } from "../src/components/WebFileIcon/PdfIcon";
import { ImageIcon } from "../src/components/WebFileIcon/ImageIcon";
import { ChangeEvent, useState } from "react";
import { fn } from "@storybook/test";
const meta = {
title: "Forms/Dropdown",
@ -12,20 +13,46 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {},
args: {
onChange: fn(),
onSelected: fn(),
onFocus: fn(),
onBlur: fn(),
onMouseEnter: fn(),
onMouseLeave: fn(),
},
} satisfies Meta<typeof Dropdown>;
export default meta;
type Story = StoryObj<typeof meta>;
const Template = () => {
const [value, setValue] = useState<string>("");
const onChange = (e: ChangeEvent<HTMLInputElement>) =>
setValue(e.currentTarget.value);
type Props = {
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
const onSelected = (o: DropdownOption) => setValue(o.title);
onSelected?: (o: DropdownOption) => void;
onClick?: () => void;
onMouseEnter?: () => void;
onBlur?: () => void;
};
const Template = (p: Props) => {
const [value, setValue] = useState<string>("");
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
p.onChange(e);
setValue(e.currentTarget.value);
};
const onSelected = (o: DropdownOption) => {
setValue(o.title);
p.onSelected?.(o);
};
return (
<Dropdown
{...p}
placeholder="Select your file"
onChange={onChange}
onSelected={onSelected}
@ -46,7 +73,7 @@ const Template = () => {
);
};
export const Default = Template.bind({});
export const Default = Template;
export const CustomStyle: Story = {
args: {
@ -54,6 +81,5 @@ export const CustomStyle: Story = {
options: [],
style: { "--codex-input-border": "1px solid red" },
value: "",
onChange: () => "",
},
};

View File

@ -11,7 +11,13 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {},
args: { onChange: fn() },
args: {
onFocus: fn(),
onBlur: fn(),
onMouseEnter: fn(),
onMouseLeave: fn(),
onChange: fn(),
},
} satisfies Meta<typeof Input>;
export default meta;

View File

@ -10,7 +10,14 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {},
args: { onChange: fn() },
args: {
onGroupChange: fn(),
onChange: fn(),
onFocus: fn(),
onBlur: fn(),
onMouseEnter: fn(),
onMouseLeave: fn(),
},
} satisfies Meta<typeof InputGroup>;
export default meta;

View File

@ -10,7 +10,13 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {},
args: { onChange: fn() },
args: {
onFocus: fn(),
onBlur: fn(),
onMouseEnter: fn(),
onMouseLeave: fn(),
onChange: fn(),
},
} satisfies Meta<typeof Select>;
export default meta;

View File

@ -1,6 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Stepper } from "../src/components/Stepper/Stepper";
import React, { useState } from "react";
import { fn } from "@storybook/test";
const meta = {
title: "Advanced/Stepper",
@ -10,13 +11,16 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {},
// args: { onClick: fn() },
args: { onChangeStep: fn() },
} satisfies Meta<typeof Stepper>;
export default meta;
type Story = StoryObj<typeof meta>;
const Template = () => {
type Props = {
onChangeStep: (s: number, state: "before" | "end") => void | Promise<void>;
};
const Template = (p: Props) => {
const [step, setStep] = useState(0);
const [progress, setProgress] = useState(false);
@ -24,6 +28,8 @@ const Template = () => {
const title = titles[step];
const onChangeStep = (newStep: number, event: "before" | "end") => {
p.onChangeStep(newStep, event);
if (event === "before") {
setProgress(true);
return;

View File

@ -2,6 +2,7 @@ import type { Meta } from "@storybook/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { UploadResponse } from "@codex/sdk-js";
import { Upload } from "../src/components/Upload/Upload";
import { fn } from "@storybook/test";
const meta = {
title: "Advanced/Upload",
@ -11,16 +12,36 @@ const meta = {
},
tags: ["autodocs"],
argTypes: {},
args: {
onMouseEnter: fn(),
onMouseLeave: fn(),
onClick: fn(),
onSuccess: fn(),
onDeleteItem: fn(),
onFileChange: fn(),
},
} satisfies Meta<typeof Upload>;
export default meta;
const queryClient = new QueryClient();
const Template = () => {
type Props = {
onClick?: () => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
onSuccess?: () => void;
onDeletedItem?: () => void;
};
const Template = (p: Props) => {
return (
<QueryClientProvider client={queryClient}>
{<Upload useWorker={false} multiple />}
{<Upload useWorker={false} multiple {...p} />}
</QueryClientProvider>
);
};
@ -59,11 +80,11 @@ const slowProvider = () =>
}
);
const SlowTemplate = () => {
const SlowTemplate = (p: Props) => {
return (
<div className="demo">
<QueryClientProvider client={queryClient}>
{<Upload useWorker={false} multiple provider={slowProvider} />}
{<Upload useWorker={false} multiple provider={slowProvider} {...p} />}
</QueryClientProvider>
</div>
);
@ -71,7 +92,7 @@ const SlowTemplate = () => {
export const Slow = SlowTemplate.bind({});
const SingleTemplate = () => {
const SingleTemplate = (p: Props) => {
return (
<div className="demo">
<QueryClientProvider client={queryClient}>
@ -81,6 +102,7 @@ const SingleTemplate = () => {
multiple={false}
editable={false}
provider={slowProvider}
{...p}
/>
}
</QueryClientProvider>