Skip to content
/ refine-shadcn Public template

This package provides theme integration using shadcn-ui for refine.dev

License

Notifications You must be signed in to change notification settings

ferdiunal/refine-shadcn

Repository files navigation

๐ŸŽจ Refine ShadCN Theme

A modern, production-ready UI theme package for Refine applications built with shadcn/ui components, featuring Tailwind CSS v4, React 19, and comprehensive i18n support.

npm version License: MIT TypeScript

โœจ Key Highlights

  • ๐ŸŽจ Latest Tailwind CSS v4 with OKLCH color system
  • โšก React 19 & TypeScript ready
  • ๐ŸŒ Built-in i18n support (English & Turkish)
  • ๐ŸŽฏ Refine-optimized components
  • ๐Ÿงฉ Shadcn/ui design system
  • ๐Ÿ“ฑ Responsive & Accessible
  • ๐Ÿ”ง Fully customizable

๐Ÿš€ Live Demos

๐Ÿ“ฆ Installation

# npm
npm install @ferdiunal/refine-shadcn

# pnpm (recommended)
pnpm add @ferdiunal/refine-shadcn

# yarn
yarn add @ferdiunal/refine-shadcn

๐Ÿ”ง Quick Setup

1. Import Global Styles

// In your main application file (App.tsx, index.tsx, etc.)
import "@ferdiunal/refine-shadcn/dist/globals.css";

2. Basic Usage with Refine

import { Refine } from "@refinedev/core";
import { BrowserRouter } from "react-router-dom";
import { ThemeProvider } from "@ferdiunal/refine-shadcn";

function App() {
  return (
    <BrowserRouter>
      <ThemeProvider>
        <Refine
          // ... your Refine configuration
        >
          {/* Your application */}
        </Refine>
      </ThemeProvider>
    </BrowserRouter>
  );
}

๐Ÿงฉ Core Components

Action Buttons

All buttons include built-in authorization checks, loading states, and i18n support:

import { 
  CreateButton, 
  EditButton, 
  DeleteButton, 
  ShowButton, 
  ListButton 
} from "@ferdiunal/refine-shadcn";

function ActionBar() {
  return (
    <div className="flex gap-2">
      <CreateButton resource="posts" />
      <EditButton resource="posts" recordItemId="1" />
      <ShowButton resource="posts" recordItemId="1" />
      <DeleteButton 
        resource="posts" 
        recordItemId="1"
        confirmTitle="Delete Post?"
        confirmDescription="This will permanently delete the post."
      />
      <ListButton resource="posts" />
    </div>
  );
}

Framework Templates

FormField Component

Type-safe form fields with automatic validation and i18n support:

import { FormField } from "@ferdiunal/refine-shadcn";
import { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ferdiunal/refine-shadcn/ui";

function UserForm({ control }) {
  return (
    <div className="space-y-4">
      <FormField
        control={control}
        name="name"
        label="Full Name"
        description="Enter your full name"
      >
        {(field) => <Input placeholder="John Doe" {...field} />}
      </FormField>

      <FormField
        control={control}
        name="role"
        label="User Role"
      >
        {(field) => (
          <Select onValueChange={field.onChange} value={field.value}>
            <SelectTrigger>
              <SelectValue placeholder="Select role..." />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="admin">Administrator</SelectItem>
              <SelectItem value="user">User</SelectItem>
            </SelectContent>
          </Select>
        )}
      </FormField>
    </div>
  );
}

๐ŸŒ Internationalization (i18n)

Built-in support for multiple languages with automatic component translation:

import { Refine } from "@refinedev/core";
import { I18nProvider, locales } from "@ferdiunal/refine-shadcn";

// Quick i18n setup
const i18nProvider = {
  translate: (key: string, defaultMessage?: string) => i18n.t(key, defaultMessage),
  changeLocale: (lang: string) => i18n.changeLanguage(lang),
  getLocale: () => i18n.language,
};

function App() {
  return (
    <Refine i18nProvider={i18nProvider}>
      <I18nProvider>
        {/* All components now support i18n automatically */}
        <CreateButton /> {/* Automatically translates to "Create" or "OluลŸtur" */}
        <DeleteButton /> {/* Shows localized confirmation dialogs */}
      </I18nProvider>
    </Refine>
  );
}

Supported Languages: ๐Ÿ‡ฌ๐Ÿ‡ง English โ€ข ๐Ÿ‡น๐Ÿ‡ท Turkish

Auto-translated Components:

  • โœ… All action buttons (Create, Edit, Delete, etc.)
  • โœ… Form labels and validation messages
  • โœ… Confirmation dialogs
  • โœ… Table headers and pagination
  • โœ… Loading states and notifications

๐Ÿ“‹ Page Components

List Page

Feature-rich data tables with filtering, sorting, and bulk actions:

๐Ÿ“Š Table Implementation Example
import { ListPage, Table, TableFilterProps } from "@ferdiunal/refine-shadcn";
import { AvatarImage } from "@radix-ui/react-avatar";
import { BaseRecord, HttpError, useUserFriendlyName } from "@refinedev/core";
import type { UseTableReturnType } from "@refinedev/react-table";
import { Edit, Eye, Trash2 } from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Checkbox } from "@/components/ui/checkbox";

