diff --git a/.agents/skills/add-new-component/SKILL.md b/.agents/skills/add-new-component/SKILL.md index cfc7a4739..28810464b 100644 --- a/.agents/skills/add-new-component/SKILL.md +++ b/.agents/skills/add-new-component/SKILL.md @@ -22,14 +22,10 @@ packages/raystack/ ├── .module.css # Styles └── __tests__/.test.tsx # Tests -apps/www/src/ -├── content/docs/components// -│ ├── index.mdx # Docs page -│ ├── demo.ts # Code demos -│ └── props.ts # Prop interfaces -└── components/playground/ - ├── -examples.tsx # Playground example - └── index.ts # Register export +apps/www/src/content/docs/components// +├── index.mdx # Docs page +├── demo.ts # Code demos + playground +└── props.ts # Prop interfaces ``` ## Step 1: Create the Component Source @@ -453,39 +449,47 @@ export interface ComponentProps { - Keep descriptions concise - Include `className` prop on all sub-component interfaces -## Step 7: Add Playground Example +## Step 7: Add the Interactive Playground -Create `apps/www/src/components/playground/-examples.tsx`: +The playground is a permanent, user-facing feature on the component's docs page — not a dev-time scratch file. It opens a dialog with a live preview of the component, a controls panel, and a live code editor. A reader flips the controls, and both the preview and the code update from `getCode(props)`. Control state is written to the URL, so a configured example is a shareable link. It is rendered by `apps/www/src/components/demo/demo-playground.tsx`; you only supply the `playground` export. -```tsx +Any component with configurable props should have a real playground covering its main props — one control per prop that matters. Add the `playground` export to `demo.ts`: + +```ts 'use client'; -import { Component, Flex, Text } from '@raystack/apsara'; -import PlaygroundLayout from './playground-layout'; - -export function ComponentExamples() { - return ( - - - Default: - - Toggle - Content - - - - ); -} +import type { ComponentPropsType } from '@/components/demo/types'; +import { getPropsString } from '@/lib/utils'; + +export const getCode = (props: ComponentPropsType) => + ``; + +export const playground = { + type: 'playground', + controls: { + variant: { type: 'select', options: ['solid', 'outline'], defaultValue: 'solid' }, + size: { type: 'select', options: ['small', 'normal'], defaultValue: 'normal' }, + disabled: { type: 'checkbox', defaultValue: false }, + children: { type: 'text', initialValue: 'Click me' } + }, + getCode +}; ``` -Register in `apps/www/src/components/playground/index.ts` (alphabetical order): +Reference it from `index.mdx`: -```ts -export * from './code-block-examples'; -export * from './-examples'; // <-- new -export * from './combobox-examples'; +```mdx +import { playground } from "./demo.ts"; + + ``` +Notes: +- Control types: `select` (`options` + `defaultValue`), `checkbox` (`defaultValue`), `text` (`initialValue`), `icon`. +- One control per prop that changes the component's look or behavior. Cover the real API, not a token subset. +- `getCode` receives the changed props (values that differ from their default) and must return the JSX string for the current setup. Use `getPropsString` to serialize them; pull `children` out and place it between the tags. +- See `button/demo.ts` for a full, real example. + ## Step 8: Verify ```bash @@ -502,4 +506,4 @@ Checklist: - [ ] Every rendered element has a `data-slot`, with a `data-slots.test.tsx` covering them - [ ] CSS uses `--rs-*` tokens only - [ ] Export in `packages/raystack/index.tsx` in alphabetical order -- [ ] Playground example added and registered +- [ ] Interactive `playground` added to `demo.ts`, covering the component's main props diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c647f93e..e0126ef35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,7 @@ Thank you for your interest in contributing to Apsara! This guide will help you - [Release Workflow Details](#release-workflow-details) - [NPM Publishing](#npm-publishing) - [Canary Releases](#canary-releases) + - [Project Documentation](#project-documentation) - [Getting Help](#getting-help) - [Code of Conduct](#code-of-conduct) @@ -57,8 +58,8 @@ pnpm dev ``` 3. **Make your changes** in the appropriate directories: - - **Components**: `packages/raystack/` - - **Documentation**: `apps/www/` + - **Components**: `packages/raystack/components/` + - **Documentation**: `apps/www/src/content/docs/` 4. **Test your changes**: ```bash @@ -92,19 +93,19 @@ pnpm dev ## Component Development -1. Create components in `packages/raystack/` +1. Create components in `packages/raystack/components/` 2. Follow the existing component structure: ``` component-name/ - ├── index.ts # Export barrel file + ├── index.tsx # Export barrel file ├── component-name.tsx # Main component ├── component-name.module.css # Styles └── __tests__/ # Tests └── component-name.test.tsx ``` -3. Export new components from `packages/raystack/index.ts` -4. Update the component documentation in `apps/www/content/docs` +3. Export new components from `packages/raystack/index.tsx` +4. Update the component documentation in `apps/www/src/content/docs` ## Documentation Development @@ -293,6 +294,13 @@ Pushes to `main` are published the same way but don't have a PR to comment on. I pnpm add https://pkg.pr.new/raystack/apsara/@raystack/apsara@ ``` +## Project Documentation + +Beyond this guide, the repo keeps deeper docs under `docs/`: + +- [Migration Guide](./docs/V1-migration.md) — breaking changes and how to move from the Radix-based release to the current Base UI-based version. +- [RFCs](./docs/rfcs/) — design proposals and decisions behind major features (Base UI migration, unified DataView, guided Tour). + ## Getting Help If you encounter issues: diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f51f2f845..05bdef754 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -96,12 +96,12 @@ apsara/ ### Key Directories - **`packages/raystack/`**: Contains the main Apsara component library - - `accordion/`, `avatar/`, `badge/`, `button/`, etc.: React components (at root level) - - `v1/`: Legacy structure for backward compatibility - - `v1/components/`: Legacy component structure - - `v1/hooks/`: Custom React hooks - - `v1/icons/`: Icon components - - `style.css`: Main stylesheet + - `components/`: React components, one folder each (`accordion/`, `avatar/`, `button/`, etc.) + - `hooks/`: Custom React hooks + - `icons/`: Icon components + - `styles/`: Shared styles and theme tokens + - `types/`: Shared TypeScript types + - `test-utils/`: Test helpers - `dist/`: Built output - **`apps/www/`**: Documentation website built with Next.js and Fumadocs @@ -125,7 +125,7 @@ import { Button, Flex } from '@raystack/apsara' // Specific feature imports import { ChevronDownIcon } from '@raystack/apsara/icons' -import { useLocalStorage } from '@raystack/apsara/hooks' +import { useCopyToClipboard } from '@raystack/apsara/hooks' // Styles import '@raystack/apsara/style.css' @@ -253,11 +253,13 @@ pnpm build:apsara ``` This creates optimized builds in the `dist/` directory with: -- ESM modules (`dist/index.js`, `dist/v1/index.js`) -- CommonJS modules (`dist/index.cjs`, `dist/v1/index.cjs`) -- TypeScript declarations (`dist/index.d.ts`, `dist/v1/index.d.ts`) +- ESM modules (`dist/index.js`) +- CommonJS modules (`dist/index.cjs`) +- TypeScript declarations (`dist/index.d.ts`) - CSS files (`dist/style.css`, `dist/normalize.css`) +The package also exports a `./v1` entry point. It is a legacy alias that maps to the same root `dist` files, kept so older `@raystack/apsara/v1` imports keep working. + ### Build Configuration The build process uses: diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..b4b594f72 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative + Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, excluding + those notices that do not pertain to any part of the Derivative + Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and do + not modify the License. You may add Your own attribution notices + within Derivative Works that You distribute, alongside or as an + addendum to the NOTICE text from the Work, provided that such + additional attribution notices cannot be construed as modifying + the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Raystack + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/agents.md b/agents.md index dedd45ad3..7683265b6 100644 --- a/agents.md +++ b/agents.md @@ -21,7 +21,7 @@ packages/raystack/ # Main component library ├── icons/ # Icon components ├── styles/ # Global styles ├── types/ # Type definitions -└── test-utils.tsx # Testing utilities +└── test-utils/ # Testing utilities apps/www/ # Documentation site ├── src/content/docs/ # Component documentation diff --git a/apps/www/README.md b/apps/www/README.md index e215bc4cc..69677777a 100644 --- a/apps/www/README.md +++ b/apps/www/README.md @@ -1,36 +1,39 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Apsara Documentation Site + +The documentation site for [Apsara](https://apsara.raystack.org), built with [Next.js](https://nextjs.org) and [Fumadocs](https://fumadocs.dev). ## Getting Started -First, run the development server: +From the repo root, `pnpm start` runs both the component library and this docs site together. To run only the docs site, from this folder: -```bash -npm run dev -# or -yarn dev -# or +```sh pnpm dev -# or -bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +Open [http://localhost:3000](http://localhost:3000) to see it. -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +The site reads the local `@raystack/apsara` package, so a build of the library runs automatically before `pnpm build`. When you change a component, restart the library dev server (or `pnpm start` from the root) to pick it up. -## Learn More +## Content -To learn more about Next.js, take a look at the following resources: +Documentation lives in `src/content/docs/` as `.mdx` files. Each component has its own folder: -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +``` +src/content/docs/components// +├── index.mdx # the page: overview, anatomy, examples, accessibility +├── props.ts # prop tables, rendered by +└── demo.ts # live code examples shown by the component +``` -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +Navigation for sections like `theme` and `ai-elements` is set by their `meta.json`. The `components/` folder has no `meta.json`, so its pages are picked up automatically in alphabetical order. -## Deploy on Vercel +To add or edit a component page, see the [Documentation Development](../../CONTRIBUTING.md#documentation-development) section in the contributing guide. -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +## Scripts -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +```sh +pnpm dev # start the dev server +pnpm build # build the static site (builds the library first) +pnpm start # serve the production build +pnpm lint # check formatting and lint rules with Biome +``` diff --git a/apps/www/src/app/examples/README.md b/apps/www/src/app/examples/README.md new file mode 100644 index 000000000..b5b5a4e7d --- /dev/null +++ b/apps/www/src/app/examples/README.md @@ -0,0 +1,22 @@ +# Examples + +A manual-QA harness for trying Apsara components in a full-page context. These +routes are for testers and maintainers — they are **not** linked from the +public docs site. + +## When to use this + +Use `/examples` when you need to see a component in a real layout with real +data and interactions — something the small doc demos under +`src/content/docs/components/*/demo.ts` can't show. Common cases: a full app +shell (sidebar + navbar + table), server-driven data flows, or a new component +you are still building. + +## How to add an example + +1. Create a route folder next to this file: `app/examples//page.tsx`. +2. Build whatever you need to test. Import components from `@raystack/apsara`. +3. Open it at `http://localhost:3000/examples/`. + +Keep each example self-contained in its own folder. If two examples need the +same fixture data, put the shared data in a sibling file and import it from both. diff --git a/apps/www/src/app/examples/color-picker/page.tsx b/apps/www/src/app/examples/color-picker/page.tsx deleted file mode 100644 index 73a46e751..000000000 --- a/apps/www/src/app/examples/color-picker/page.tsx +++ /dev/null @@ -1,170 +0,0 @@ -'use client'; - -import { Button, ColorPicker, Flex, Popover, Text } from '@raystack/apsara'; -import { useState } from 'react'; - -const cardStyle = { - width: 280, - padding: 16, - borderRadius: 8, - background: 'var(--rs-color-background-base-primary)', - border: '1px solid var(--rs-color-border-base-primary)' -} as const; - -export default function ColorPickerExamplesPage() { - const [controlledValue, setControlledValue] = useState( - 'oklch(0.5438 0.191 267.01)' - ); - const [controlledMode, setControlledMode] = useState< - 'hex' | 'rgb' | 'hsl' | 'oklch' - >('oklch'); - const [popoverColor, setPopoverColor] = useState('#DA2929'); - - return ( - - - - ColorPicker - - - The picker edits internally in OKLCH for perceptual uniformity. The - area pad shows a chroma × lightness cross-section at the selected hue; - the mode prop selects the output format (hex / rgb / hsl - / oklch). - - - - - - - Default (hex) - - - - - - - - - - - - - - - OKLCH mode - - - defaultValue='oklch(0.5438 0.191 267.01)'{' '} - with defaultMode='oklch'. - - - - - - - - - - - - - - - Controlled — emits live value - - { - setControlledValue(value); - setControlledMode(mode as typeof controlledMode); - }} - onModeChange={mode => - setControlledMode(mode as typeof controlledMode) - } - > - - - - - - - - - - - value: - - - {controlledValue} - - - mode: {controlledMode} - - - - - - - Popover trigger - - - - } - /> - - - - - - - - - - - - - - {popoverColor} - - - - - ); -} diff --git a/apps/www/src/app/examples/combobox/page.tsx b/apps/www/src/app/examples/combobox/page.tsx deleted file mode 100644 index c3cee46d3..000000000 --- a/apps/www/src/app/examples/combobox/page.tsx +++ /dev/null @@ -1,162 +0,0 @@ -'use client'; - -import { - Button, - Combobox, - Dialog, - Flex, - Toast, - toastManager, - useToastManager -} from '@raystack/apsara'; - -const FRUITS = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']; - -function ToastButton({ - label, - source, - type -}: { - label: string; - source: string; - type?: 'success' | 'info' | 'warning'; -}) { - // Demonstrates the hook flavor — works because every button is a - // descendant of in the tree below. - const { add } = useToastManager(); - return ( - - ); -} - -const Page = () => { - return ( - - -

