@dsbtek/component-library
v1.2.0
Published
Advanced components built with shadcn/ui
Readme
@dsbtek/component-library
A collection of advanced React components built with TypeScript, Tailwind CSS, and shadcn/ui.
Table of Contents
Installation
- Install the package:
npm install @dsbtek/component-library- Install peer dependencies:
npm install react@^18 react-dom@^18- Set up Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p- Update your
tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@dsbtek/component-library/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
},
},
plugins: [require('tailwindcss-animate')],
};- Add these CSS variables to your global CSS:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}Components
Breadcrumbs
A navigation component that helps users understand their current location within a website's hierarchy.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| segments | Array<{ label: string; href?: string }> | Required | Array of breadcrumb segments
| separator | React.ReactNode | <ChevronRight className="h-4 w-4" /> | Custom separator between breadcrumb items
| className | string | - | Additional CSS classes
| onNavigate | (href: string) => void | - | Callback function when a breadcrumb is clicked
Example Usage
import { Breadcrumbs } from '@dsbtek/component-library';
function BreadcrumbsExample() {
return (
<Breadcrumbs
segments={[
{ label: 'Home', href: '/' },
{ label: 'Products', href: '/products' },
{ label: 'Electronics', href: '/products/electronics' },
{ label: 'Smartphones' },
]}
onNavigate={(href) => console.log(`Navigating to: ${href}`)}
/>
);
}ColorPicker
A comprehensive color selection component with RGB, HSL support, color schemes, and history.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| color | string | #000000 | Current color value in hex format
| onChange | (value: string) => void | - | Callback function when color changes
| className | string | - | Additional CSS classes
Example Usage
import { ColorPicker } from '@dsbtek/component-library';
import { useState } from 'react';
function ColorPickerExample() {
const [color, setColor] = useState('#3B82F6');
return (
<ColorPicker
color={color}
onChange={setColor}
/>
);
}DataTable
A feature-rich table component with sorting, filtering, pagination, and more.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| data | T[] | Required | Array of data items
| columns | ColumnDef<T>[] | Required | Array of column definitions
| meta | { current_page: number; last_page: number; per_page: number; total: number } | - | Pagination metadata
| loading | boolean | false | Loading state of the table
| onPageChange | (page: number) => void | - | Callback when page changes
| onPerPageChange | (perPage: number) => void | - | Callback when items per page changes
| onSort | (column: string, direction: 'asc' | 'desc' | null) => void | - | Callback when sorting changes
| onSearch | (value: string) => void | - | Callback when search value changes
| onFilter | (filters: AdvancedFilter[]) => void | - | Callback when filters change
| pageSizeOptions | number[] | [10,20,50] | Available options for items per page
| renderItemActions | (row: T) => React.ReactNode | - | Function to render action buttons for each row
| features | Partial<DataTableFeatures> | - | Object to enable/disable various table features
Example Usage
import { DataTable } from '@dsbtek/component-library';
function DataTableExample() {
const columns = [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
{ accessorKey: 'role', header: 'Role' },
];
const data = [
{ id: '1', name: 'John Doe', email: '[email protected]', role: 'Admin' },
{ id: '2', name: 'Jane Smith', email: '[email protected]', role: 'User' },
// ... more data
];
return (
<DataTable
data={data}
columns={columns}
features={{
sorting: true,
pagination: true,
search: true,
}}
onPageChange={(page) => console.log(`Page changed to ${page}`)}
onSort={(column, direction) => console.log(`Sorting ${column} ${direction}`)}
/>
);
}DateTimePicker
A versatile date and time selection component with support for ranges, time-only, and date-only modes.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| date | DateValue | DateRange | undefined | null | - | Selected date(s)
| setDate | (date: DateValue | DateRange | undefined) => void | Required | Callback function when date changes
| isRange | boolean | false | Enable date range selection
| includeTime | boolean | true | Include time selection
| dateOnly | boolean | false | Show date picker only
| timeOnly | boolean | false | Show time picker only
| minDate | Date | - | Minimum selectable date
| maxDate | Date | - | Maximum selectable date
| disabledDates | Date[] | - | Array of disabled dates
| clearable | boolean | true | Allow clearing the selection
| disabled | boolean | false | Disable the input
Example Usage
import { DateTimePicker } from '@dsbtek/component-library';
import { useState } from 'react';
function DateTimePickerExample() {
const [date, setDate] = useState<Date | undefined>(new Date());
return (
<DateTimePicker
date={date}
setDate={setDate}
includeTime={true}
/>
);
}FileInput
A file upload component with drag and drop support, previews, and progress indication.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| value | FileWithPreview[] | - | Array of selected files
| onChange | (files: FileWithPreview[]) => void | Required | Callback when files change
| multiple | boolean | false | Allow multiple file selection
| accept | Record<string, string[]> | { "image/*": [".png", ".jpg", ".jpeg", ".gif"], "application/pdf": [".pdf"] } | Accepted file types
| maxSize | number | 2MB | Maximum file size in bytes
| maxFiles | number | 5 | Maximum number of files
| disabled | boolean | false | Disable the input
| loading | boolean | false | Show loading state
| progress | number | number[] | - | Upload progress percentage
| onRemove | (file: FileWithPreview) => void | - | Callback when a file is removed
Example Usage
import { FileInput } from '@dsbtek/component-library';
import { useState } from 'react';
function FileInputExample() {
const [files, setFiles] = useState([]);
return (
<FileInput
value={files}
onChange={setFiles}
multiple={true}
maxSize={5 * 1024 * 1024} // 5MB
/>
);
}MultiSelect
A flexible select component that supports single or multiple selection with grouping.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| options | OptionType[] | Required | Array of selectable options
| selected | string[] | Required | Array of selected option values
| onChange | (value: string[]) => void | Required | Callback when selection changes
| placeholder | string | "Select..." | Placeholder text
| className | string | - | Additional CSS classes
| multiple | boolean | false | Allow multiple selection
| onLoadMore | () => void | - | Callback for infinite loading
| hasMore | boolean | false | Indicates if more options can be loaded
| isDialog | boolean | false | Use dialog instead of drawer on mobile
Example Usage
import { MultiSelect } from '@dsbtek/component-library';
import { useState } from 'react';
function MultiSelectExample() {
const [selected, setSelected] = useState(['react']);
const options = [
{ label: 'React', value: 'react', group: 'Frontend' },
{ label: 'Vue', value: 'vue', group: 'Frontend' },
{ label: 'Angular', value: 'angular', group: 'Frontend' },
{ label: 'Node.js', value: 'nodejs', group: 'Backend' },
{ label: 'Express', value: 'express', group: 'Backend' },
];
return (
<MultiSelect
options={options}
selected={selected}
onChange={setSelected}
placeholder="Select technologies..."
multiple={true}
/>
);
}MultiStepper
A comprehensive multi-step form component with navigation, validation, and customizable steps.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| context | React.Context<UseMultiStepFormTypeOptions<T>> | Required | Context created with buildMultiStepForm
| previousLabel | string | "Previous" | Label for the previous button
| nextLabel | string | "Next" | Label for the next button
| endStepLabel | string | "Submit" | Label for the submit button
| showNavbar | boolean | true | Show the step navigation bar
| showButtons | boolean | true | Show the navigation buttons
| isLoading | boolean | false | Loading state for the submit button
| className | string | "" | Additional CSS classes
| debug | boolean | false | Show debug information
Utility Functions
| Function | Description |-----|----- | buildMultiStepForm | Creates a context and provider for a multi-step form | useMultiStepForm | Hook for accessing multi-step form functionality
Example Usage
import {
MultiStepper,
buildMultiStepForm,
useMultiStepForm,
Form
} from '@dsbtek/component-library';
import { z } from 'zod';
import { useForm, FormProvider, useFormContext } from 'react-hook-form';
import {
FormField,
FormItem,
FormLabel,
FormControl,
FormMessage
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { PasswordInput } from '@dsbtek/component-library';
import { MultiSelect } from '@dsbtek/component-library';
import { FileInput } from '@dsbtek/component-library';
// Define your form schema
const signupSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Please enter a valid email"),
phone: z.string().min(10, "Please enter a valid phone number"),
password: z.string().min(8, "Password must be at least 8 characters"),
password_confirmation: z.string(),
roles: z.array(z.string()),
permissions: z.array(z.string()),
image: z.any().optional(),
}).refine((data) => data.password === data.password_confirmation, {
message: "Passwords do not match",
path: ["password_confirmation"],
});
// Step 1: Basic Information
function Step1() {
const { control } = useFormContext();
return (
<>
<FormField
control={control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Enter your name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="Enter your email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Phone</FormLabel>
<FormControl>
<Input placeholder="Enter your phone number" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
}
// Step 2: Security
function Step2() {
const { control } = useFormContext();
return (
<>
<FormField
control={control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<PasswordInput placeholder="Enter your password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="password_confirmation"
render={({ field }) => (
<FormItem>
<FormLabel>Confirm Password</FormLabel>
<FormControl>
<PasswordInput placeholder="Confirm your password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
}
// Step 3: Roles & Permissions
function Step3() {
const { control } = useFormContext();
const roleOptions = [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
{ label: 'Editor', value: 'editor' },
];
const permissionOptions = [
{ label: 'Create', value: 'create', group: 'Content' },
{ label: 'Edit', value: 'edit', group: 'Content' },
{ label: 'Delete', value: 'delete', group: 'Content' },
{ label: 'View Users', value: 'view_users', group: 'Users' },
{ label: 'Manage Users', value: 'manage_users', group: 'Users' },
];
return (
<>
<FormField
control={control}
name="roles"
render={({ field }) => (
<FormItem>
<FormLabel>Roles</FormLabel>
<FormControl>
<MultiSelect
options={roleOptions}
selected={field.value || []}
onChange={field.onChange}
placeholder="Select roles"
multiple={true}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name="permissions"
render={({ field }) => (
<FormItem>
<FormLabel>Permissions</FormLabel>
<FormControl>
<MultiSelect
options={permissionOptions}
selected={field.value || []}
onChange={field.onChange}
placeholder="Select permissions"
multiple={true}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
}
// Step 4: Profile Picture
function Step4() {
const { control } = useFormContext();
return (
<FormField
control={control}
name="image"
render={({ field }) => (
<FormItem>
<FormLabel>Profile Picture</FormLabel>
<FormControl>
<FileInput
value={field.value || []}
onChange={field.onChange}
accept={{ "image/*": [".png", ".jpg", ".jpeg"] }}
maxSize={2 * 1024 * 1024} // 2MB
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
}
// Define your form steps
const forms: Form<z.infer<typeof signupSchema>>[] = [
{ id: 1, label: "Basic Information", form: Step1, fields: ["name", "email", "phone"] },
{ id: 2, label: "Security", form: Step2, fields: ["password", "password_confirmation"] },
{ id: 3, label: "Roles & Permissions", form: Step3, fields: ["roles", "permissions"] },
{ id: 4, label: "Profile Picture", form: Step4, fields: ["image"] },
];
// Initial form data
const initialFormData = {
name: "",
email: "",
phone: "",
password: "",
password_confirmation: "",
roles: [],
permissions: [],
image: undefined,
};
// Create form context and provider
const { FormContext, FormProvider } = buildMultiStepForm(
{
schema: signupSchema,
currentStep: 0,
setCurrentStep: () => {},
forms,
saveFormData: async (data) => {
// Submit your form data
console.log("Form submitted:", data);
return { success: true };
},
},
signupSchema,
initialFormData
);
// Use the multi-step form
function SignupForm() {
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (data) => {
setIsLoading(true);
try {
// Submit your form data
await saveUserData(data);
toast.success("Account created successfully!");
} catch (error) {
toast.error("Failed to create account");
} finally {
setIsLoading(false);
}
};
return (
<FormProvider>
<div className="max-w-md mx-auto p-6 bg-card rounded-lg shadow-md">
<h1 className="text-2xl font-bold mb-6 text-center">Create Account</h1>
<MultiStepper
context={FormContext}
previousLabel="Back"
nextLabel="Continue"
endStepLabel="Sign Up"
isLoading={isLoading}
className="space-y-6"
/>
</div>
</FormProvider>
);
}PasswordInput
A password input component with show/hide functionality.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| className | string | - | Additional CSS classes
| showPasswordLabel | string | "Show password" | Screen reader label for show password button
| hidePasswordLabel | string | "Hide password" | Screen reader label for hide password button
| ...props | React.InputHTMLAttributes<HTMLInputElement> | - | All standard input props
Example Usage
import { PasswordInput } from '@dsbtek/component-library';
import { useState } from 'react';
function PasswordInputExample() {
const [password, setPassword] = useState('');
return (
<PasswordInput
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
/>
);
}PhoneInput
An international phone number input with country selection and validation.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| value | string | - | Phone number value
| defaultCountry | CountryCode | "US" | Default country code
| ...props | React.ComponentPropsWithoutRef<'input'> | - | All standard input props
Example Usage
import { PhoneInput, getPhoneData } from '@dsbtek/component-library';
import { useState, useEffect } from 'react';
function PhoneInputExample() {
const [phone, setPhone] = useState('+1');
const [phoneData, setPhoneData] = useState(getPhoneData(phone));
useEffect(() => {
setPhoneData(getPhoneData(phone));
}, [phone]);
return (
<PhoneInput
value={phone}
onChange={(e) => setPhone(e.target.value)}
defaultCountry="US"
/>
);
}ResponsiveAlertDialog
An alert dialog component that adapts to desktop (modal) and mobile (drawer) views.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| trigger | React.ReactNode | Required | Element that triggers the dialog
| title | string | Required | Dialog title
| description | string | Required | Dialog description
| cancelText | string | "Cancel" | Text for the cancel button
| confirmText | string | "Continue" | Text for the confirm button
| onConfirm | () => void | Required | Callback when confirm button is clicked
Example Usage
import { ResponsiveAlertDialog } from '@dsbtek/component-library';
import { Button } from '@dsbtek/component-library';
function AlertDialogExample() {
const handleDelete = () => {
console.log('Item deleted');
};
return (
<ResponsiveAlertDialog
trigger={<Button variant="destructive">Delete Item</Button>}
title="Are you sure?"
description="This action cannot be undone. This will permanently delete the item."
cancelText="Cancel"
confirmText="Delete"
onConfirm={handleDelete}
/>
);
}ResponsiveDialog
A dialog component that adapts to desktop (modal) and mobile (drawer) views.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| trigger | React.ReactNode | Required | Element that triggers the dialog
| title | string | Required | Dialog title
| description | string | Required | Dialog description
| children | React.ReactNode | Required | Dialog content
Example Usage
import { ResponsiveDialog } from '@dsbtek/component-library';
import { Button } from '@dsbtek/component-library';
function DialogExample() {
return (
<ResponsiveDialog
trigger={<Button>Open Dialog</Button>}
title="Edit Profile"
description="Make changes to your profile here."
>
<form className="space-y-4 pt-4">
<div className="space-y-2">
<label htmlFor="name">Name</label>
<input id="name" className="w-full p-2 border rounded" />
</div>
<div className="space-y-2">
<label htmlFor="email">Email</label>
<input id="email" type="email" className="w-full p-2 border rounded" />
</div>
<div className="flex justify-end space-x-2">
<Button type="button" variant="outline">Cancel</Button>
<Button type="submit">Save</Button>
</div>
</form>
</ResponsiveDialog>
);
}TagInput
A component for adding and managing tags with suggestions and validation.
Props
| Prop | Type | Default | Description
|-----|-----|-----|-----
| placeholder | string | "Add tag..." | Placeholder text
| tags | string[] | Required | Array of current tags
| setTags | (tags: string[]) => void | Required | Callback when tags change
| suggestions | string[] | [] | Array of tag suggestions
| maxTags | number | - | Maximum number of tags allowed
| disabled | boolean | false | Disable the input
| error | string | - | Error message to display
Example Usage
import { TagInput } from '@dsbtek/component-library';
import { useState } from 'react';
function TagInputExample() {
const [tags, setTags] = useState(['react', 'typescript']);
const suggestions = [
'react', 'vue', 'angular', 'svelte', 'javascript',
'typescript', 'node', 'express', 'mongodb'
];
return (
<TagInput
tags={tags}
setTags={setTags}
suggestions={suggestions}
placeholder="Add technologies..."
/>
);
}Styling
All components use Tailwind CSS for styling and can be customized using Tailwind classes. The components also respect the theme variables defined in your CSS.
Custom Styling Example
<Breadcrumbs
className="text-blue-600 dark:text-blue-400"
segments={[
{ label: 'Home', href: '/' },
{ label: 'Products', href: '/products' },
{ label: 'Current Page' },
]}
/>
<ColorPicker
className="w-[300px] rounded-lg"
color="#3B82F6"
onChange={setColor}
/>TypeScript Support
All components include full TypeScript support out of the box. Type definitions are automatically available when you import components.
Contributing
Please read our Contributing Guide before submitting a Pull Request.
License
MIT © Smartflowtech Team
