Skip to content

Commit

Permalink
Pol Gubau; Field component
Browse files Browse the repository at this point in the history
  • Loading branch information
PolGubau committed Aug 14, 2023
1 parent f7cada8 commit 6910649
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 133 deletions.
147 changes: 45 additions & 102 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,8 @@
"vite": "^4.3.8",
"vite-plugin-css-injected-by-js": "^3.1.1",
"vite-plugin-dts": "^2.3.0"
},
"dependencies": {
"tailwind-variants": "^0.1.13"
}
}
21 changes: 21 additions & 0 deletions src/components/Inputs/DateInput/DateInput.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react";
import { DateInput } from "../DateInput";

const meta = {
title: "Inputs/DateInput",
component: DateInput,
} satisfies Meta<typeof DateInput>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
onChange: (date: string) => console.log(date),
},
};
export const WithLabel: Story = {
args: {
label: "Date",
},
};
49 changes: 49 additions & 0 deletions src/components/Inputs/DateInput/DateInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import "../../../style/baseTheme.scss";

interface Props {
value?: string;
label?: string;
onChange?: (newDate: string) => void;
className?: string;
id?: string;
}

const DateInput: React.FC<Props> = ({ label, value, onChange, className, id }) => {
const handleDateChange = (e: { target: { value: string } }) => {
const selectedDate = e.target.value;
onChange?.(selectedDate);
};

const formatValue = (value: string | undefined) => {
// if not value, return the actual date
if (!value) return new Date().toISOString().split("T")[0];

// take the date string and format it to yyyy-mm-dd
// if the value is empty, return an empty string
// if the value is not empty, return the formatted value
return new Date(value).toISOString().split("T")[0];
};

return (
<div className="position-relative flex flex-col w-fit px-2 py-1 transition-all ring-1 ring-primary border-primary/50 rounded-lg focus-within:shadow-lg hover:ring-2 focus-within:ring-accent/50 ">
{label && (
<label className="text-xs" htmlFor="date">
{label}
</label>
)}
<input
type="date"
name="date"
id={id}
className={`
bg-transparent
outline-none
${className}`}
value={formatValue(value)}
onChange={handleDateChange}
/>
</div>
);
};

export default DateInput;
1 change: 1 addition & 0 deletions src/components/Inputs/DateInput/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as DateInput } from "./DateInput";
68 changes: 68 additions & 0 deletions src/components/Inputs/Field/Field.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Field } from "../Field";

const meta = {
title: "Inputs/Field",
component: Field,
} satisfies Meta<typeof Field>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
label: "Input",
},
};
export const WithValue: Story = {
args: {
label: "Input",
value: "This is a value",
},
};
export const NumberType: Story = {
args: {
label: "Number Input",
type: "number",
},
};
export const HelperText: Story = {
args: {
label: "Input",
helperText: "This is a helper text",
},
};
export const InputWithError: Story = {
args: {
label: "Input",
error: "This is an error",
},
};
export const InputWithHelperTextAndError: Story = {
args: {
label: "Input",
helperText: "This is a helper text",
error: "This is an error",
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};
export const MaxLength: Story = {
args: {
maxLength: 10,
},
};
export const Multiline: Story = {
args: {
multiline: true,
},
};
export const MultilineWithLabels: Story = {
args: {
label: "Input",
multiline: true,
},
};
98 changes: 98 additions & 0 deletions src/components/Inputs/Field/Field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { ChangeEvent } from "react";
import { containerStyles, inputStyles, labelStyles } from "./Styled";
import React from "react";
interface Props<T> {
label?: string;
name?: string;
type?: "text" | "number" | "password" | "email" | "date" | "time" | "url" | "search" | "tel";
value?: T;
multiline?: boolean;
onChange?: (value: T) => void;
error?: string;
className?: string;
disabled?: boolean;
required?: boolean;
helperText?: string;
maxLength?: number;
fullWidth?: boolean;
size?: "small" | "normal" | "large";
props?: Omit<React.InputHTMLAttributes<HTMLInputElement>, keyof Props<T>>;
}

const Field = <T extends string | number>({
label,
value = undefined,
onChange,
disabled,
required,
type = "text",
error,
multiline,
helperText,
className,
maxLength,
fullWidth = false,
size = "normal",
...props
}: Props<T>) => {
const handleChange = (e: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement>) => {
const inputValue = e.target.value;
let newValue: T = inputValue as T;
if (!onChange) return;

if (type === "number") {
newValue = Number(inputValue) as T;
if (isNaN(newValue as number)) {
newValue = "" as T;
}
}
if (!newValue) return;

onChange(newValue);
};
const [focused, setFocused] = React.useState(false);
const hasValue =
typeof value === "number" ||
type === "date" ||
type === "time" ||
type === "number" ||
Boolean(value?.length);
return (
<div className={containerStyles()}>
{multiline ? (
<textarea
onFocus={() => setFocused(true)}
rows={5}
disabled={disabled ?? false}
maxLength={maxLength}
required={required}
defaultValue={value ?? ""}
onChange={handleChange}
className={inputStyles() + className}
/>
) : (
<input
{...(props as Omit<React.InputHTMLAttributes<HTMLInputElement>, keyof Props<T>>)}
type={type}
onFocus={() => setFocused(true)}
maxLength={maxLength}
required={required}
disabled={disabled}
defaultValue={value ?? ""}
onChange={handleChange}
className={inputStyles() + className}
/>
)}
{Boolean(label) && (
<label className={labelStyles({ isUp: hasValue || focused })}>
{label}
{required && `*`}
</label>
)}
{Boolean(helperText) && <span className="color-primary/60 text-xs">{helperText}</span>}
{Boolean(error) && <span className="text-xs text-red-500">{error}</span>}
</div>
);
};

export default Field;
47 changes: 47 additions & 0 deletions src/components/Inputs/Field/Styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { tv } from "tailwind-variants";

export const containerStyles = tv({
base: `flex flex-col gap-1 relative w-full text-primary transition-all `,
});

export const labelStyles = tv({
base: `label
transition-all
absolute
text-primary/60
top-0 left-2
translate-y-1
p-1
pointer-events-none `,
variants: {
isUp: {
true: "text-primary py-0 translate-y-[-50%] bg-white text-sm focus:text-accent",
false: " ",
},
},

defaultVariants: {
isUp: false,
},
});

export const inputStyles = tv({
base: `transition-all
outline-none
rounded-lg
p-2
ring-1
ring-primary
hover:shadow-lg
focus:ring-accent
peer`,
variants: {
multiline: {
true: "input peer min-h-[100px] max-h-[300px] resize-y",
false: "input peer ",
},
},
defaultVariants: {
multiline: false,
},
});
1 change: 1 addition & 0 deletions src/components/Inputs/Field/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Field } from './Field';
7 changes: 7 additions & 0 deletions src/components/Inputs/Switch/Switch.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,10 @@ export const Disabled: Story = {
disabled: true,
},
};
export const WithError: Story = {
args: {
label: "Switch",
checked: true,
error: "This is an error",
},
};
Loading

0 comments on commit 6910649

Please sign in to comment.