Skip to content

Commit

Permalink
add popover and update props
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-babylonlabs committed Dec 6, 2024
1 parent 6e111a6 commit e560b94
Show file tree
Hide file tree
Showing 27 changed files with 655 additions and 227 deletions.
2 changes: 1 addition & 1 deletion src/components/Dialog/Dialog.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Dialog, DialogFooter, DialogBody, DialogHeader } from "./index";

import { ScrollLocker } from "@/context/Dialog.context";
import { Button } from "@/components/Button";
import { Checkbox } from "@/components/Inputs";
import { Checkbox } from "@/components/Form";
import { Text } from "@/components/Text";
import { Heading } from "@/index";

Expand Down
2 changes: 1 addition & 1 deletion src/components/Dialog/MobileDialog.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MobileDialog, DialogFooter, DialogBody, DialogHeader } from "./index";

import { ScrollLocker } from "@/context/Dialog.context";
import { Button } from "@/components/Button";
import { Checkbox } from "@/components/Inputs";
import { Checkbox } from "@/components/Form";
import { Text } from "@/components/Text";

const meta: Meta<typeof MobileDialog> = {
Expand Down
File renamed without changes.
File renamed without changes.
55 changes: 55 additions & 0 deletions src/components/Form/Input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.bbn-input {
@apply relative flex flex-col;

&-wrapper {
@apply flex items-center rounded border border-primary-light/20 bg-secondary-contrast px-4 py-2 text-primary-light transition-colors;

&:focus-within {
@apply border-primary-light;
}

&.bbn-input-error {
@apply border-error-main;
}

&.bbn-input-warning {
@apply border-warning-main;
}

&.bbn-input-success {
@apply border-success-main;
}

&.bbn-input-disabled {
@apply opacity-50 pointer-events-none;
}
}

&-field {
@apply w-full bg-transparent text-sm outline-none placeholder:text-primary-light/50;
}

&-suffix {
@apply ml-2 flex items-center text-primary-light/50;
}

&-prefix {
@apply mr-2 flex items-center text-primary-light/50;
}

&-state-text {
@apply mt-1 text-sm;
}

&-state-text-error {
@apply text-error-main;
}

&-state-text-warning {
@apply text-warning-main;
}

&-state-text-success {
@apply text-success-main;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Meta, StoryObj } from "@storybook/react";
import { RiSearchLine } from "react-icons/ri";
import { useState } from "react";

import { Loader } from "@/components/Loader";

import { Input } from "./Input";

const meta: Meta<typeof Input> = {
Expand All @@ -11,34 +13,46 @@ const meta: Meta<typeof Input> = {

export default meta;

type Story = StoryObj<typeof meta>;
type Story = StoryObj<typeof Input>;

export const Default: Story = {
args: {
placeholder: "Default input",
},
};

export const WithSuffix: Story = {
export const Disabled: Story = {
args: {
placeholder: "Search by Name or Public Key",
suffix: <RiSearchLine size={20} />,
placeholder: "Disabled input",
disabled: true,
},
};

export const WithClickableSuffix: Story = {
export const Error: Story = {
args: {
placeholder: "Click the search icon",
suffix: <RiSearchLine size={20} />,
onSuffixClick: () => alert("Search clicked!"),
placeholder: "Input with error",
state: {
type: "error",
text: "This field is required",
},
},
};

export const Loading: Story = {
export const WithSuffix: Story = {
args: {
placeholder: "Loading state",
suffix: <RiSearchLine size={20} />,
isLoading: true,
placeholder: "Search...",
decorators: {
suffix: <RiSearchLine size={20} />,
},
},
};

export const WithPrefix: Story = {
args: {
placeholder: "Amount",
decorators: {
prefix: "$",
},
},
};

Expand All @@ -54,17 +68,15 @@ export const LoadingWithInteraction: Story = {
return (
<Input
placeholder="Click search to see loading"
suffix={<RiSearchLine size={20} />}
isLoading={isLoading}
onSuffixClick={handleSearch}
decorators={{
suffix: (
<button onClick={handleSearch} disabled={isLoading} className="h-5 w-5">
{isLoading ? <Loader size={20} /> : <RiSearchLine size={20} />}
</button>
),
}}
disabled={isLoading}
/>
);
},
};

export const Disabled: Story = {
args: {
placeholder: "Disabled input",
disabled: true,
},
};
47 changes: 47 additions & 0 deletions src/components/Form/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { forwardRef, type DetailedHTMLProps, type InputHTMLAttributes, type ReactNode } from "react";
import { twJoin } from "tailwind-merge";
import "./Input.css";

export interface InputProps
extends Omit<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "prefix" | "suffix"> {
className?: string;
wrapperClassName?: string;
decorators?: {
prefix?: ReactNode;
suffix?: ReactNode;
};
disabled?: boolean;
state?: {
type?: "error" | "warning";
text?: string;
render?: ReactNode;
};
}

export const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, wrapperClassName, decorators, disabled = false, state, ...props }, ref) => {
const stateClass =
state?.type === "error" ? "bbn-input-error" : state?.type === "warning" ? "bbn-input-warning" : "";

const stateTextClass =
state?.type === "error"
? "bbn-input-state-text-error"
: state?.type === "warning"
? "bbn-input-state-text-warning"
: "";

return (
<div className={twJoin("bbn-input", wrapperClassName)}>
<div className={twJoin("bbn-input-wrapper", disabled && "bbn-input-disabled", stateClass)}>
{decorators?.prefix && <div className="bbn-input-prefix">{decorators.prefix}</div>}
<input ref={ref} className={twJoin("bbn-input-field", className)} disabled={disabled} {...props} />
{decorators?.suffix && <div className="bbn-input-suffix">{decorators.suffix}</div>}
</div>
{state?.render ??
(state?.text && <span className={twJoin("bbn-input-state-text", stateTextClass)}>{state.text}</span>)}
</div>
);
},
);

Input.displayName = "Input";
File renamed without changes.
File renamed without changes.
45 changes: 45 additions & 0 deletions src/components/Form/Select.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.bbn-select {
@apply relative;
width: var(--select-width, auto);

&-trigger {
@apply flex w-full cursor-pointer items-center justify-between rounded border border-primary-light bg-secondary-contrast px-4 py-2 text-sm text-primary-light transition-all outline-none;

&:focus-visible {
@apply border-primary-light ring-1 ring-primary-light;
}
}

&-icon {
@apply ml-2 text-primary-light transition-transform;

&-open {
@apply rotate-180;
}
}

&-menu {
@apply z-50 mt-1 max-h-60 overflow-auto rounded border border-primary-light/20 bg-secondary-contrast py-1 shadow-lg;
min-width: max(var(--menu-width, var(--component-width)), var(--component-width));
width: var(--menu-width, var(--component-width));
position: var(--menu-position, absolute);
left: var(--menu-left, 0);
transform: var(--menu-transform, none);
}

&-option {
@apply cursor-pointer px-4 py-3 text-sm text-primary-light transition-colors hover:bg-primary-light/10;

&-selected {
@apply border-l-2 border-primary-light bg-primary-light/10;
}

&-highlighted {
@apply bg-primary-light/20;
}
}

&-disabled {
@apply cursor-not-allowed opacity-50 pointer-events-none;
}
}
102 changes: 102 additions & 0 deletions src/components/Form/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";

import { Select } from "./Select";

const meta: Meta<typeof Select> = {
component: Select,
tags: ["autodocs"],
};

export default meta;

type Story = StoryObj<typeof meta>;

const options = [
{ value: "active", label: "Active" },
{ value: "inactive", label: "Inactive" },
{ value: "pending", label: "Pending" },
];

export const Default: Story = {
args: {
options,
placeholder: "Select status",
},
};

export const Controlled: Story = {
args: {
options,
placeholder: "Select status",
},
render: (args) => {
const [value, setValue] = useState("active");

return (
<Select
{...args}
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
/>
);
},
};

export const CenterAligned: Story = {
args: {
options,
selectWidth: "200px",
menuWidth: "300px",
},
render: (args) => (
<div className="flex w-full justify-center gap-1">
<Select {...args} placeholder="Select status" selectWidth={args.selectWidth} menuWidth={args.menuWidth} />
</div>
),
};

export const LeftAligned: Story = {
args: {
options,
selectWidth: "200px",
menuWidth: "300px",
},
render: (args) => (
<div className="flex w-full">
<Select {...args} placeholder="Select status" selectWidth={args.selectWidth} menuWidth={args.menuWidth} />
</div>
),
};

export const RightAligned: Story = {
args: {
options,
selectWidth: "200px",
menuWidth: "300px",
},
render: (args) => (
<div className="flex w-full justify-between gap-1">
<div className="w-1/2">Title</div>
<Select {...args} placeholder="Select status" selectWidth={args.selectWidth} menuWidth={args.menuWidth} />
</div>
),
};

export const Disabled: Story = {
args: {
options,
placeholder: "Select status",
disabled: true,
},
};

export const CustomSelectedDisplay: Story = {
args: {
options,
placeholder: "Select status",
renderSelected: (option) => `Showing ${option.value}`,
},
};
Loading

0 comments on commit e560b94

Please sign in to comment.