const UserList = () => {
    const friendly = useUserFriendlyName();
    const bulkDeleteAction = (
        table: UseTableReturnType<BaseRecord, HttpError>,
    ) => {
        const label = `Delete Selected (${
            table.getSelectedRowModel().rows.length
        }) ${friendly(
            "Row",
            table.getSelectedRowModel().rows.length > 1 ? "plural" : "singular",
        )}`;

        return {
            label,
            onClick: () => {
                alert("Delete Selected");
            },
        };
    };
    return (
        <ListPage>
            <Table enableSorting enableFilters>
                <Table.Column
                    accessorKey="id"
                    id={"select"}
                    header={({ table }) => (
                        <Table.CheckAll
                            options={[bulkDeleteAction(table)]}
                            table={table}
                        />
                    )}
                    cell={({ row }) => (
                        <Checkbox
                            className="translate-y-[2px]"
                            checked={row.getIsSelected()}
                            onCheckedChange={(value) =>
                                row.toggleSelected(!!value)
                            }
                            aria-label="Select row"
                            key={`checkbox-${row.original.id}`}
                        />
                    )}
                />
                <Table.Column
                    header={"ID"}
                    id="id"
                    accessorKey="id"
                    enableSorting
                    enableHiding
                />
                <Table.Column
                    header={"Avatar"}
                    id="avatar"
                    accessorKey="avatar"
                    cell={({ row }) =>
                        row.original.avatar?.[0]?.url && (
                            <Avatar>
                                <AvatarImage
                                    src={row.original.avatar[0].url}
                                    alt={row.original.avatar[0].name}
                                />
                                <AvatarFallback>
                                    {row.original.firstName[0]}
                                    {row.original.lastName[0]}
                                </AvatarFallback>
                            </Avatar>
                        )
                    }
                />
                <Table.Column
                    header={"First Name"}
                    accessorKey="firstName"
                    id="firstName"
                    enableSorting
                    enableHiding
                />
                <Table.Column
                    header={"Last Name"}
                    accessorKey="lastName"
                    id="lastName"
                    enableSorting
                    enableHiding
                />
                <Table.Column
                    header={"Birthday"}
                    accessorKey="birthday"
                    id="birthday"
                    enableSorting
                    enableHiding
                    filter={(props: TableFilterProps) => (
                        <Table.Filter.DateRangePicker {...props} align="end" />
                    )}
                />
                <Table.Column
                    accessorKey={"id"}
                    id={"actions"}
                    cell={({ row: { original } }) => (
                        <Table.Actions>
                            <Table.ShowAction
                                title="Detail"
                                row={original}
                                resource="users"
                                icon={<Eye size={16} />}
                            />
                            <Table.EditAction
                                title="Edit"
                                row={original}
                                resource="users"
                                icon={<Edit size={16} />}
                            />
                            <Table.DeleteAction
                                title="Delete"
                                row={original}
                                withForceDelete={true}
                                resource="users"
                                icon={<Trash2 size={16} />}
                            />
                        </Table.Actions>
                    )}
                />
            </Table>
        </ListPage>
    );
};

export default UserList;

Show Page

Clean, structured display for individual record details:

๐Ÿ‘๏ธ Show Page Example
import { ShowPage } from "@ferdiunal/refine-shadcn";
import { IResourceComponentsProps, useShow } from "@refinedev/core";
import { IUser } from "./Form";
const UserShow: React.FC<IResourceComponentsProps> = () => {
    const {
        query: { data },
    } = useShow<IUser>();
    const record = data?.data;

    return (
        <ShowPage>
            <ShowPage.Row title="ID" children={record?.id as number} />
            <ShowPage.Row
                title="First Name"
                children={record?.firstName?.toString() || ""}
            />
            <ShowPage.Row
                title="Last Name"
                children={record?.firstName?.toString() || ""}
            />
            <ShowPage.Row
                title="Email"
                children={record?.email?.toString() || ""}
            />
        </ShowPage>
    );
};

export default UserShow;

Create Page

User-friendly forms for creating new records with validation:

โž• Create Form Example
import { CreatePage } from "@ferdiunal/refine-shadcn";
import { Field, Form } from "@ferdiunal/refine-shadcn";
import { zodResolver } from "@hookform/resolvers/zod";
import { RedirectAction } from "@refinedev/core";
import { useForm } from "@refinedev/react-hook-form";
import * as z from "zod";
import { Input } from "@/components/ui/input";

export interface IUser {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
}

const formSchema = z.object({
    firstName: z.string().min(2, {
        message: "Firstname must be at least 2 characters.",
    }),
    lastName: z.string().min(2, {
        message: "Lastname must be at least 2 characters.",
    }),
    email: z.string().email({
        message: "Please enter a valid email address.",
    }),
});

const UserCreate = () => {
    const { ...form } = useForm<z.infer<typeof formSchema>>({
        mode: "all",
        resolver: zodResolver(formSchema),
        defaultValues: {
            firstName: "",
            lastName: "",
            email: "",
        },
        refineCoreProps: {
            autoSave: {
                enabled: true,
            },
            redirect,
        },
        warnWhenUnsavedChanges: true,
    });

    return (
        <CreatePage>
            <Form {...form}>
                <Field {...form} name="firstName" label="Firstname">
                    <Input placeholder="Firstname" />
                </Field>
                <Field {...form} name="lastName" label="Lastname">
                    <Input placeholder="Lastname" />
                </Field>
                <Field {...form} name="email" label="Email">
                    <Input placeholder="email" type="email" />
                </Field>
            </Form>
        </CreatePage>
    );
};

export default UserCreate;

Edit Page

Pre-populated forms for updating existing records:

โœ๏ธ Edit Form Example
import { EditPage } from "@ferdiunal/refine-shadcn";
import { Field, Form } from "@ferdiunal/refine-shadcn";
import { zodResolver } from "@hookform/resolvers/zod";
import { RedirectAction } from "@refinedev/core";
import { useForm } from "@refinedev/react-hook-form";
import * as z from "zod";
import { Input } from "@/components/ui/input";

export interface IUser {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
}

const formSchema = z.object({
    firstName: z.string().min(2, {
        message: "Firstname must be at least 2 characters.",
    }),
    lastName: z.string().min(2, {
        message: "Lastname must be at least 2 characters.",
    }),
    email: z.string().email({
        message: "Please enter a valid email address.",
    }),
});

const UserEdit = () => {
    const { ...form } = useForm<z.infer<typeof formSchema>>({
        mode: "all",
        resolver: zodResolver(formSchema),
        defaultValues: {
            firstName: "",
            lastName: "",
            email: "",
        },
        refineCoreProps: {
            autoSave: {
                enabled: true,
            },
            redirect,
        },
        warnWhenUnsavedChanges: true,
    });

    return (
        <EditPage>
            <Form {...form}>
                <Field {...form} name="firstName" label="Firstname">
                    <Input placeholder="Firstname" />
                </Field>
                <Field {...form} name="lastName" label="Lastname">
                    <Input placeholder="Lastname" />
                </Field>
                <Field {...form} name="email" label="Email">
                    <Input placeholder="email" type="email" />
                </Field>
            </Form>
        </EditPage>
    );
};

export default UserEdit;

๐ŸŽจ UI Components

Access the complete shadcn/ui component library:

import { 
  Button, 
  Input, 
  Card, 
  Dialog, 
  Select,
  Table,
  Form
} from "@ferdiunal/refine-shadcn/ui";

function MyComponent() {
  return (
    <Card className="p-6">
      <Button variant="primary" size="lg">
        Click me
      </Button>
      <Input placeholder="Type here..." />
    </Card>
  );
}

๐ŸŽฏ Advanced Features

Custom Theming

Override CSS variables to match your brand:

:root {
  --primary: oklch(0.7 0.15 250);
  --primary-foreground: oklch(0.98 0.02 250);
  --secondary: oklch(0.96 0.01 250);
  /* Customize all theme tokens */
}

TypeScript Integration

Full type safety across all components:

// Automatic type inference
type UserForm = {
  name: string;
  email: string;
  role: 'admin' | 'user';
};

// Type-safe form fields
<FormField<UserForm, "name">
  control={control}
  name="name" // โœ… Fully typed
  label="Full Name"
>
  {(field) => <Input {...field} />}
</FormField>

๐Ÿ“š Resources

๐Ÿค Contributing

We welcome contributions! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments


Made with โค๏ธ by @ferdiunal

โญ Star this repo if it helped you!

About

This package provides theme integration using shadcn-ui for refine.dev

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors 4

  •  
  •  
  •  
  •