Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions ui/content/docs/components/input.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
---
title: Input
description: A text input component
---

import { Input } from '@trakteer/input';
import { TypeTable } from 'fumadocs-ui/components/type-table';

<div className="flex gap-4 p-16 border items-center justify-center rounded-xl">
<Input placeholder="Enter your name" />
</div>

### Usage

```bash tab="npm"
npx @fydemy/ui@latest add input
```

```bash tab="pnpm"
pnpm dlx @fydemy/ui@latest add input
```

```bash tab="yarn"
yarn @fydemy/ui@latest add input
```

```bash tab="bun"
bunx --bun @fydemy/ui@latest add input
```

```jsx
<Input placeholder="Enter your name" />
```

<AutoTypeTable
type={`
export type InputProps = {
type?: "text" | "email" | "password" | "number" | "tel" | "url" | "search";
placeholder?: string;
value?: string;
defaultValue?: string;
variant?: "default" | "error" | "success";
disabled?: boolean;
readonly?: boolean;
label?: string;
helperText?: string;
errorMessage?: string;
icon?: React.ReactNode;
iconPosition?: "left" | "right";
className?: string;
id?: string;
name?: string;
required?: boolean;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
};
`}
/>

### Examples

import { Mail, Search, Lock, User } from 'lucide-react';

#### With Label

An input with a label for better accessibility and user experience.

<div className="flex flex-col gap-4 p-16 border items-center justify-center rounded-xl">
<Input label="Email Address" placeholder="name@example.com" type="email" />
<Input label="Full Name" placeholder="John Doe" />
</div>

```jsx
<Input label="Email Address" placeholder="name@example.com" type="email" />
<Input label="Full Name" placeholder="John Doe" />
```

#### With Icon

An input with icon on the left or right side. Use `lucide-react` icon is recommended.

<div className="flex flex-col gap-4 p-16 border items-center justify-center rounded-xl">
<Input label="Email" placeholder="name@example.com" type="email" icon={<Mail />} iconPosition="left" />
<Input label="Search" placeholder="Search..." type="search" icon={<Search />} iconPosition="right" />
</div>

```jsx
<Input
label="Email"
placeholder="name@example.com"
type="email"
icon={<Mail />}
iconPosition="left"
/>
<Input
label="Search"
placeholder="Search..."
type="search"
icon={<Search />}
iconPosition="right"
/>
```

#### Variants

Control the variant either `default`, `error`, or `success`. `default` is the default value.

<div className="flex flex-col gap-4 p-16 border items-center justify-center rounded-xl">
<Input label="Default" placeholder="Default input" variant="default" />
<Input label="Success" placeholder="Valid input" variant="success" />
<Input label="Error" placeholder="Invalid input" variant="error" />
</div>

```jsx
<Input label="Default" placeholder="Default input" variant="default" />
<Input label="Success" placeholder="Valid input" variant="success" />
<Input label="Error" placeholder="Invalid input" variant="error" />
```

#### With Helper Text

Add helper text or error message to provide additional context or validation feedback.

<div className="flex flex-col gap-4 p-16 border items-center justify-center rounded-xl">
<Input label="Password" type="password" placeholder="Enter your password" helperText="Must be at least 8 characters" />
<Input label="Email" type="email" placeholder="name@example.com" variant="error" errorMessage="Please enter a valid email address" />
</div>

```jsx
<Input
label="Password"
type="password"
placeholder="Enter your password"
helperText="Must be at least 8 characters"
/>
<Input
label="Email"
type="email"
placeholder="name@example.com"
variant="error"
errorMessage="Please enter a valid email address"
/>
```

#### States

Input supports `disabled` and `readonly` states.

<div className="flex flex-col gap-4 p-16 border items-center justify-center rounded-xl">
<Input label="Disabled" placeholder="Cannot edit" disabled />
<Input label="Readonly" placeholder="Read only value" readonly value="Read only content" />
</div>

```jsx
<Input label="Disabled" placeholder="Cannot edit" disabled />
<Input label="Readonly" placeholder="Read only value" readonly value="Read only content" />
```

#### Input Types

Support for various HTML input types.

<div className="flex flex-col gap-4 p-16 border items-center justify-center rounded-xl">
<Input label="Text" type="text" placeholder="Enter text" />
<Input label="Email" type="email" placeholder="name@example.com" icon={<Mail />} iconPosition="left" />
<Input label="Password" type="password" placeholder="Enter password" icon={<Lock />} iconPosition="left" />
<Input label="Number" type="number" placeholder="Enter number" />
<Input label="Search" type="search" placeholder="Search..." icon={<Search />} iconPosition="left" />
</div>

```jsx
<Input label="Text" type="text" placeholder="Enter text" />
<Input label="Email" type="email" placeholder="name@example.com" icon={<Mail />} iconPosition="left" />
<Input label="Password" type="password" placeholder="Enter password" icon={<Lock />} iconPosition="left" />
<Input label="Number" type="number" placeholder="Enter number" />
<Input label="Search" type="search" placeholder="Search..." icon={<Search />} iconPosition="left" />
```

#### Controlled Input

Use `value` and `onChange` to control the input state.

