diff --git a/apps/www/src/app/examples/page.tsx b/apps/www/src/app/examples/page.tsx index dc326dc30..b65134762 100644 --- a/apps/www/src/app/examples/page.tsx +++ b/apps/www/src/app/examples/page.tsx @@ -1,4 +1,17 @@ 'use client'; +import { + ActivityLogIcon, + BarChartIcon, + DashboardIcon, + DotsHorizontalIcon, + FileTextIcon, + GearIcon, + HomeIcon, + MixerHorizontalIcon, + PersonIcon, + QuestionMarkCircledIcon, + BellIcon as RadixBellIcon +} from '@radix-ui/react-icons'; import { Amount, Avatar, @@ -30,12 +43,6 @@ import { TextArea, Tooltip } from '@raystack/apsara'; -import { - BellIcon, - FilterIcon, - OrganizationIcon, - SidebarIcon -} from '@raystack/apsara/icons'; import dayjs from 'dayjs'; import React, { useState } from 'react'; @@ -59,16 +66,16 @@ const Page = () => { // Sample options data with icons const selectOptions = [ - { value: 'dashboard', label: 'Dashboard', icon: }, - { value: 'analytics', label: 'Analytics', icon: }, - { value: 'settings', label: 'Settings', icon: }, - { value: 'profile', label: 'Profile', icon: } + { value: 'dashboard', label: 'Dashboard', icon: }, + { value: 'analytics', label: 'Analytics', icon: }, + { value: 'settings', label: 'Settings', icon: }, + { value: 'profile', label: 'Profile', icon: } ]; const filterOptions = [ - { value: 'Option 1', label: 'Option 1', icon: }, - { value: 'Option 2', label: 'Option 2', icon: }, - { value: 'Option 3', label: 'Option 3', icon: } + { value: 'Option 1', label: 'Option 1', icon: }, + { value: 'Option 2', label: 'Option 2', icon: }, + { value: 'Option 3', label: 'Option 3', icon: } ]; return ( @@ -79,7 +86,7 @@ const Page = () => { backgroundColor: 'var(--rs-color-background-base-primary)' }} > - + { onClick={() => console.log('Logo clicked')} aria-label='Logo' > - + Raystack @@ -96,31 +103,103 @@ const Page = () => { - }> + }> Dashboard - }> + }> Analytics - }> - Reports - - Activities + alert('Resources trailing icon clicked')} + aria-label='Resources group actions' + style={{ + border: 0, + background: 'transparent', + color: 'inherit', + padding: 0, + display: 'inline-flex', + alignItems: 'center', + cursor: 'pointer' + }} + > + + + } + > + }> + Reports + + + } + > + Activities + + console.log('Notifications clicked')} + leadingIcon={} + > + Notifications + + - - Settings - - Notifications + alert('Account trailing icon clicked')} + aria-label='Account group actions' + style={{ + border: 0, + background: 'transparent', + color: 'inherit', + padding: 0, + display: 'inline-flex', + alignItems: 'center', + cursor: 'pointer' + }} + > + + + } + > + }> + Settings + + }> + }> + Notifications + + } disabled> + Billing + + - Help & Support - - Preferences + }> + Help & Support + + + }> + Preferences + + }> + Documentation + + @@ -283,7 +362,7 @@ const Page = () => { color: 'var(--rs-color-foreground-base-secondary)' }} > - + 25% @@ -298,7 +377,7 @@ const Page = () => { color: 'var(--rs-color-foreground-base-secondary)' }} > - + 25% @@ -313,7 +392,7 @@ const Page = () => { color: 'var(--rs-color-foreground-base-secondary)' }} > - + 25% @@ -350,7 +429,9 @@ const Page = () => { color: 'var(--rs-color-foreground-base-secondary)' }} > - + Sun @@ -369,7 +450,9 @@ const Page = () => { color: 'var(--rs-color-foreground-base-secondary)' }} > - + 15th @@ -388,7 +471,9 @@ const Page = () => { color: 'var(--rs-color-foreground-base-secondary)' }} > - + Today @@ -1362,7 +1447,7 @@ const Page = () => { } + icon={} heading='KYC required for image orders' subHeading='Please contact your organization owner to complete the KYC process for the image orders. You can also contact support@raystack.io for assistance.' primaryAction={ @@ -1559,7 +1644,7 @@ const Page = () => { @@ -1580,7 +1665,7 @@ const Page = () => { } + leadingIcon={} width='100%' /> @@ -1717,7 +1802,7 @@ const Page = () => { @@ -1749,7 +1834,7 @@ const Page = () => { } + leadingIcon={} width='100%' /> @@ -2191,7 +2276,7 @@ const Page = () => { } + icon={} heading='Zero state' variant='empty2' subHeading='Get started by creating your first user. Filter bar and search are hidden in zero state.' @@ -2199,7 +2284,7 @@ const Page = () => { } emptyState={ } + icon={} heading='Empty state' variant='empty1' subHeading="We couldn't find any matches for that keyword or filter." @@ -2271,7 +2356,7 @@ const Page = () => { } + icon={} heading='zero state' variant='empty2' subHeading='Get started by creating your first user.' @@ -2279,7 +2364,7 @@ const Page = () => { } emptyState={ } + icon={} heading='empty state' variant='empty1' subHeading="We couldn't find any matches for that filter. Try adjusting your filters or search query. Filter bar remains visible so you can modify filters." @@ -2346,7 +2431,7 @@ const Page = () => { } + icon={} heading='zero state' variant='empty2' subHeading='Get started by creating your first user.' @@ -2354,7 +2439,7 @@ const Page = () => { } emptyState={ } + icon={} heading='empty state' variant='empty1' subHeading="We couldn't find any matches for that search. Try a different search term. Filter bar stays hidden when only search is applied." @@ -2407,7 +2492,7 @@ const Page = () => { align='center' style={{ padding: '40px' }} > - { } emptyState={ } + icon={} heading='empty state' variant='empty1' subHeading='Try adjusting your filters or search query.' @@ -2477,7 +2562,7 @@ const Page = () => { } + icon={} heading='zero state' variant='empty2' subHeading='Search is enabled even in zero state. Start typing to see empty state. Filter bar will only appear when filters are applied.' @@ -2485,7 +2570,7 @@ const Page = () => { } emptyState={ } + icon={} heading='empty state' variant='empty1' subHeading='Search applied but no results. Filter bar stays hidden when only search is used.' @@ -2551,7 +2636,7 @@ const Page = () => { } + icon={} heading='zero state' variant='empty2' subHeading='Get started by creating your first user.' @@ -2559,7 +2644,7 @@ const Page = () => { } emptyState={ } + icon={} heading='empty state' variant='empty1' subHeading="We couldn't find any matches for that keyword or filter." diff --git a/apps/www/src/content/docs/components/sidebar/demo.ts b/apps/www/src/content/docs/components/sidebar/demo.ts index cfb8dc34e..8473ab3b1 100644 --- a/apps/www/src/content/docs/components/sidebar/demo.ts +++ b/apps/www/src/content/docs/components/sidebar/demo.ts @@ -27,6 +27,9 @@ export const preview = { + }> + Overview + } active> Dashboard @@ -46,11 +49,6 @@ export const preview = { Activities - - }> - Help - - }> @@ -76,6 +74,7 @@ export const positionDemo = { + }>Overview } active>Dashboard }>Analytics @@ -100,6 +99,7 @@ export const positionDemo = { + }>Overview } active>Dashboard }>Analytics @@ -114,6 +114,75 @@ export const positionDemo = { ] }; +export const variantDemo = { + type: 'code', + tabs: [ + { + name: 'Plain', + code: sidebarLayout(` + + + + + + + Apsara + + + + }>Overview + + } active>Dashboard + }>Analytics + + + `) + }, + { + name: 'Floating', + code: sidebarLayout(` + + + + + + + Apsara + + + + }>Overview + + } active>Dashboard + }>Analytics + + + `) + }, + { + name: 'Inset', + code: sidebarLayout(` + + + + + + + Apsara + + + + }>Overview + + } active>Dashboard + }>Analytics + + + `) + } + ] +}; + export const stateDemo = { type: 'code', tabs: [ @@ -129,6 +198,7 @@ export const stateDemo = { + }>Overview } active>Dashboard }>Analytics @@ -152,6 +222,7 @@ export const stateDemo = { + }>Overview } active>Dashboard }>Analytics @@ -175,6 +246,7 @@ export const stateDemo = { + }>Overview } active>Dashboard }>Analytics @@ -198,6 +270,7 @@ export const stateDemo = { + }>Overview } active>Dashboard }>Analytics @@ -227,6 +300,7 @@ export const tooltipDemo = { + }>Overview } active>Dashboard }>Analytics @@ -251,6 +325,7 @@ export const collapsibleDemo = { + }>Overview } active>Dashboard }>Analytics @@ -271,10 +346,91 @@ export const hideTooltipDemo = { + }>Overview } active>Dashboard }>Settings + }> + Help + + + `) +}; + +export const collapsibleGroupDemo = { + type: 'code', + code: sidebarLayout(` + + + + + + Apsara + + + + }> + Overview + + + }> + Reports + + }> + Activities + + + }> + }> + Settings + + + + `) +}; + +export const moreDemo = { + type: 'code', + code: sidebarLayout(` + + + + + + Apsara + + + + } active> + Dashboard + + }> + Analytics + + + }> + Reports + + + }> + Activities + + } disabled> + Notifications + + + + + + }> + Preferences + + }> + Documentation + + + `) }; diff --git a/apps/www/src/content/docs/components/sidebar/index.mdx b/apps/www/src/content/docs/components/sidebar/index.mdx index 636e4802d..584698a9b 100644 --- a/apps/www/src/content/docs/components/sidebar/index.mdx +++ b/apps/www/src/content/docs/components/sidebar/index.mdx @@ -7,10 +7,13 @@ source: packages/raystack/components/sidebar import { preview, positionDemo, + variantDemo, + collapsibleGroupDemo, stateDemo, tooltipDemo, collapsibleDemo, - hideTooltipDemo + hideTooltipDemo, + moreDemo } from "./demo.ts"; @@ -27,6 +30,9 @@ import { Sidebar } from "@raystack/apsara"; Item + + Hidden item + @@ -55,7 +61,7 @@ The main section wraps navigation groups and items. It accepts all `div` props a ### Item -*Note: `leadingIcon` is optional and will show a fallback avatar only in collapsed state. You can pass `<>` to render truly nothing. Use the `as` prop to render as a custom element (e.g. a router `Link`).* +*Note: `leadingIcon` is optional and will show a fallback avatar only in collapsed state. You can pass `<>` to render truly nothing. Use the `render` prop to render as a custom element (e.g. a router `Link`).* @@ -63,6 +69,14 @@ The main section wraps navigation groups and items. It accepts all `div` props a The footer section is a container that accepts all `div` props. It's commonly used for secondary links (e.g. Help, Preferences) and stays at the bottom of the sidebar. +### More + +Renders a sidebar row that opens an Apsara menu with additional `Sidebar.Item` entries. It can be used inside `Sidebar.Group` or directly under `Sidebar.Main` / `Sidebar.Footer`. + +*Note: if `leadingIcon` is not provided, the trigger uses a default dots icon. In collapsed state, it follows the same item tooltip behavior and respects `hideCollapsedItemTooltip`.* + + + ## Examples ### Position @@ -71,6 +85,16 @@ The Sidebar can be positioned on either the left or right side of the screen. +### Variants + +Use `variant` to switch the Sidebar surface style: + +- `plain` (default): regular surface with side border +- `floating`: lifted surface with shadow +- `inset`: transparent surface without border or shadow + + + ### State The Sidebar supports expanded and collapsed states with smooth transitions. @@ -99,6 +123,18 @@ Set `hideCollapsedItemTooltip` to disable tooltips on navigation items when the +### Collapsible Group + +Enable `collapsible` on `Sidebar.Group` to make section items collapsible. You can also pass `trailingIcon` for section-level actions. + + + +### More + +Use `Sidebar.More` when you want to keep a section compact and move secondary items into a menu. + + + ## Accessibility The Sidebar implements the following accessibility features: diff --git a/apps/www/src/content/docs/components/sidebar/props.ts b/apps/www/src/content/docs/components/sidebar/props.ts index ccf03e15a..b9fedd4fc 100644 --- a/apps/www/src/content/docs/components/sidebar/props.ts +++ b/apps/www/src/content/docs/components/sidebar/props.ts @@ -22,6 +22,11 @@ export interface SidebarRootProps { */ position?: 'left' | 'right'; + /** Visual style variant of the Sidebar. + * @default "plain" + */ + variant?: 'plain' | 'floating' | 'inset'; + /** Hide tooltips on sidebar items when sidebar is collapsed. * @default false */ @@ -37,6 +42,11 @@ export interface SidebarGroupProps { /** String for the group title. */ label: string; + /** Makes group items collapsible. + * @default false + */ + collapsible?: boolean; + /** Optional ReactNode for group icon. */ leadingIcon?: ReactNode; @@ -71,7 +81,7 @@ export interface SidebarItemProps { * * @default "" */ - as?: ReactElement; + render?: ReactElement; /** Optional class names for customizing parts of the item. */ classNames?: { @@ -83,3 +93,26 @@ export interface SidebarItemProps { text?: string; }; } + +export interface SidebarMoreProps { + /** String for the more trigger label. */ + label?: string; + + /** Optional ReactNode for the trigger icon. */ + leadingIcon?: ReactNode; + + /** Sidebar items rendered inside the menu content. */ + children?: ReactNode; + + /** Optional class names for customizing parts of the more trigger/menu. */ + classNames?: { + /** Class name for the trigger root element. */ + root?: string; + /** Class name for the leading icon container. */ + leadingIcon?: string; + /** Class name for the text element. */ + text?: string; + /** Class name for menu content container. */ + menuContent?: string; + }; +} diff --git a/docs/V1-migration.md b/docs/V1-migration.md index 38ef61c69..ea9ff3000 100644 --- a/docs/V1-migration.md +++ b/docs/V1-migration.md @@ -1080,40 +1080,62 @@ Key changes: 1. **`disabled` prop replaced by `collapsible={false}`:** ```tsx -// Before — disabled prevents toggling but still shows trigger +// Before -// After — collapsible={false} hides the resize handle entirely +// After ``` -2. **`asChild` removed from Root** -- always renders `