Combobox + nested dialogs + toast

-

- Toasts triggered from any depth of nested dialog still render at the - root viewport. Each level has its own combobox and toast button. -

- - - - - {FRUITS.map(f => ( - - {f} - - ))} - - - - - {/* Singleton flavor — usable from anywhere, including non-React code. */} - - - - }> - Open dialog 1 - - - - Dialog 1 - - Triggers a toast and opens a nested dialog. - - - - - - - - {FRUITS.map(f => ( - - {f} - - ))} - - - - - - - }> - Open dialog 2 - - - - Dialog 2 (nested) - - A toast fired from here still appears at the root - viewport — even though this dialog is portaled. - - - - - - - - - }> - Close - - - - - - - - - }> - Close - - - - - -
-
- ); -}; - -export default Page; diff --git a/apps/www/src/app/examples/datatable-virtual/page.tsx b/apps/www/src/app/examples/datatable-virtual/page.tsx deleted file mode 100644 index 737e98b67..000000000 --- a/apps/www/src/app/examples/datatable-virtual/page.tsx +++ /dev/null @@ -1,604 +0,0 @@ -'use client'; - -import { - Button, - DataTable, - DataTableColumnDef, - EmptyState, - Flex, - IconButton, - Navbar, - Search, - Sidebar, - Text -} from '@raystack/apsara'; -import { BellIcon, FilterIcon, SidebarIcon } from '@raystack/apsara/icons'; -import { useCallback, useMemo, useState } from 'react'; - -const sampleData = [ - { - id: '1', - name: 'Alice', - email: 'alice@example.com', - role: 'Admin', - department: 'Engineering', - team: 'Frontend', - location: 'NYC', - phone: '+1-555-0101', - status: 'Active', - joined: '2022-01-15' - }, - { - id: '2', - name: 'Bob', - email: 'bob@example.com', - role: 'User', - department: 'Product', - team: 'Design', - location: 'SF', - phone: '+1-555-0102', - status: 'Active', - joined: '2022-03-20' - }, - { - id: '3', - name: 'Carol', - email: 'carol@example.com', - role: 'Manager', - department: 'Engineering', - team: 'Backend', - location: 'NYC', - phone: '+1-555-0103', - status: 'Active', - joined: '2021-11-08' - }, - { - id: '4', - name: 'Dave', - email: 'dave@example.com', - role: 'User', - department: 'Sales', - team: 'East', - location: 'Boston', - phone: '+1-555-0104', - status: 'Away', - joined: '2023-02-14' - }, - { - id: '5', - name: 'Eve', - email: 'eve@example.com', - role: 'Admin', - department: 'Engineering', - team: 'Frontend', - location: 'Remote', - phone: '+1-555-0105', - status: 'Active', - joined: '2020-06-01' - }, - { - id: '6', - name: 'Frank', - email: 'frank@example.com', - role: 'User', - department: 'Support', - team: 'Tier 1', - location: 'Austin', - phone: '+1-555-0106', - status: 'Active', - joined: '2023-05-10' - }, - { - id: '7', - name: 'Grace', - email: 'grace@example.com', - role: 'Manager', - department: 'Product', - team: 'Design', - location: 'SF', - phone: '+1-555-0107', - status: 'Active', - joined: '2021-09-22' - }, - { - id: '8', - name: 'Henry', - email: 'henry@example.com', - role: 'Admin', - department: 'Engineering', - team: 'Backend', - location: 'Seattle', - phone: '+1-555-0108', - status: 'Away', - joined: '2019-12-05' - }, - { - id: '9', - name: 'Ivy', - email: 'ivy@example.com', - role: 'User', - department: 'Marketing', - team: 'Content', - location: 'NYC', - phone: '+1-555-0109', - status: 'Active', - joined: '2022-08-30' - }, - { - id: '10', - name: 'Jack', - email: 'jack@example.com', - role: 'User', - department: 'Engineering', - team: 'Frontend', - location: 'Remote', - phone: '+1-555-0110', - status: 'Active', - joined: '2023-01-12' - }, - { - id: '11', - name: 'Kate', - email: 'kate@example.com', - role: 'Manager', - department: 'Sales', - team: 'West', - location: 'LA', - phone: '+1-555-0111', - status: 'Active', - joined: '2020-04-18' - }, - { - id: '12', - name: 'Leo', - email: 'leo@example.com', - role: 'Admin', - department: 'Engineering', - team: 'DevOps', - location: 'NYC', - phone: '+1-555-0112', - status: 'Active', - joined: '2021-07-07' - }, - { - id: '13', - name: 'Mia', - email: 'mia@example.com', - role: 'User', - department: 'Product', - team: 'Design', - location: 'Chicago', - phone: '+1-555-0113', - status: 'Away', - joined: '2022-11-25' - }, - { - id: '14', - name: 'Noah', - email: 'noah@example.com', - role: 'User', - department: 'Support', - team: 'Tier 2', - location: 'Austin', - phone: '+1-555-0114', - status: 'Active', - joined: '2023-03-03' - }, - { - id: '15', - name: 'Olivia', - email: 'olivia@example.com', - role: 'Manager', - department: 'Engineering', - team: 'Frontend', - location: 'SF', - phone: '+1-555-0115', - status: 'Active', - joined: '2020-10-11' - }, - { - id: '16', - name: 'Paul', - email: 'paul@example.com', - role: 'Admin', - department: 'Sales', - team: 'East', - location: 'Boston', - phone: '+1-555-0116', - status: 'Active', - joined: '2019-08-19' - }, - { - id: '17', - name: 'Quinn', - email: 'quinn@example.com', - role: 'User', - department: 'Marketing', - team: 'Growth', - location: 'Remote', - phone: '+1-555-0117', - status: 'Active', - joined: '2022-05-06' - }, - { - id: '18', - name: 'Ryan', - email: 'ryan@example.com', - role: 'User', - department: 'Engineering', - team: 'Backend', - location: 'Seattle', - phone: '+1-555-0118', - status: 'Away', - joined: '2021-02-28' - }, - { - id: '19', - name: 'Sara', - email: 'sara@example.com', - role: 'Manager', - department: 'Support', - team: 'Tier 1', - location: 'Austin', - phone: '+1-555-0119', - status: 'Active', - joined: '2020-01-14' - }, - { - id: '20', - name: 'Tom', - email: 'tom@example.com', - role: 'Admin', - department: 'Product', - team: 'Design', - location: 'NYC', - phone: '+1-555-0120', - status: 'Active', - joined: '2018-12-01' - }, - { - id: '21', - name: 'Uma', - email: 'uma@example.com', - role: 'User', - department: 'Engineering', - team: 'Frontend', - location: 'Remote', - phone: '+1-555-0121', - status: 'Active', - joined: '2023-04-17' - }, - { - id: '22', - name: 'Victor', - email: 'victor@example.com', - role: 'User', - department: 'Sales', - team: 'West', - location: 'LA', - phone: '+1-555-0122', - status: 'Active', - joined: '2022-09-09' - }, - { - id: '23', - name: 'Wendy', - email: 'wendy@example.com', - role: 'Manager', - department: 'Engineering', - team: 'Backend', - location: 'SF', - phone: '+1-555-0123', - status: 'Away', - joined: '2021-06-21' - }, - { - id: '24', - name: 'Xavier', - email: 'xavier@example.com', - role: 'Admin', - department: 'Marketing', - team: 'Content', - location: 'Chicago', - phone: '+1-555-0124', - status: 'Active', - joined: '2019-03-12' - }, - { - id: '25', - name: 'Yara', - email: 'yara@example.com', - role: 'User', - department: 'Product', - team: 'Design', - location: 'Remote', - phone: '+1-555-0125', - status: 'Active', - joined: '2022-07-04' - }, - { - id: '26', - name: 'Zane', - email: 'zane@example.com', - role: 'User', - department: 'Support', - team: 'Tier 2', - location: 'Austin', - phone: '+1-555-0126', - status: 'Active', - joined: '2023-02-22' - }, - { - id: '27', - name: 'Amy', - email: 'amy@example.com', - role: 'Manager', - department: 'Engineering', - team: 'DevOps', - location: 'NYC', - phone: '+1-555-0127', - status: 'Active', - joined: '2020-11-30' - }, - { - id: '28', - name: 'Ben', - email: 'ben@example.com', - role: 'Admin', - department: 'Sales', - team: 'East', - location: 'Boston', - phone: '+1-555-0128', - status: 'Away', - joined: '2021-04-05' - }, - { - id: '29', - name: 'Chloe', - email: 'chloe@example.com', - role: 'User', - department: 'Marketing', - team: 'Growth', - location: 'SF', - phone: '+1-555-0129', - status: 'Active', - joined: '2022-12-19' - }, - { - id: '30', - name: 'Dan', - email: 'dan@example.com', - role: 'User', - department: 'Engineering', - team: 'Frontend', - location: 'Seattle', - phone: '+1-555-0130', - status: 'Active', - joined: '2023-06-08' - } -]; - -const PAGE_SIZE = 25; -const TOTAL_ROWS = 200; - -const fullDataset = Array.from({ length: TOTAL_ROWS }, (_, i) => { - const base = sampleData[i % sampleData.length]; - return { - ...base, - id: String(i + 1), - name: `${base.name} ${Math.floor(i / sampleData.length) + 1}`, - email: `${base.name.toLowerCase()}${i + 1}@example.com`, - phone: `+1-555-${String(1000 + i).padStart(4, '0')}` - }; -}); - -const columns: DataTableColumnDef<(typeof fullDataset)[number], unknown>[] = [ - { - accessorKey: 'name', - header: 'Name', - enableSorting: true, - enableColumnFilter: true, - filterType: 'string' as const, - enableGrouping: true, - showGroupCount: true, - enableHiding: true - }, - { - accessorKey: 'email', - header: 'Email', - enableSorting: true, - enableColumnFilter: true, - filterType: 'string' as const, - enableHiding: true - }, - { - accessorKey: 'role', - header: 'Role', - enableSorting: true, - enableColumnFilter: true, - filterType: 'select' as const, - enableGrouping: true, - showGroupCount: true, - enableHiding: true, - filterOptions: [ - { value: 'Admin', label: 'Admin' }, - { value: 'User', label: 'User' }, - { value: 'Manager', label: 'Manager' } - ] - }, - { - accessorKey: 'department', - header: 'Department', - enableSorting: true, - enableGrouping: true, - showGroupCount: true, - enableHiding: true - }, - { - accessorKey: 'team', - header: 'Team', - enableSorting: true, - enableGrouping: true, - showGroupCount: true, - enableHiding: true - }, - { - accessorKey: 'location', - header: 'Location', - enableSorting: true, - enableGrouping: true, - showGroupCount: true, - enableHiding: true - }, - { accessorKey: 'phone', header: 'Phone', enableHiding: true }, - { - accessorKey: 'status', - header: 'Status', - enableSorting: true, - enableGrouping: true, - showGroupCount: true, - enableColumnFilter: true, - filterType: 'select' as const, - enableHiding: true, - filterOptions: [ - { value: 'Active', label: 'Active' }, - { value: 'Away', label: 'Away' } - ] - }, - { - accessorKey: 'joined', - header: 'Joined', - enableSorting: true, - enableHiding: true - } -]; - -const Page = () => { - const [navbarSearch, setNavbarSearch] = useState(''); - const [data, setData] = useState(() => fullDataset.slice(0, PAGE_SIZE)); - const [isLoading, setIsLoading] = useState(false); - - const handleLoadMore = useCallback(async () => { - if (data.length >= TOTAL_ROWS || isLoading) return; - setIsLoading(true); - await new Promise(resolve => setTimeout(resolve, 1500)); - setData(prev => fullDataset.slice(0, prev.length + PAGE_SIZE)); - setIsLoading(false); - }, [data.length, isLoading]); - - const tableColumns = useMemo(() => columns, []); - - return ( - - - - - {}} aria-label='Logo'> - - - - Raystack - - - - - }> - Examples - - } - > - DataTable – Virtualized - - } - > - DataTable – Content - - - - Help & Support - Preferences - - - - - - - - DataTable – Virtualized – {TOTAL_ROWS} rows w/ infinite scroll, - grouping & sorting - - - - ) => - setNavbarSearch(e.target.value) - } - onClear={() => setNavbarSearch('')} - size='small' - style={{ width: '200px' }} - /> - - - - - - - - - - } - heading='No results' - variant='empty1' - subHeading='Try adjusting your filters or search.' - /> - } - /> - - - - - - ); -}; - -export default Page; diff --git a/apps/www/src/app/examples/datatable/page.tsx b/apps/www/src/app/examples/datatable/page.tsx deleted file mode 100644 index c8a2d9b6e..000000000 --- a/apps/www/src/app/examples/datatable/page.tsx +++ /dev/null @@ -1,611 +0,0 @@ -'use client'; - -import { - Button, - DataTable, - DataTableColumnDef, - EmptyState, - Flex, - IconButton, - Navbar, - Search, - Sidebar, - Text -} from '@raystack/apsara'; -import { BellIcon, FilterIcon, SidebarIcon } from '@raystack/apsara/icons'; -import { useState } from 'react'; - -const sampleData = [ - { - id: '1', - name: 'Alice', - email: 'alice@example.com', - role: 'Admin', - department: 'Engineering', - team: 'Frontend', - location: 'NYC', - phone: '+1-555-0101', - status: 'Active', - joined: '2022-01-15' - }, - { - id: '2', - name: 'Bob', - email: 'bob@example.com', - role: 'User', - department: 'Product', - team: 'Design', - location: 'SF', - phone: '+1-555-0102', - status: 'Active', - joined: '2022-03-20' - }, - { - id: '3', - name: 'Carol', - email: 'carol@example.com', - role: 'Manager', - department: 'Engineering', - team: 'Backend', - location: 'NYC', - phone: '+1-555-0103', - status: 'Active', - joined: '2021-11-08' - }, - { - id: '4', - name: 'Dave', - email: 'dave@example.com', - role: 'User', - department: 'Sales', - team: 'East', - location: 'Boston', - phone: '+1-555-0104', - status: 'Away', - joined: '2023-02-14' - }, - { - id: '5', - name: 'Eve', - email: 'eve@example.com', - role: 'Admin', - department: 'Engineering', - team: 'Frontend', - location: 'Remote', - phone: '+1-555-0105', - status: 'Active', - joined: '2020-06-01' - }, - { - id: '6', - name: 'Frank', - email: 'frank@example.com', - role: 'User', - department: 'Support', - team: 'Tier 1', - location: 'Austin', - phone: '+1-555-0106', - status: 'Active', - joined: '2023-05-10' - }, - { - id: '7', - name: 'Grace', - email: 'grace@example.com', - role: 'Manager', - department: 'Product', - team: 'Design', - location: 'SF', - phone: '+1-555-0107', - status: 'Active', - joined: '2021-09-22' - }, - { - id: '8', - name: 'Henry', - email: 'henry@example.com', - role: 'Admin', - department: 'Engineering', - team: 'Backend', - location: 'Seattle', - phone: '+1-555-0108', - status: 'Away', - joined: '2019-12-05' - }, - { - id: '9', - name: 'Ivy', - email: 'ivy@example.com', - role: 'User', - department: 'Marketing', - team: 'Content', - location: 'NYC', - phone: '+1-555-0109', - status: 'Active', - joined: '2022-08-30' - }, - { - id: '10', - name: 'Jack', - email: 'jack@example.com', - role: 'User', - department: 'Engineering', - team: 'Frontend', - location: 'Remote', - phone: '+1-555-0110', - status: 'Active', - joined: '2023-01-12' - }, - { - id: '11', - name: 'Kate', - email: 'kate@example.com', - role: 'Manager', - department: 'Sales', - team: 'West', - location: 'LA', - phone: '+1-555-0111', - status: 'Active', - joined: '2020-04-18' - }, - { - id: '12', - name: 'Leo', - email: 'leo@example.com', - role: 'Admin', - department: 'Engineering', - team: 'DevOps', - location: 'NYC', - phone: '+1-555-0112', - status: 'Active', - joined: '2021-07-07' - }, - { - id: '13', - name: 'Mia', - email: 'mia@example.com', - role: 'User', - department: 'Product', - team: 'Design', - location: 'Chicago', - phone: '+1-555-0113', - status: 'Away', - joined: '2022-11-25' - }, - { - id: '14', - name: 'Noah', - email: 'noah@example.com', - role: 'User', - department: 'Support', - team: 'Tier 2', - location: 'Austin', - phone: '+1-555-0114', - status: 'Active', - joined: '2023-03-03' - }, - { - id: '15', - name: 'Olivia', - email: 'olivia@example.com', - role: 'Manager', - department: 'Engineering', - team: 'Frontend', - location: 'SF', - phone: '+1-555-0115', - status: 'Active', - joined: '2020-10-11' - }, - { - id: '16', - name: 'Paul', - email: 'paul@example.com', - role: 'Admin', - department: 'Sales', - team: 'East', - location: 'Boston', - phone: '+1-555-0116', - status: 'Active', - joined: '2019-08-19' - }, - { - id: '17', - name: 'Quinn', - email: 'quinn@example.com', - role: 'User', - department: 'Marketing', - team: 'Growth', - location: 'Remote', - phone: '+1-555-0117', - status: 'Active', - joined: '2022-05-06' - }, - { - id: '18', - name: 'Ryan', - email: 'ryan@example.com', - role: 'User', - department: 'Engineering', - team: 'Backend', - location: 'Seattle', - phone: '+1-555-0118', - status: 'Away', - joined: '2021-02-28' - }, - { - id: '19', - name: 'Sara', - email: 'sara@example.com', - role: 'Manager', - department: 'Support', - team: 'Tier 1', - location: 'Austin', - phone: '+1-555-0119', - status: 'Active', - joined: '2020-01-14' - }, - { - id: '20', - name: 'Tom', - email: 'tom@example.com', - role: 'Admin', - department: 'Product', - team: 'Design', - location: 'NYC', - phone: '+1-555-0120', - status: 'Active', - joined: '2018-12-01' - }, - { - id: '21', - name: 'Uma', - email: 'uma@example.com', - role: 'User', - department: 'Engineering', - team: 'Frontend', - location: 'Remote', - phone: '+1-555-0121', - status: 'Active', - joined: '2023-04-17' - }, - { - id: '22', - name: 'Victor', - email: 'victor@example.com', - role: 'User', - department: 'Sales', - team: 'West', - location: 'LA', - phone: '+1-555-0122', - status: 'Active', - joined: '2022-09-09' - }, - { - id: '23', - name: 'Wendy', - email: 'wendy@example.com', - role: 'Manager', - department: 'Engineering', - team: 'Backend', - location: 'SF', - phone: '+1-555-0123', - status: 'Away', - joined: '2021-06-21' - }, - { - id: '24', - name: 'Xavier', - email: 'xavier@example.com', - role: 'Admin', - department: 'Marketing', - team: 'Content', - location: 'Chicago', - phone: '+1-555-0124', - status: 'Active', - joined: '2019-03-12' - }, - { - id: '25', - name: 'Yara', - email: 'yara@example.com', - role: 'User', - department: 'Product', - team: 'Design', - location: 'Remote', - phone: '+1-555-0125', - status: 'Active', - joined: '2022-07-04' - }, - { - id: '26', - name: 'Zane', - email: 'zane@example.com', - role: 'User', - department: 'Support', - team: 'Tier 2', - location: 'Austin', - phone: '+1-555-0126', - status: 'Active', - joined: '2023-02-22' - }, - { - id: '27', - name: 'Amy', - email: 'amy@example.com', - role: 'Manager', - department: 'Engineering', - team: 'DevOps', - location: 'NYC', - phone: '+1-555-0127', - status: 'Active', - joined: '2020-11-30' - }, - { - id: '28', - name: 'Ben', - email: 'ben@example.com', - role: 'Admin', - department: 'Sales', - team: 'East', - location: 'Boston', - phone: '+1-555-0128', - status: 'Away', - joined: '2021-04-05' - }, - { - id: '29', - name: 'Chloe', - email: 'chloe@example.com', - role: 'User', - department: 'Marketing', - team: 'Growth', - location: 'SF', - phone: '+1-555-0129', - status: 'Active', - joined: '2022-12-19' - }, - { - id: '30', - name: 'Dan', - email: 'dan@example.com', - role: 'User', - department: 'Engineering', - team: 'Frontend', - location: 'Seattle', - phone: '+1-555-0130', - status: 'Active', - joined: '2023-06-08' - } -]; - -const columns: DataTableColumnDef<(typeof sampleData)[number], unknown>[] = [ - { - accessorKey: 'name', - header: 'Name', - enableColumnFilter: true, - filterType: 'string' as const, - enableGrouping: true, - showGroupCount: true, - enableSorting: true, - enableHiding: true - }, - { - accessorKey: 'email', - header: 'Email', - enableColumnFilter: true, - filterType: 'string' as const, - enableSorting: true, - enableHiding: true - }, - { - accessorKey: 'role', - header: 'Role', - enableColumnFilter: true, - filterType: 'select' as const, - enableGrouping: true, - showGroupCount: true, - enableSorting: true, - enableHiding: true, - filterOptions: [ - { value: 'Admin', label: 'Admin' }, - { value: 'User', label: 'User' }, - { value: 'Manager', label: 'Manager' } - ] - }, - { - accessorKey: 'department', - header: 'Department', - enableGrouping: true, - showGroupCount: true, - enableSorting: true, - enableHiding: true - }, - { - accessorKey: 'team', - header: 'Team', - enableColumnFilter: true, - filterType: 'multiselect' as const, - enableGrouping: true, - showGroupCount: true, - enableSorting: true, - enableHiding: true, - filterOptions: [ - { value: 'Frontend', label: 'Frontend' }, - { value: 'Backend', label: 'Backend' }, - { value: 'Design', label: 'Design' }, - { value: 'DevOps', label: 'DevOps' }, - { value: 'Content', label: 'Content' }, - { value: 'Growth', label: 'Growth' }, - { value: 'East', label: 'East' }, - { value: 'West', label: 'West' }, - { value: 'Tier 1', label: 'Tier 1' }, - { value: 'Tier 2', label: 'Tier 2' } - ] - }, - { - accessorKey: 'location', - header: 'Location', - enableGrouping: true, - showGroupCount: true, - enableSorting: true, - enableHiding: true - }, - { accessorKey: 'phone', header: 'Phone', enableHiding: true }, - { - accessorKey: 'status', - header: 'Status', - enableGrouping: true, - showGroupCount: true, - enableSorting: true, - enableHiding: true - }, - { - accessorKey: 'joined', - header: 'Joined', - enableSorting: true, - enableHiding: true - }, - { accessorKey: 'name', id: 'name_2', header: 'Name', enableHiding: true }, - { accessorKey: 'email', id: 'email_2', header: 'Email', enableHiding: true }, - { accessorKey: 'role', id: 'role_2', header: 'Role', enableHiding: true }, - { - accessorKey: 'department', - id: 'dept_2', - header: 'Department', - enableHiding: true - }, - { accessorKey: 'team', id: 'team_2', header: 'Team', enableHiding: true }, - { - accessorKey: 'location', - id: 'loc_2', - header: 'Location', - enableHiding: true - }, - { accessorKey: 'phone', id: 'phone_2', header: 'Phone', enableHiding: true }, - { - accessorKey: 'status', - id: 'status_2', - header: 'Status', - enableHiding: true - }, - { - accessorKey: 'joined', - id: 'joined_2', - header: 'Joined', - enableHiding: true - }, - { accessorKey: 'name', id: 'name_3', header: 'Name', enableHiding: true }, - { accessorKey: 'email', id: 'email_3', header: 'Email', enableHiding: true }, - { accessorKey: 'role', id: 'role_3', header: 'Role', enableHiding: true } -]; - -const Page = () => { - const [navbarSearch, setNavbarSearch] = useState(''); - - return ( - - - - - {}} aria-label='Logo'> - - - - Raystack - - - - - }> - Examples - - } - > - DataTable - - }> - Reports - Activities - - - Settings - Notifications - - - - Help & Support - Preferences - - - - - - - - DataTable – Client mode - - - - ) => - setNavbarSearch(e.target.value) - } - onClear={() => setNavbarSearch('')} - size='small' - style={{ width: '200px' }} - /> - - - - - - - - - } - heading='No results' - variant='empty1' - subHeading='Try adjusting your filters or search.' - /> - } - /> - - - - - ); -}; - -export default Page; diff --git a/apps/www/src/app/examples/dataview/page.tsx b/apps/www/src/app/examples/dataview/page.tsx deleted file mode 100644 index 32c79b21c..000000000 --- a/apps/www/src/app/examples/dataview/page.tsx +++ /dev/null @@ -1,621 +0,0 @@ -/** biome-ignore-all lint/suspicious/noShadowRestrictedNames: TODO: look into this later */ -'use client'; - -import { CalendarIcon } from '@radix-ui/react-icons'; -import { - Avatar, - AvatarGroup, - Badge, - Chip, - DataView, - type DataViewField, - type DataViewListColumn, - EmptyState, - Flex, - getAvatarColor, - IconButton, - Indicator, - Navbar, - Sidebar, - Text -} from '@raystack/apsara'; -import { BellIcon, FilterIcon, SidebarIcon } from '@raystack/apsara/icons'; - -type ProfileCell = { row: { original: Profile } }; - -type Profile = { - id: string; - name: string; - subheading: string; - role: 'Admin' | 'User' | 'Manager'; - label: string; - status: 'Active' | 'Away' | 'Offline'; - collaborators: { id: string; name: string }[]; - team: string; - updatedAt: string; -}; - -const profiles: Profile[] = [ - { - id: '1', - name: 'Alice Cooper', - subheading: 'alice@example.com', - role: 'Admin', - label: 'Platform Lead', - status: 'Active', - collaborators: [ - { id: 'c1', name: 'Bob' }, - { id: 'c2', name: 'Carol' }, - { id: 'c3', name: 'Dan' } - ], - team: 'Frontend', - updatedAt: '2024-02-15' - }, - { - id: '2', - name: 'Bob Nguyen', - subheading: 'bob@example.com', - role: 'User', - label: 'Designer', - status: 'Active', - collaborators: [ - { id: 'c4', name: 'Eve' }, - { id: 'c5', name: 'Grace' } - ], - team: 'Design', - updatedAt: '2024-03-01' - }, - { - id: '3', - name: 'Carol Park', - subheading: 'carol@example.com', - role: 'Manager', - label: 'Backend Mgr', - status: 'Active', - collaborators: [ - { id: 'c6', name: 'Henry' }, - { id: 'c7', name: 'Ryan' }, - { id: 'c8', name: 'Wendy' }, - { id: 'c9', name: 'Leo' } - ], - team: 'Backend', - updatedAt: '2024-01-22' - }, - { - id: '4', - name: 'Dave Sanders', - subheading: 'dave@example.com', - role: 'User', - label: 'Sales AE', - status: 'Away', - collaborators: [{ id: 'c10', name: 'Paul' }], - team: 'Sales East', - updatedAt: '2024-02-28' - }, - { - id: '5', - name: 'Eve Okafor', - subheading: 'eve@example.com', - role: 'Admin', - label: 'Eng Lead', - status: 'Active', - collaborators: [ - { id: 'c11', name: 'Uma' }, - { id: 'c12', name: 'Jack' } - ], - team: 'Frontend', - updatedAt: '2024-03-10' - }, - { - id: '6', - name: 'Frank Liu', - subheading: 'frank@example.com', - role: 'User', - label: 'Support', - status: 'Active', - collaborators: [], - team: 'Tier 1', - updatedAt: '2024-03-04' - }, - { - id: '7', - name: 'Grace Romero', - subheading: 'grace@example.com', - role: 'Manager', - label: 'Design Mgr', - status: 'Active', - collaborators: [ - { id: 'c13', name: 'Bob' }, - { id: 'c14', name: 'Mia' }, - { id: 'c15', name: 'Tom' } - ], - team: 'Design', - updatedAt: '2024-02-02' - }, - { - id: '8', - name: 'Henry Becker', - subheading: 'henry@example.com', - role: 'Admin', - label: 'SRE', - status: 'Offline', - collaborators: [ - { id: 'c16', name: 'Carol' }, - { id: 'c17', name: 'Amy' } - ], - team: 'DevOps', - updatedAt: '2024-01-11' - }, - { - id: '9', - name: 'Ivy Chen', - subheading: 'ivy@example.com', - role: 'User', - label: 'Content Writer', - status: 'Active', - collaborators: [{ id: 'c18', name: 'Quinn' }], - team: 'Content', - updatedAt: '2024-03-08' - }, - { - id: '10', - name: 'Jack Patel', - subheading: 'jack@example.com', - role: 'User', - label: 'Frontend Eng', - status: 'Active', - collaborators: [ - { id: 'c19', name: 'Alice' }, - { id: 'c20', name: 'Eve' }, - { id: 'c21', name: 'Olivia' } - ], - team: 'Frontend', - updatedAt: '2024-02-20' - }, - { - id: '11', - name: 'Kate Rhodes', - subheading: 'kate@example.com', - role: 'Manager', - label: 'Sales Mgr', - status: 'Active', - collaborators: [ - { id: 'c22', name: 'Victor' }, - { id: 'c23', name: 'Dave' } - ], - team: 'Sales West', - updatedAt: '2024-01-30' - }, - { - id: '12', - name: 'Leo Braganza', - subheading: 'leo@example.com', - role: 'Admin', - label: 'DevOps Lead', - status: 'Active', - collaborators: [ - { id: 'c24', name: 'Amy' }, - { id: 'c25', name: 'Henry' } - ], - team: 'DevOps', - updatedAt: '2024-02-11' - } -]; - -const STATUS_COLOR: Record< - Profile['status'], - 'success' | 'warning' | 'neutral' -> = { - Active: 'success', - Away: 'warning', - Offline: 'neutral' -}; - -// Cell renderers shared between Table and List variants of DataView.List. -const renderNameCell = ({ row }: ProfileCell) => ( - - - - - {row.original.name} - - - {row.original.subheading} - - - -); - -const renderEmailCell = ({ row }: ProfileCell) => ( - - {row.original.subheading} - -); - -const renderRoleCell = ({ row }: ProfileCell) => ( - {row.original.role} -); - -const renderLabelCell = ({ row }: ProfileCell) => ( - - {row.original.label} - -); - -const renderTeamCell = ({ row }: ProfileCell) => ( - - {row.original.team} - -); - -const renderStatusCell = ({ row }: ProfileCell) => { - const status = row.original.status; - return {status}; -}; - -const renderCollaboratorsCell = ({ row }: ProfileCell) => { - const collaborators = row.original.collaborators; - if (!collaborators.length) { - return ( - - — - - ); - } - return ( - - {collaborators.map(c => ( - - ))} - - ); -}; - -const renderUpdatedAtCell = ({ row }: ProfileCell) => ( - - {row.original.updatedAt} - -); - -// Renderer-agnostic metadata — drives filters, sort, group, visibility across -// every renderer (List variants, Custom, …). Declared once on root. -const fields: DataViewField[] = [ - { - accessorKey: 'name', - label: 'Name', - filterable: true, - filterType: 'string', - sortable: true, - hideable: false - }, - { - accessorKey: 'subheading', - label: 'Email', - filterable: true, - filterType: 'string', - hideable: true - }, - { - accessorKey: 'role', - label: 'Role', - filterable: true, - filterType: 'select', - groupable: true, - hideable: true, - showGroupCount: true, - filterOptions: [ - { value: 'Admin', label: 'Admin' }, - { value: 'User', label: 'User' }, - { value: 'Manager', label: 'Manager' } - ] - }, - { - accessorKey: 'label', - label: 'Label', - filterable: true, - filterType: 'string', - hideable: true - }, - { - accessorKey: 'team', - label: 'Team', - filterable: true, - filterType: 'string', - groupable: true, - hideable: true - }, - { - accessorKey: 'status', - label: 'Status', - filterable: true, - filterType: 'select', - groupable: true, - hideable: true, - filterOptions: [ - { value: 'Active', label: 'Active' }, - { value: 'Away', label: 'Away' }, - { value: 'Offline', label: 'Offline' } - ] - }, - { - accessorKey: 'collaborators', - label: 'Collaborators', - hideable: true - }, - { - accessorKey: 'updatedAt', - label: 'Updated', - filterable: true, - filterType: 'date', - sortable: true, - hideable: true - } -]; - -// Table presentation of DataView.List — all fields surfaced as columns. -const tableColumns: DataViewListColumn[] = [ - { accessorKey: 'name', cell: renderNameCell, width: 'minmax(220px, 1.5fr)' }, - { - accessorKey: 'subheading', - cell: renderEmailCell, - width: 'minmax(200px, 1fr)' - }, - { accessorKey: 'role', cell: renderRoleCell, width: '120px' }, - { accessorKey: 'label', cell: renderLabelCell, width: 'minmax(140px, 1fr)' }, - { accessorKey: 'team', cell: renderTeamCell, width: 'minmax(120px, 1fr)' }, - { accessorKey: 'status', cell: renderStatusCell, width: '120px' }, - { - accessorKey: 'collaborators', - cell: renderCollaboratorsCell, - width: 'auto' - }, - { accessorKey: 'updatedAt', cell: renderUpdatedAtCell, width: '140px' } -]; - -// List presentation of DataView.List — the `1fr` middle track on Name pushes -// trailing metadata to the right edge (justify-between effect via grid). -const listColumns: DataViewListColumn[] = [ - { accessorKey: 'name', cell: renderNameCell, width: '1fr' }, - { accessorKey: 'label', cell: renderLabelCell, width: 'auto' }, - { accessorKey: 'team', cell: renderTeamCell, width: 'auto' }, - { - accessorKey: 'collaborators', - cell: renderCollaboratorsCell, - width: 'auto' - }, - { accessorKey: 'status', cell: renderStatusCell, width: 'auto' } -]; - -const INDICATOR_COLOR: Record< - Profile['status'], - 'success' | 'warning' | 'neutral' -> = { - Active: 'success', - Away: 'warning', - Offline: 'neutral' -}; - -function ProfileCard({ profile }: { profile: Profile }) { - return ( - - - - - - - - - - {profile.name} - - - - - {profile.role} - - - - - - } - > - Updated {profile.updatedAt} - - - - - {profile.team} - - - - - ); -} - -const Page = () => { - return ( - - - - - {}} aria-label='Logo'> - - - - Apsara - - - - - } - active - > - DataView - - - - Help & Support - Preferences - - - - - - - - DataView · People directory - - - - - - - - data={profiles} - fields={fields} - mode='client' - defaultSort={{ name: 'name', order: 'asc' }} - getRowId={(row: Profile) => row.id} - views={[ - { value: 'table', label: 'Table' }, - { value: 'list', label: 'List' }, - { value: 'custom', label: 'Custom' } - ]} - defaultView='table' - > - - - - - - - {/* Same renderer, two presentations — switched by the view switcher in DisplayControls */} - - - - name='custom'> - {({ table, hasData }) => { - if (!hasData) return null; - const rows = table - .getRowModel() - .rows.filter(r => !r.subRows?.length); - return ( -
- {rows.map(row => ( - - ))} -
- ); - }} - - - {/* Empty/zero state lifted out of renderers — single sibling reads context */} - - } - heading='No matching people' - variant='empty1' - subHeading='Try adjusting your filters or search.' - /> - - - } - heading='No people yet' - variant='empty1' - subHeading='Add your first teammate to get started.' - /> - - - -
-
-
- ); -}; - -export default Page; diff --git a/apps/www/src/app/examples/page.tsx b/apps/www/src/app/examples/page.tsx index 01048209b..f81eaac54 100644 --- a/apps/www/src/app/examples/page.tsx +++ b/apps/www/src/app/examples/page.tsx @@ -1,3051 +1,34 @@ -'use client'; -import { - ActivityLogIcon, - BarChartIcon, - DashboardIcon, - DotsHorizontalIcon, - FileTextIcon, - GearIcon, - HomeIcon, - MixerHorizontalIcon, - PersonIcon, - QuestionMarkCircledIcon, - BellIcon as RadixBellIcon -} from '@radix-ui/react-icons'; -import { - Amount, - Avatar, - AvatarGroup, - Breadcrumb, - Button, - Calendar, - Callout, - DataTable, - DatePicker, - Dialog, - Drawer, - EmptyState, - Flex, - IconButton, - Indicator, - Input, - Menu, - Navbar, - Popover, - RangePicker, - ScrollArea, - Search, - Select, - Sidebar, - Spinner, - Tabs, - Text, - TextArea, - Tooltip -} from '@raystack/apsara'; -import dayjs from 'dayjs'; -import React, { useState } from 'react'; +import { Flex, Text } from '@raystack/apsara'; -/** Kitchen-sink examples route rendering Apsara components for manual QA. */ -const Page = () => { - const [dialogOpen, setDialogOpen] = useState(false); - const [nestedDialogOpen, setNestedDialogOpen] = useState(false); - const [dialogDrawerOpen, setDialogDrawerOpen] = useState(false); - const [dialogSheetOpen, setDialogSheetOpen] = useState(false); - const [search1, setSearch1] = useState(''); - const [search2, setSearch2] = useState(''); - const [search3, setSearch3] = useState(''); - const [search4, setSearch4] = useState(''); - const [selectValue, setSelectValue] = useState(''); - const [selectValue1, setSelectValue1] = useState(''); - const [selectValue2, setSelectValue2] = useState(''); - const [inputValue, setInputValue] = useState(''); - const [calloutDismissed, setCalloutDismissed] = useState(false); - const [rangeValue, setRangeValue] = useState({ - from: dayjs('2027-11-15').toDate(), - to: dayjs('2027-12-10').toDate() - }); - - // 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: } - ]; - - const filterOptions = [ - { value: 'Option 1', label: 'Option 1', icon: }, - { value: 'Option 2', label: 'Option 2', icon: }, - { value: 'Option 3', label: 'Option 3', icon: } - ]; +export const metadata = { + title: 'Examples' +}; +/** + * Bare examples landing page. + * + * `/examples` is a manual-QA harness for trying Apsara components in a + * full-page context — the kind of thing the small doc demos can't show. + * It is not linked from the public site. + * + * To add an example, drop a new route folder next to this file, e.g. + * `app/examples//page.tsx`, and build whatever you need to test. + * See README.md in this folder for the convention. + */ +export default function ExamplesPage() { return ( - <> - - - - - console.log('Logo clicked')} - aria-label='Logo' - > - - - - Raystack - - - - - - }> - Dashboard - - - }> - Analytics - - - 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 - - - - - 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 - - }> - Documentation - - - - - - - - - - Examples - - - - - - - - - - - Main - - {`const button = (x>=2 && y!=3) - const getLoaderOnlyClass = (size) => - size === 'small' - ? styles['loader-only-button-small'] - : styles['loader-only-button-normal']; - - const test = 10 >= 8 : true : false; - - - <= < > >= == === != !== - - => ==> && || !! ?? - <-- --> *** **** - - /* comment */ /* ---------- __ */`} - - - ) => - setSearch1(e.target.value) - } - onClear={() => setSearch1('')} - /> - console.log(value)} - slotProps={{ - calendar: { - captionLayout: 'dropdown', - startMonth: dayjs().add(3, 'month').toDate(), - endMonth: dayjs().add(4, 'year').toDate(), - disabled: { - before: dayjs().add(3, 'month').toDate(), - after: dayjs().add(3, 'year').toDate() - } - }, - input: { - size: 'small' - } - }} - /> - - - setRangeValue({ - from: range.from ?? new Date(), - to: range.to ?? new Date() - }) - } - slotProps={{ - calendar: { - captionLayout: 'dropdown', - numberOfMonths: 2, - startMonth: dayjs('2024-01-01').toDate(), - endMonth: dayjs('2027-12-01').toDate(), - defaultMonth: dayjs('2027-11-01').toDate() - }, - startInput: { - size: 'small' - }, - endInput: { - size: 'small' - } - }} - /> - - - Some important message in the footer - - } - /> - - - - - Calendar with Date Info (Object) - - - - - - 25% - - - ), - [dayjs().add(5, 'day').format('DD-MM-YYYY')]: ( - - - - 25% - - - ), - [dayjs().add(10, 'day').format('DD-MM-YYYY')]: ( - - - - 25% - - - ) - }} - /> - - - Calendar with Date Info (Function) - - - { - const today = new Date(); - const isToday = - date.getDate() === today.getDate() && - date.getMonth() === today.getMonth() && - date.getFullYear() === today.getFullYear(); - - // Show info on Sundays - if (date.getDay() === 0) { - return ( - - - - Sun - - - ); - } - - // Show info on 15th of any month - if (date.getDate() === 15) { - return ( - - - - 15th - - - ); - } - - // Show info for today - if (isToday) { - return ( - - - - Today - - - ); - } - - return null; - }} - /> - - - Skeleton Examples - - - - - Hello - - } - /> - - Hello this is a long dummy text that spans multiple lines and - is quite lengthy. It is used to demonstrate the functionality - of the tooltip component in various scenarios. Hello this is a - long dummy text that spans multiple lines and is quite - lengthy. It is used to demonstrate the functionality of the - tooltip component in various scenarios. - - - - - - - - - - {/* Button Examples */} - - Button Examples - All Combinations - - - {/* Solid Variant */} - - Solid Variant - - {/* Normal Size */} - - Normal Size: - - - - - - - - - - - - - - - - - - - - - - - - {/* Small Size */} - - Small Size: - - - - - - - - - - - - - - - - - - - - - - - - - {/* Outline Variant */} - - Outline Variant - - {/* Normal Size */} - - Normal Size: - - - - - - - - - - - - - - - - - - - - - - - - {/* Small Size */} - - Small Size: - - - - - - - - - - - - - - - - - - - - - - - - - {/* Ghost Variant */} - - Ghost Variant - - {/* Normal Size */} - - Normal Size: - - - - - - - - - - - - - - - - - - - - - - - - {/* Small Size */} - - Small Size: - - - - - - - - - - - - - - - - - - - - - - - - - {/* Text Variant */} - - Text Variant - - {/* Normal Size */} - - Normal Size: - - - - - - - - - - - - - - - - - - - - - - - - {/* Small Size */} - - Small Size: - - - - - - - - - - - - - - - - - - - - - - - - - - Spinner Examples - - - - - - - - - - - - - - - Button Loading States Examples - - - - {/* Solid Variant */} - - Solid Variant (Inverted Spinner) - - - - - - - - {/* Outline Variant */} - - - Outline Variant (Matching Color Spinner) - - - - - - - - - {/* Ghost Variant */} - - - Ghost Variant (Matching Color Spinner for colored) - - - - - - - - - {/* Text Variant */} - - - Text Variant (Matching Color Spinner for colored) - - - - - - - - - - {/* Size Variants */} - - Size Variants - - - - - - - - - {/* Loading with and without text */} - - Loading With/Without Text - - - - - - - - - {/* Disabled Loading State */} - - Disabled Loading State - - - - - - - - - - ) => - setSearch2(e.target.value) - } - onClear={() => setSearch2('')} - /> - - ) => - setSearch3(e.target.value) - } - onClear={() => setSearch3('')} - /> - - ) => - setSearch4(e.target.value) - } - onClear={() => setSearch4('')} - /> - - - {/* Select component examples */} - - Select Examples - - - } - 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={ - - } - variant='empty1' - /> - -