```jsx
const [value, setValue] = useState('');

<Input label="Controlled Input" value={value} onChange={(e) => setValue(e.target.value)} placeholder="Type something..." />;
```
157 changes: 157 additions & 0 deletions ui/public/trakteer/input/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/* Input Wrapper */
.input-wrapper[data-theme='trakteer'] {
font-family: 'Pontano Sans', sans-serif;
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
}

/* Input Label */
.input-wrapper[data-theme='trakteer'] .input-label {
font-size: 14px;
font-weight: 600;
color: var(--trakteer-text-primary);
margin-bottom: 0.25rem;
}

/* Input Container */
.input-wrapper[data-theme='trakteer'] .input-container {
position: relative;
display: flex;
align-items: center;
width: 100%;
}

/* Input Base Styles */
.input-wrapper[data-theme='trakteer'] .input {
font-family: 'Pontano Sans', sans-serif;
width: 100%;
padding: 0.75rem 1rem;
font-size: 16px;
color: var(--trakteer-text-primary);
background-color: var(--trakteer-bg-primary);
border: 1px solid var(--trakteer-border-medium);
border-bottom: 4px solid var(--trakteer-border-medium);
border-radius: 12px;
outline: none;
transition: all 0.2s ease;
}

.input-wrapper[data-theme='trakteer'] .input::placeholder {
color: var(--trakteer-text-muted);
}

/* Input with Icon */
.input-wrapper[data-theme='trakteer'] .input-container:has(.input-icon-left) .input {
padding-left: 2.75rem;
}

.input-wrapper[data-theme='trakteer'] .input-container:has(.input-icon-right) .input {
padding-right: 2.75rem;
}

/* Input Icon */
.input-wrapper[data-theme='trakteer'] .input-icon {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
color: var(--trakteer-text-secondary);
z-index: 1;
}

.input-wrapper[data-theme='trakteer'] .input-icon-left {
left: 1rem;
}

.input-wrapper[data-theme='trakteer'] .input-icon-right {
right: 1rem;
}

.input-wrapper[data-theme='trakteer'] .input-icon svg {
width: 18px;
height: 18px;
}

/* Input Default Variant */
.input-wrapper[data-theme='trakteer'] .input[data-variant='default'] {
border-color: var(--trakteer-border-medium);
border-bottom-color: var(--trakteer-border-medium);
}

.input-wrapper[data-theme='trakteer'] .input[data-variant='default']:focus {
border-color: var(--trakteer-default-border);
border-bottom-color: var(--trakteer-default-border);
background-color: var(--trakteer-bg-primary);
}

.input-wrapper[data-theme='trakteer'] .input[data-variant='default']:focus + .input-icon-right,
.input-wrapper[data-theme='trakteer'] .input-container:has(.input[data-variant='default']:focus) .input-icon {
color: var(--trakteer-default);
}

/* Input Error Variant */
.input-wrapper[data-theme='trakteer'] .input[data-variant='error'] {
border-color: var(--trakteer-destructive-border);
border-bottom-color: var(--trakteer-destructive-border);
background-color: var(--trakteer-bg-primary);
}

.input-wrapper[data-theme='trakteer'] .input[data-variant='error']:focus {
border-color: var(--trakteer-destructive-border);
border-bottom-color: var(--trakteer-destructive-border);
}

.input-wrapper[data-theme='trakteer'] .input[data-variant='error']:focus + .input-icon-right,
.input-wrapper[data-theme='trakteer'] .input-container:has(.input[data-variant='error']:focus) .input-icon {
color: var(--trakteer-destructive);
}

/* Input Success Variant */
.input-wrapper[data-theme='trakteer'] .input[data-variant='success'] {
border-color: var(--trakteer-default-border);
border-bottom-color: var(--trakteer-default-border);
background-color: var(--trakteer-bg-primary);
}

.input-wrapper[data-theme='trakteer'] .input[data-variant='success']:focus {
border-color: var(--trakteer-default-border);
border-bottom-color: var(--trakteer-default-border);
}

.input-wrapper[data-theme='trakteer'] .input[data-variant='success']:focus + .input-icon-right,
.input-wrapper[data-theme='trakteer'] .input-container:has(.input[data-variant='success']:focus) .input-icon {
color: var(--trakteer-default);
}

/* Input Disabled State */
.input-wrapper[data-theme='trakteer'] .input:disabled {
background-color: var(--trakteer-bg-secondary);
border-color: var(--trakteer-border-light);
border-bottom-color: var(--trakteer-border-light);
color: var(--trakteer-text-muted);
cursor: not-allowed;
opacity: 0.6;
}

.input-wrapper[data-theme='trakteer'] .input:disabled::placeholder {
color: var(--trakteer-text-muted);
}

/* Input Readonly State */
.input-wrapper[data-theme='trakteer'] .input[readonly] {
background-color: var(--trakteer-bg-secondary);
cursor: default;
}

/* Input Helper Text */
.input-wrapper[data-theme='trakteer'] .input-helper {
font-size: 12px;
color: var(--trakteer-text-secondary);
margin-top: 0.25rem;
}

.input-wrapper[data-theme='trakteer'] .input-helper-error {
color: var(--trakteer-destructive);
}
Loading
Loading