diff --git a/apps/design-system/README.md b/apps/design-system/README.md index 12706027761..959e3631802 100644 --- a/apps/design-system/README.md +++ b/apps/design-system/README.md @@ -58,13 +58,13 @@ The design system _references_ components rather than housing them. That’s an With that out of the way, there are several parts of this design system that need to be manually updated after components have been added or removed (from documentation). These include: - `config/docs.ts`: list of components in the sidebar -- `content/docs`: where the actual component documentation `.mdx` file lives +- `content/docs`: the actual component documentation - `registry/examples.ts`: list of example components -- `registry/default/example`: where the actual example component(s) live -- `registry/charts.ts`: Chart components -- `registry/fragments.ts`: Fragment components +- `registry/default/example`: the actual example components +- `registry/charts.ts`: chart components +- `registry/fragments.ts`: fragment components -You will need to rebuild the design system’s registry each time a new example component is added. In other words: whenever a new file enters `registry`, it needs to be rebuilt. You can do that via: +You may need to rebuild the design system’s registry. You can do that via: ```bash cd apps/design-system diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index 005d8278801..cead6b766f8 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -2293,6 +2293,39 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "empty-state-missing-route": { + name: "empty-state-missing-route", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-missing-route")), + source: "", + files: ["registry/default/example/empty-state-missing-route.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "empty-state-zero-items-table": { + name: "empty-state-zero-items-table", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-zero-items-table")), + source: "", + files: ["registry/default/example/empty-state-zero-items-table.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "empty-state-initial-state": { + name: "empty-state-initial-state", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-initial-state")), + source: "", + files: ["registry/default/example/empty-state-initial-state.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "chart-area-axes": { name: "chart-area-axes", type: "components:block", diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index 35407e0b433..ec78bcd99ca 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -6,28 +6,6 @@ interface DocsConfig { } export const docsConfig: DocsConfig = { - // mainNav: [ - // { - // title: 'Documentation', - // href: '/docs', - // }, - // { - // title: 'Components', - // href: '/docs/components/accordion', - // }, - // { - // title: 'Themes', - // href: '/themes', - // }, - // { - // title: 'Examples', - // href: '/examples', - // }, - // { - // title: 'Blocks', - // href: '/blocks', - // }, - // ], sidebarNav: [ { title: 'Getting Started', @@ -77,6 +55,11 @@ export const docsConfig: DocsConfig = { href: '/docs/ui-patterns/navigation', items: [], }, + { + title: 'Empty States', + href: '/docs/ui-patterns/empty-states', + items: [], + }, ], }, { diff --git a/apps/design-system/content/docs/ui-patterns/empty-states.mdx b/apps/design-system/content/docs/ui-patterns/empty-states.mdx new file mode 100644 index 00000000000..876f5020a0e --- /dev/null +++ b/apps/design-system/content/docs/ui-patterns/empty-states.mdx @@ -0,0 +1,38 @@ +--- +title: Empty states +description: Convey the absence of data and provide clear instruction for what to do about it. +--- + +At a minimum, empty states convey the fact that there is nothing to list, perform, or display on the current page. They should also provide a clear call to action for the user to take. + +## Missing route + +Users may accidentally navigate to a non-existent dynamic route, such as a non-existent bucket in [Storage](https://supabase.com/dashboard/project/_/storage) or a non-existent table in the [Table Editor](https://supabase.com/dashboard/project/_/editor). In these cases, follow the pattern of a centered [Admonition](../fragments/admonition) as shown below.. + + + +## Zero results + +Tabular information without results—or perhaps no data to begin with—should have an empty state that matches the larger presentation. + +For instance, a [Table](../components/table) may just display a single row just like it would if it had data. Dulling the TableHead text color and removing the TableCell hover state can further reinforce the lack of usable data. + + + +The treatment for other layouts, such as the list of users in [Authentication](https://supabase.com/dashboard/project/_/auth/users), should match their own general styling. + +## Initial state + +Perhaps the user has not yet created any data yet. They might be a feature for the first time. In these cases, the empty state should provide the briefest information about the lack of data, putting more focus on the value proposition and primary action. + + + +Keep in mind that this empty state will likely appear after a visual loading state. Consider layout shift and button placement during and after the transition. + +## Components + +There is not yet a shared empty state UI component. The context and needs for each placement differ enough to warrant custom components for each placement. That said, we should aim to make these as consistent as possible over time. See the below examples that might share common logic in a future centralized component. + +## External references + +- [_Empty States_ on GitHub Primer](https://primer.style/product/ui-patterns/empty-states/) diff --git a/apps/design-system/next.config.mjs b/apps/design-system/next.config.mjs index 2280b564d29..2c3beb53dca 100644 --- a/apps/design-system/next.config.mjs +++ b/apps/design-system/next.config.mjs @@ -26,16 +26,27 @@ const nextConfig = { return [ ...(BASE_PATH.length ? [ - { - source: '/', - destination: BASE_PATH, - basePath: false, - permanent: false, - }, - ] + { + source: '/', + destination: BASE_PATH, + basePath: false, + permanent: false, + }, + ] : []), ] }, + // Turbopack configuration to handle .md files with raw-loader + // This mirrors the webpack configuration added by withContentlayer + // and ensures both bundlers can process content files properly + turbopack: { + rules: { + '*.md': { + loaders: ['raw-loader'], + as: '*.js', + }, + }, + }, } export default withContentlayer(nextConfig) diff --git a/apps/design-system/registry/default/example/empty-state-initial-state.tsx b/apps/design-system/registry/default/example/empty-state-initial-state.tsx new file mode 100644 index 00000000000..0c0db657166 --- /dev/null +++ b/apps/design-system/registry/default/example/empty-state-initial-state.tsx @@ -0,0 +1,22 @@ +import { Button } from 'ui' +import { Plus } from 'lucide-react' +import { BucketAdd } from 'icons' + +export default function EmptyStateInitialState() { + return ( + + ) +} diff --git a/apps/design-system/registry/default/example/empty-state-missing-route.tsx b/apps/design-system/registry/default/example/empty-state-missing-route.tsx new file mode 100644 index 00000000000..fca36ca0fd0 --- /dev/null +++ b/apps/design-system/registry/default/example/empty-state-missing-route.tsx @@ -0,0 +1,22 @@ +import { Admonition } from 'ui-patterns/admonition' +import Link from 'next/link' +import { Button } from 'ui' + +const bucketId = 'user_avatars' + +export default function EmptyStateMissingRoute() { + return ( +
+ + + +
+ ) +} diff --git a/apps/design-system/registry/default/example/empty-state-zero-items-table.tsx b/apps/design-system/registry/default/example/empty-state-zero-items-table.tsx new file mode 100644 index 00000000000..67949539b13 --- /dev/null +++ b/apps/design-system/registry/default/example/empty-state-zero-items-table.tsx @@ -0,0 +1,25 @@ +import { Card, Table, TableBody, TableHead, TableHeader, TableRow, TableCell } from 'ui' + +export default function EmptyStateZeroItemsTable() { + return ( + + + + + Table name + Date created + + + + + + +

No tables yet

+

Connect a table from your database

+
+
+
+
+
+ ) +} diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index b47de24fed5..622605d6896 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -1263,4 +1263,19 @@ export const examples: Registry = [ type: 'components:example', files: ['example/logs-bar-chart.tsx'], }, + { + name: 'empty-state-missing-route', + type: 'components:example', + files: ['example/empty-state-missing-route.tsx'], + }, + { + name: 'empty-state-zero-items-table', + type: 'components:example', + files: ['example/empty-state-zero-items-table.tsx'], + }, + { + name: 'empty-state-initial-state', + type: 'components:example', + files: ['example/empty-state-initial-state.tsx'], + }, ]