Skip to content

Commit

Permalink
feat: small ui revamp, add theming capability (#178)
Browse files Browse the repository at this point in the history
* feat: small ui revamp, add theming capability

* add changeset
  • Loading branch information
foyarash authored Mar 14, 2024
1 parent b7d2077 commit 150787b
Show file tree
Hide file tree
Showing 28 changed files with 314 additions and 198 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-ligers-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": minor
---

feat: ui revamp, add theming capability
1 change: 1 addition & 0 deletions apps/docs/pages/docs/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"getting-started": "Getting Started",
"api-docs": "API",
"i18n": "I18n",
"theming": "Theming",
"glossary": "Glossary",
"route": "Route name",
"edge-cases": "Edge cases"
Expand Down
32 changes: 17 additions & 15 deletions apps/docs/pages/docs/i18n.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ Next Admin supports i18n with the `translations` prop of the `NextAdmin` compone

The following keys are accepted:

| Name | Description | Default value |
| ------------------------------- | ------------------------------------------------------------------------------------- | ------------------------- |
| list.header.add.label | The "Add" button in the list header | Add |
| list.header.search.placeholder | The placeholder used in the search input | Search |
| list.footer.indicator.showing | The "Showing from" text in the list indicator, e.g: <u>Showing from</u> 1 to 10 of 25 | Showing from |
| list.footer.indicator.to | The "to" text in the list indicator, e.g: Showing from 1 <u>to</u> 10 of 25 | to |
| list.footer.indicator.of | The "of" text in the list indicator, e.g: Showing from 1 to 10 <u>of</u> 25 | of |
| list.row.actions.delete.label | The text in the delete button displayed at the end of each row | Delete |
| list.empty.label | The text displayed when there is no row in the list | No \{\{resource\}\} found |
| form.button.save.label | The text displayed in the form submit button | Submit |
| form.button.delete.label | The text displayed in the form delete button | Delete |
| form.widgets.file_upload.label | The text displayed in file upload widget to select a file | Choose a file |
| form.widgets.file_upload.delete | The text displayed in file upload widget to delete the current file | Delete |
| actions.label | The text displayed in the dropdown button for the actions list | Action |
| actions.delete.label | The text displayed for the default delete action in the actions dropdown | Delete |
| Name | Description | Default value |
| -------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------- |
| list.header.add.label | The "Add" button in the list header | Add |
| list.header.search.placeholder | The placeholder used in the search input | Search |
| list.footer.indicator.showing | The "Showing from" text in the list indicator, e.g: <u>Showing from</u> 1 to 10 of 25 | Showing from |
| list.footer.indicator.to | The "to" text in the list indicator, e.g: Showing from 1 <u>to</u> 10 of 25 | to |
| list.footer.indicator.of | The "of" text in the list indicator, e.g: Showing from 1 to 10 <u>of</u> 25 | of |
| list.row.actions.delete.label | The text in the delete button displayed at the end of each row | Delete |
| list.empty.label | The text displayed when there is no row in the list | No \{\{resource\}\} found |
| form.button.save.label | The text displayed in the form submit button | Submit |
| form.button.delete.label | The text displayed in the form delete button | Delete |
| form.widgets.file_upload.label | The text displayed in file upload widget to select a file | Choose a file |
| form.widgets.file_upload.drag_and_drop | The text displayed in file upload widget to indicate a drag & drop is possible | or drag and drop |
| form.widgets.file_upload.delete | The text displayed in file upload widget to delete the current file | Delete |
| actions.label | The text displayed in the dropdown button for the actions list | Action |
| actions.delete.label | The text displayed for the default delete action in the actions dropdown | Delete |

There is two ways to translate these default keys, provide a function named `getMessages` inside the options or provide `translations` props to `NextAdmin` component.

> Note that the function way allows you to provide an object with a multiple level structure to translate the keys, while the `translations` props only allows you to provide a flat object (`form.widgets.file_upload.delete` ex.)
You can also pass your own set of translations. For example you can set a custom action name as a translation key, which will then be translated by the lib.
Expand Down
42 changes: 42 additions & 0 deletions apps/docs/pages/docs/theming.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Theming

Next Admin comes with a default theme which currently consists of Tailwind's indigo color as a primary color.

To use the default theme, you can import the generated CSS file from Next Admin:

```js
import "@premieroctet/next-admin/dist/styles.css";
```

However, you might want to override this primary color. It is possible to do so in your own Tailwind config file:

```js
module.exports = {
content: [
...yourFiles,
"./node_modules/@premieroctet/next-admin/dist/**/*.js",
],
theme: {
extend: {
colors: {
nextadmin: {
primary: {
50: "#f3f4f6",
100: "#e7e9ed",
200: "#c7cdd6",
300: "#a7b1bf",
400: "#67788f",
500: "#27445f",
600: "#24405a",
700: "#1c334b",
800: "#172940",
900: "#0f1d2e",
},
},
},
},
},
};
```

You will then need to import your own generated CSS file instead of the Next Admin's one.
1 change: 0 additions & 1 deletion apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Dashboard from "@/components/Dashboard";
import { options } from "@/options";
import { prisma } from "@/prisma";
import schema from "@/prisma/json-schema/json-schema.json";
import "@premieroctet/next-admin/dist/styles.css";

export default async function AdminPage({
params,
Expand Down
4 changes: 2 additions & 2 deletions apps/example/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export const options: NextAdminOptions = {
posts: "col-span-2 row-start-4",
role: "col-span-2 row-start-4",
birthDate: "col-span-3 row-start-5",
avatar: "col-span-1 row-start-5",
metadata: "col-span-4 row-start-6",
avatar: "col-span-4 row-start-6",
metadata: "col-span-4 row-start-7",
},
fields: {
email: {
Expand Down
5 changes: 5 additions & 0 deletions apps/example/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const defaultColors = require("tailwindcss/colors");

/** @type {import('tailwindcss').Config} */
/* eslint-disable max-len */
module.exports = {
Expand All @@ -22,6 +24,9 @@ module.exports = {
},
extend: {
colors: {
nextadmin: {
primary: defaultColors.indigo,
},
// light mode
tremor: {
brand: {
Expand Down
54 changes: 33 additions & 21 deletions packages/next-admin/src/components/ActionsDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from "./radix/Dropdown";
import { useAction } from "../hooks/useAction";
import { useI18n } from "../context/I18nContext";
import { Fragment, useState } from "react";
import { Transition } from "@headlessui/react";

type Props = {
actions: ModelAction[];
Expand All @@ -26,13 +28,14 @@ const ActionsDropdown = ({
}: Props) => {
const { runAction } = useAction(resource, selectedIds);
const { t } = useI18n();
const [isOpen, setIsOpen] = useState(false);

const onActionClick = (action: ModelAction) => {
runAction(action);
};

return (
<Dropdown>
<Dropdown onOpenChange={setIsOpen}>
<DropdownTrigger
asChild
className="flex items-center gap-x-2 text-sm text-gray-700 rounded-md border border-input bg-transparent px-3 py-2"
Expand All @@ -44,26 +47,35 @@ const ActionsDropdown = ({
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
</button>
</DropdownTrigger>
<DropdownBody>
<DropdownContent
className="min-w-[10rem] p-2"
sideOffset={4}
data-testid="actions-dropdown-content"
>
{actions?.map((action) => {
return (
<DropdownItem
key={action.title}
className={clsx("rounded-md py-1 px-2", {
"text-red-600": action.style === "destructive",
})}
onClick={() => onActionClick(action)}
>
{t(action.title)}
</DropdownItem>
);
})}
</DropdownContent>
<DropdownBody forceMount>
<Transition.Root as={Fragment} show={isOpen}>
<Transition.Child
as={DropdownContent}
className="min-w-[10rem] p-2"
sideOffset={4}
data-testid="actions-dropdown-content"
enter="transition-all ease-out"
enterFrom="opacity-0 -translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition-all ease-in"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-1"
>
{actions?.map((action) => {
return (
<DropdownItem
key={action.title}
className={clsx("rounded-md py-1 px-2", {
"text-red-600": action.style === "destructive",
})}
onClick={() => onActionClick(action)}
>
{t(action.title)}
</DropdownItem>
);
})}
</Transition.Child>
</Transition.Root>
</DropdownBody>
</Dropdown>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/next-admin/src/components/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function Cell({ cell, formatter }: Props) {
<Link
onClick={(e) => e.stopPropagation()}
href={`${basePath}/${cell.value.url}`}
className="hover:underline cursor-pointer text-indigo-700 hover:text-indigo-900 font-semibold flex items-center gap-1"
className="hover:underline cursor-pointer text-nextadmin-primary-700 hover:text-nextadmin-primary-900 font-semibold flex items-center gap-1"
>
{cellValue}
<Clipboard value={cell.value.url} />
Expand Down Expand Up @@ -74,7 +74,7 @@ export default function Cell({ cell, formatter }: Props) {
className={clsx(
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium",
cell
? "bg-indigo-50 text-indigo-500"
? "bg-nextadmin-primary-50 text-nextadmin-primary-500"
: "bg-neutral-50 text-neutral-600"
)}
>
Expand Down
32 changes: 21 additions & 11 deletions packages/next-admin/src/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,24 @@ export function DataTable({
const { basePath } = useConfig();
const { t } = useI18n();

const columnsVisibility = columns.reduce((acc, column) => {
// @ts-expect-error
const key = column.accessorKey as Field<typeof resource>;
acc[key] = Object.keys(data[0]).includes(key);
return acc;
}, {} as Record<Field<typeof resource>, boolean>);
const columnsVisibility = columns.reduce(
(acc, column) => {
// @ts-expect-error
const key = column.accessorKey as Field<typeof resource>;
acc[key] = Object.keys(data[0]).includes(key);
return acc;
},
{} as Record<Field<typeof resource>, boolean>
);

const modelIdProperty = resourcesIdProperty[resource];
const checkboxColumn: ColumnDef<ListDataItem<ModelName>> = {
id: "__nextadmin-select-row",
header: ({ table }) => {
if (table.getRowModel().rows.length === 0) {
return null;
}

return (
<div className="px-1">
<Checkbox
Expand Down Expand Up @@ -95,8 +102,8 @@ export function DataTable({

return (
<Button
variant="destructive"
size="sm"
variant="destructiveOutline"
onClick={(evt) => {
evt.stopPropagation();
onDelete?.(row.original[idProperty].value as string | number);
Expand Down Expand Up @@ -124,9 +131,9 @@ export function DataTable({
});

return (
<div className="rounded-md border">
<div className="overflow-hidden shadow-md ring-1 ring-black ring-opacity-5 sm:rounded-lg">
<Table>
<TableHeader className="bg-indigo-100">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
Expand All @@ -150,7 +157,7 @@ export function DataTable({
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
className="cursor-pointer hover:bg-indigo-50"
className="cursor-pointer even:bg-gray-50"
onClick={() => {
window.location.href = `${basePath}/${resource.toLowerCase()}/${
row.original[modelIdProperty].value
Expand All @@ -166,7 +173,10 @@ export function DataTable({
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
<TableCell
colSpan={table.getAllColumns().length}
className="h-24 text-center"
>
<div className="text-center text-gray-500">
{t("list.empty.label", { resource })}
</div>
Expand Down
4 changes: 3 additions & 1 deletion packages/next-admin/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ const Form = ({

if (result?.validation) {
setValidation(result.validation);
} else {
setValidation(undefined);
}

if (result?.deleted) {
Expand Down Expand Up @@ -303,7 +305,7 @@ const Form = ({
{...props}
value={props.value ?? ""}
className={clsx(
"block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 px-2 disabled:opacity-50 disabled:bg-gray-200 disabled:cursor-not-allowed",
"block w-full transition-all duration-300 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-nextadmin-primary-600 sm:text-sm sm:leading-6 px-2 disabled:opacity-50 disabled:bg-gray-200 disabled:cursor-not-allowed",
{ "ring-red-600": rawErrors }
)}
/>
Expand Down
2 changes: 1 addition & 1 deletion packages/next-admin/src/components/ListHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function ListHeader({
onInput={onSearchChange}
defaultValue={search}
type="search"
className="px-3 py-1.5 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm focus-visible:outline focus-visible:outline-indigo-500 focus-visible:ring focus-visible:ring-indigo-500"
className="transition-all px-3 py-1.5 border border-gray-300 rounded-md shadow-sm focus:ring-nextadmin-primary-500 focus:border-nextadmin-primary-500 sm:text-sm focus-visible:outline focus-visible:outline-nextadmin-primary-500 focus-visible:ring-0 focus-visible:ring-nextadmin-primary-500"
placeholder={t("list.header.search.placeholder")}
/>
</div>
Expand Down
12 changes: 6 additions & 6 deletions packages/next-admin/src/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,17 @@ export default function Menu({
href={item.href}
className={clsx(
item.current
? "bg-gray-50 text-indigo-600"
: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50",
? "bg-gray-50 text-nextadmin-primary-600"
: "text-gray-700 hover:text-nextadmin-primary-600 hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
>
{item.icon && (
<item.icon
className={clsx(
item.current
? "text-indigo-600"
: "text-gray-400 group-hover:text-indigo-600",
? "text-nextadmin-primary-600"
: "text-gray-400 group-hover:text-nextadmin-primary-600",
"h-6 w-6 shrink-0"
)}
aria-hidden="true"
Expand Down Expand Up @@ -137,7 +137,7 @@ export default function Menu({
<div className="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-2">
<div className="flex h-16 shrink-0 items-center">
<Link href={basePath}>
<HomeIcon className="h-6 w-6 text-indigo-600" />
<HomeIcon className="h-6 w-6 text-nextadmin-primary--500" />
</Link>
</div>
<nav className="flex flex-1 flex-col">
Expand Down Expand Up @@ -171,7 +171,7 @@ export default function Menu({
<div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6">
<div className="flex h-16 shrink-0 items-center">
<Link href={basePath}>
<HomeIcon className="h-6 w-6 text-indigo-600" />
<HomeIcon className="h-6 w-6 text-nextadmin-primary-600" />
</Link>
</div>
<nav className="flex flex-1 flex-col">
Expand Down
2 changes: 1 addition & 1 deletion packages/next-admin/src/components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function Pagination({
}}
className={
pageNumber === currentPageIndex + 1
? "relative z-10 inline-flex items-center bg-indigo-600 px-4 py-2 text-sm font-semibold text-white focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
? "relative z-10 inline-flex items-center bg-nextadmin-primary-600 px-4 py-2 text-sm font-semibold text-white focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nextadmin-primary-600"
: "relative hidden items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 md:inline-flex"
}
>
Expand Down
Loading

0 comments on commit 150787b

Please sign in to comment.