Skip to content

Commit

Permalink
feat: add icons for resources in sidebar and titles (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
foyarash authored Mar 20, 2024
1 parent df2efce commit 03bdc6d
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-books-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": minor
---

feat: add icons for resources in sidebar and titles
24 changes: 13 additions & 11 deletions apps/docs/pages/docs/api-docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,15 @@ It takes as **_key_** a model name of your schema as **_value_** an object to cu

By default if no models are defined, they will all be displayed in the admin. If you want more control, you have to define each model individually as empty objects or with the following properties:

| Name | Description | Default value |
| ---------- | -------------------------------------------------------------------------------- | ------------- |
| `toString` | a function that is used to display your record in related list | `id` field |
| `aliases` | an object containing the aliases of the model fields as keys, and the field name | undefined |
| `title` | a string used to display the model name in the sidebar and in the section title | Model name |
| `list` | an object containing the [list options](#list-property) | undefined |
| `edit` | an object containing the [edit options](#edit-property) | undefined |
| `actions` | an array of [actions](#actions-property) | undefined |
| Name | Description | Default value |
| ---------- | --------------------------------------------------------------------------------------------------- | ------------- |
| `toString` | a function that is used to display your record in related list | `id` field |
| `aliases` | an object containing the aliases of the model fields as keys, and the field name | undefined |
| `title` | a string used to display the model name in the sidebar and in the section title | Model name |
| `list` | an object containing the [list options](#list-property) | undefined |
| `edit` | an object containing the [edit options](#edit-property) | undefined |
| `actions` | an array of [actions](#actions-property) | undefined |
| `icon` | the [outline HeroIcon name](https://heroicons.com/outline) displayed in the sidebar and pages title | undefined |

You can customize the following for each model:

Expand Down Expand Up @@ -287,9 +288,10 @@ For the `edit` property, it can take the following:

`pages` is an object that allows you to add your own sub pages as a sidebar menu entry. It is an object where the key is the path (without the base path) and the value is an object with the following properties:

| Name | Description |
| ------- | ---------------------------------------------- |
| `title` | the title of the page displayed on the sidebar |
| Name | Description |
| ------- | ----------------------------------------------------------------------------------------------- |
| `title` | the title of the page displayed on the sidebar |
| `icon` | the [outline HeroIcon name](https://heroicons.com/outline) of the page displayed on the sidebar |

#### `actions` property

Expand Down
10 changes: 7 additions & 3 deletions apps/example/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export const options: NextAdminOptions = {
model: {
User: {
toString: (user) => `${user.name} (${user.email})`,
title: "👥 Users",
title: "Users",
icon: "UsersIcon",
aliases: {
id: "ID",
},
Expand Down Expand Up @@ -102,7 +103,8 @@ export const options: NextAdminOptions = {
},
Post: {
toString: (post) => `${post.title}`,
title: "📝 Posts",
title: "Posts",
icon: "NewspaperIcon",
list: {
display: ["id", "title", "published", "author", "categories", "rate"],
search: ["title", "content"],
Expand Down Expand Up @@ -136,7 +138,8 @@ export const options: NextAdminOptions = {
},
},
Category: {
title: "📚 Categories",
title: "Categories",
icon: "InboxStackIcon",
toString: (category) => `${category.name}`,
list: {
display: ["name", "posts"],
Expand All @@ -150,6 +153,7 @@ export const options: NextAdminOptions = {
pages: {
"/custom": {
title: "Custom page",
icon: "AdjustmentsHorizontalIcon",
},
},
sidebar: {
Expand Down
10 changes: 7 additions & 3 deletions apps/example/pageRouterOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export const options: NextAdminOptions = {
model: {
User: {
toString: (user) => `${user.name} (${user.email})`,
title: "Users 👥",
title: "Users",
icon: "UsersIcon",
list: {
display: ["id", "name", "email", "posts", "role", "birthDate"],
search: ["name", "email"],
Expand Down Expand Up @@ -90,7 +91,8 @@ export const options: NextAdminOptions = {
},
Post: {
toString: (post) => `${post.title}`,
title: "Posts 📝",
title: "Posts",
icon: "NewspaperIcon",
list: {
display: [
"id",
Expand Down Expand Up @@ -121,7 +123,8 @@ export const options: NextAdminOptions = {
},
},
Category: {
title: "Categories 📚",
title: "Categories",
icon: "InboxStackIcon",
toString: (category) => `${category.name}`,
list: {
display: ["name", "posts"],
Expand All @@ -135,6 +138,7 @@ export const options: NextAdminOptions = {
pages: {
"/custom": {
title: "Custom page",
icon: "AdjustmentsHorizontalIcon",
},
},
sidebar: {
Expand Down
8 changes: 6 additions & 2 deletions packages/next-admin/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useRouterInternal } from "../hooks/useRouterInternal";
import {
Field,
ModelAction,
ModelIcon,
ModelName,
NextAdminOptions,
SubmitFormResult,
Expand All @@ -36,6 +37,7 @@ import RichTextField from "./inputs/RichText/RichTextField";
import SelectWidget from "./inputs/SelectWidget";
import TextareaWidget from "./inputs/TextareaWidget";
import Button from "./radix/Button";
import ResourceIcon from "./common/ResourceIcon";

// Override Form functions to not prevent the submit
class CustomForm extends RjsfForm {
Expand All @@ -61,6 +63,7 @@ export type FormProps = {
options?: Required<NextAdminOptions>["model"][ModelName];
customInputs?: Record<Field<ModelName>, React.ReactElement | undefined>;
actions?: ModelAction[];
icon?: ModelIcon;
};

const fields: CustomForm["props"]["fields"] = {
Expand All @@ -87,6 +90,7 @@ const Form = ({
title,
customInputs,
actions,
icon,
}: FormProps) => {
const [validation, setValidation] = useState(validationProp);
const { edit, id, ...schemas } = getSchemas(data, schema, dmmfSchema);
Expand Down Expand Up @@ -334,8 +338,8 @@ const Form = ({
return (
<div className="relative">
<div className="sm:flex sm:items-center justify-between">
<h1 className="text-base font-semibold leading-6 text-gray-900">
{title}
<h1 className="text-base font-semibold leading-6 text-gray-900 flex items-center gap-2">
{!!icon && <ResourceIcon icon={icon} className="h-5 w-5" />} {title}
</h1>
{!!actions && actions.length > 0 && !!id && (
<ActionsDropdown
Expand Down
8 changes: 6 additions & 2 deletions packages/next-admin/src/components/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ListDataItem,
ListFieldsOptions,
ModelAction,
ModelIcon,
ModelName,
NextAdminOptions,
} from "../types";
Expand All @@ -28,6 +29,7 @@ import {
SelectTrigger,
SelectValue,
} from "./radix/Select";
import ResourceIcon from "./common/ResourceIcon";

export type ListProps = {
resource: ModelName;
Expand All @@ -38,6 +40,7 @@ export type ListProps = {
title: string;
actions?: ModelAction[];
deleteAction?: ModelAction["action"];
icon?: ModelIcon;
};

function List({
Expand All @@ -49,6 +52,7 @@ function List({
resourcesIdProperty,
title,
deleteAction,
icon,
}: ListProps) {
const { router, query } = useRouterInternal();
const [isPending, startTransition] = useTransition();
Expand Down Expand Up @@ -152,8 +156,8 @@ function List({
return (
<>
<div className="mt-4">
<h1 className="text-xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight mb-4">
{title}
<h1 className="text-xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight mb-4 flex items-center gap-2">
{!!icon && <ResourceIcon icon={icon} className="h-8 w-8" />} {title}
</h1>
</div>
<div className="mt-4 flow-root">
Expand Down
2 changes: 2 additions & 0 deletions packages/next-admin/src/components/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const MainLayout = ({
translations,
title = "Admin",
sidebar,
resourcesIcons,
}: PropsWithChildren<Props>) => {
const mergedTranslations = merge({ ...defaultTranslations }, translations);
const localePath = locale ? `/${locale}` : "";
Expand All @@ -37,6 +38,7 @@ export const MainLayout = ({
resourcesTitles={resourcesTitles}
customPages={customPages}
configuration={sidebar}
resourcesIcons={resourcesIcons}
/>

<main className="py-10 lg:pl-72">
Expand Down
21 changes: 16 additions & 5 deletions packages/next-admin/src/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@ import { clsx } from "clsx";
import Link from "next/link";
import { Fragment, useState } from "react";

import { AdminComponentProps, ModelName, SidebarConfiguration } from "../types";
import {
AdminComponentProps,
ModelIcon,
ModelName,
SidebarConfiguration,
} from "../types";
import { useConfig } from "../context/ConfigContext";
import { useRouterInternal } from "../hooks/useRouterInternal";
import ResourceIcon from "./common/ResourceIcon";

export type MenuProps = {
resource?: ModelName;
resources?: ModelName[];
resourcesTitles?: Record<ModelName, string | undefined>;
customPages?: AdminComponentProps["customPages"];
configuration?: SidebarConfiguration;
resourcesIcons: AdminComponentProps["resourcesIcons"];
};

export default function Menu({
Expand All @@ -23,6 +30,7 @@ export default function Menu({
resourcesTitles,
customPages,
configuration,
resourcesIcons,
}: MenuProps) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const { basePath } = useConfig();
Expand All @@ -36,6 +44,7 @@ export default function Menu({
* need to check if the pathname just ends with the page path
*/
current: pathname.endsWith(`${basePath}${page.path}`),
icon: page.icon,
}));

const ungroupedModels = resources?.filter(
Expand All @@ -47,7 +56,7 @@ export default function Menu({
name: string;
href: string;
current: boolean;
icon?: React.ElementType;
icon?: ModelIcon;
}) => {
return (
<a
Expand All @@ -59,12 +68,13 @@ export default function Menu({
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
>
{item.icon && (
<item.icon
{!!item.icon && (
<ResourceIcon
icon={item.icon}
className={clsx(
item.current
? "text-nextadmin-primary-600"
: "text-gray-400 group-hover:text-nextadmin-primary-600",
: "text-gray-700 group-hover:text-nextadmin-primary-600",
"h-6 w-6 shrink-0"
)}
aria-hidden="true"
Expand All @@ -80,6 +90,7 @@ export default function Menu({
name: resourcesTitles?.[model] || model,
href: `${basePath}/${model.toLowerCase()}`,
current: model === currentResource,
icon: resourcesIcons?.[model],
};
};

Expand Down
5 changes: 5 additions & 0 deletions packages/next-admin/src/components/NextAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function NextAdmin({
locale,
title,
sidebar,
resourcesIcons,
}: AdminComponentProps & CustomUIProps) {
if (!isAppDir && !options) {
throw new Error(
Expand All @@ -48,6 +49,7 @@ export function NextAdmin({
resource && schema ? getSchemaForResource(schema, resource) : undefined;

const resourceTitle = resourcesTitles?.[resource!] ?? resource;
const resourceIcon = resourcesIcons?.[resource!];

const renderMainComponent = () => {
if (Array.isArray(data) && resource && typeof total != "undefined") {
Expand All @@ -62,6 +64,7 @@ export function NextAdmin({
resourcesIdProperty={resourcesIdProperty!}
actions={actions}
deleteAction={deleteAction}
icon={resourceIcon}
/>
);
}
Expand All @@ -83,6 +86,7 @@ export function NextAdmin({
title={resourceTitle!}
customInputs={customInputs}
actions={actions}
icon={resourceIcon}
/>
);
}
Expand Down Expand Up @@ -114,6 +118,7 @@ export function NextAdmin({
locale={locale}
title={title}
sidebar={sidebar}
resourcesIcons={resourcesIcons}
>
{renderMainComponent()}
</MainLayout>
Expand Down
15 changes: 15 additions & 0 deletions packages/next-admin/src/components/common/ResourceIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";
import * as OutlineIcons from "@heroicons/react/24/outline";
import { ModelIcon } from "../../types";
import { SVGProps } from "react";

type Props = SVGProps<HTMLOrSVGElement> & {
icon: ModelIcon;
};

const ResourceIcon = ({ icon, ref, ...props }: Props) => {
const Icon = OutlineIcons[icon];
return <Icon {...props} />;
};

export default ResourceIcon;
Loading

0 comments on commit 03bdc6d

Please sign in to comment.