mirror of
https://github.com/makeplane/plane.git
synced 2025-12-23 15:19:37 +01:00
[WEB-4731] feat: add baseui input component to propel package (#7769)
* ✨ feat: add input component to propel package * 🚨 fix: lint * 🚨 fix: lint * fix: add aria-invalid attribute to Input component for better accessibility * chore: fix formatting in package.json and reorder imports in popover-menu stories
This commit is contained in:
@@ -100,6 +100,10 @@
|
|||||||
"import": "./dist/icons/index.mjs",
|
"import": "./dist/icons/index.mjs",
|
||||||
"require": "./dist/icons/index.js"
|
"require": "./dist/icons/index.js"
|
||||||
},
|
},
|
||||||
|
"./input": {
|
||||||
|
"import": "./dist/input/index.mjs",
|
||||||
|
"require": "./dist/input/index.js"
|
||||||
|
},
|
||||||
"./menu": {
|
"./menu": {
|
||||||
"import": "./dist/menu/index.mjs",
|
"import": "./dist/menu/index.mjs",
|
||||||
"require": "./dist/menu/index.js"
|
"require": "./dist/menu/index.js"
|
||||||
|
|||||||
1
packages/propel/src/input/index.ts
Normal file
1
packages/propel/src/input/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./input";
|
||||||
153
packages/propel/src/input/input.stories.tsx
Normal file
153
packages/propel/src/input/input.stories.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { Input } from "./index";
|
||||||
|
|
||||||
|
const meta: Meta<typeof Input> = {
|
||||||
|
title: "Components/Input",
|
||||||
|
component: Input,
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {
|
||||||
|
mode: {
|
||||||
|
control: "select",
|
||||||
|
options: ["primary", "transparent", "true-transparent"],
|
||||||
|
},
|
||||||
|
inputSize: {
|
||||||
|
control: "select",
|
||||||
|
options: ["xs", "sm", "md"],
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
control: "boolean",
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
control: "select",
|
||||||
|
options: ["text", "email", "password", "number", "tel", "url", "search"],
|
||||||
|
},
|
||||||
|
autoComplete: {
|
||||||
|
control: "select",
|
||||||
|
options: ["on", "off"],
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Input>;
|
||||||
|
|
||||||
|
const createStory = (args: Partial<React.ComponentProps<typeof Input>>): Story => ({
|
||||||
|
args: { placeholder: "Enter text...", className: "w-[400px]", ...args },
|
||||||
|
});
|
||||||
|
|
||||||
|
const createShowcaseStory = (
|
||||||
|
title: string,
|
||||||
|
sections: Array<{ label: string; props: Partial<React.ComponentProps<typeof Input>> }>
|
||||||
|
): Story => ({
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4 w-[400px]">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-sm font-medium">{title}</h3>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{sections.map(({ label, props }, index) => (
|
||||||
|
<div key={index} className="w-full">
|
||||||
|
<label className="text-xs text-gray-500">{label}</label>
|
||||||
|
<Input className="w-full" {...props} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Default = createStory({});
|
||||||
|
|
||||||
|
export const Primary = createStory({
|
||||||
|
mode: "primary",
|
||||||
|
placeholder: "Primary input",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Transparent = createStory({
|
||||||
|
mode: "transparent",
|
||||||
|
placeholder: "Transparent input",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const TrueTransparent = createStory({
|
||||||
|
mode: "true-transparent",
|
||||||
|
placeholder: "True transparent input",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ExtraSmall = createStory({
|
||||||
|
inputSize: "xs",
|
||||||
|
placeholder: "Extra small input",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Small = createStory({
|
||||||
|
inputSize: "sm",
|
||||||
|
placeholder: "Small input",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Medium = createStory({
|
||||||
|
inputSize: "md",
|
||||||
|
placeholder: "Medium input",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithError = createStory({
|
||||||
|
hasError: true,
|
||||||
|
placeholder: "Input with error",
|
||||||
|
defaultValue: "Invalid input",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Disabled = createStory({
|
||||||
|
disabled: true,
|
||||||
|
placeholder: "Disabled input",
|
||||||
|
defaultValue: "Cannot edit this",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithValue = createStory({
|
||||||
|
defaultValue: "Pre-filled value",
|
||||||
|
placeholder: "Input with value",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Email = createStory({
|
||||||
|
type: "email",
|
||||||
|
placeholder: "Enter your email",
|
||||||
|
autoComplete: "on",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Password = createStory({
|
||||||
|
type: "password",
|
||||||
|
placeholder: "Enter your password",
|
||||||
|
autoComplete: "off",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Number = createStory({
|
||||||
|
type: "number",
|
||||||
|
placeholder: "Enter a number",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Search = createStory({
|
||||||
|
type: "search",
|
||||||
|
placeholder: "Search...",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AllModes = createShowcaseStory("Input Modes", [
|
||||||
|
{ label: "Primary", props: { mode: "primary", placeholder: "Primary input" } },
|
||||||
|
{ label: "Transparent", props: { mode: "transparent", placeholder: "Transparent input" } },
|
||||||
|
{ label: "True Transparent", props: { mode: "true-transparent", placeholder: "True transparent input" } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const AllSizes = createShowcaseStory("Input Sizes", [
|
||||||
|
{ label: "Extra Small (xs)", props: { inputSize: "xs", placeholder: "Extra small input" } },
|
||||||
|
{ label: "Small (sm)", props: { inputSize: "sm", placeholder: "Small input" } },
|
||||||
|
{ label: "Medium (md)", props: { inputSize: "md", placeholder: "Medium input" } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const AllStates = createShowcaseStory("Input States", [
|
||||||
|
{ label: "Normal", props: { placeholder: "Normal input" } },
|
||||||
|
{ label: "With Error", props: { hasError: true, placeholder: "Input with error" } },
|
||||||
|
{ label: "Disabled", props: { disabled: true, placeholder: "Disabled input" } },
|
||||||
|
{ label: "With Value", props: { defaultValue: "Pre-filled value", placeholder: "Input with value" } },
|
||||||
|
]);
|
||||||
54
packages/propel/src/input/input.tsx
Normal file
54
packages/propel/src/input/input.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Input as BaseInput } from "@base-ui-components/react/input";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "../utils";
|
||||||
|
|
||||||
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
mode?: "primary" | "transparent" | "true-transparent";
|
||||||
|
inputSize?: "xs" | "sm" | "md";
|
||||||
|
hasError?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
mode = "primary",
|
||||||
|
inputSize = "sm",
|
||||||
|
hasError = false,
|
||||||
|
className = "",
|
||||||
|
autoComplete = "off",
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseInput
|
||||||
|
id={id}
|
||||||
|
ref={ref}
|
||||||
|
type={type}
|
||||||
|
name={name}
|
||||||
|
className={cn(
|
||||||
|
"block rounded-md bg-transparent text-sm placeholder-custom-text-400 focus:outline-none",
|
||||||
|
{
|
||||||
|
"rounded-md border-[0.5px] border-custom-border-200": mode === "primary",
|
||||||
|
"rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary":
|
||||||
|
mode === "transparent",
|
||||||
|
"rounded border-none bg-transparent ring-0": mode === "true-transparent",
|
||||||
|
"border-red-500": hasError,
|
||||||
|
"px-1.5 py-1": inputSize === "xs",
|
||||||
|
"px-3 py-2": inputSize === "sm",
|
||||||
|
"p-3": inputSize === "md",
|
||||||
|
},
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-invalid={hasError || undefined}
|
||||||
|
autoComplete={autoComplete}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Input.displayName = "form-input-field";
|
||||||
|
|
||||||
|
export { Input };
|
||||||
@@ -18,6 +18,7 @@ export default defineConfig({
|
|||||||
"src/emoji-reaction/index.ts",
|
"src/emoji-reaction/index.ts",
|
||||||
"src/emoji-reaction-picker/index.ts",
|
"src/emoji-reaction-picker/index.ts",
|
||||||
"src/icons/index.ts",
|
"src/icons/index.ts",
|
||||||
|
"src/input/index.ts",
|
||||||
"src/menu/index.ts",
|
"src/menu/index.ts",
|
||||||
"src/pill/index.ts",
|
"src/pill/index.ts",
|
||||||
"src/popover/index.ts",
|
"src/popover/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
import { PopoverMenu } from "./popover-menu";
|
import { PopoverMenu } from "./popover-menu";
|
||||||
|
|
||||||
type TPopoverMenu = {
|
type TPopoverMenu = {
|
||||||
|
|||||||
Reference in New Issue
Block a user