Compare commits
8 Commits
95f72407f8
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| ce1efd1c13 | |||
| 58a67dfc75 | |||
| 6d3331f802 | |||
| 69751eb6f2 | |||
| 1c87e23e5d | |||
| d36330084a | |||
| b8fb8c63c6 | |||
| df7bbba915 |
37
.github/workflows/publish-package.yml
vendored
Normal file
37
.github/workflows/publish-package.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Publish package to GitHub Packages
|
||||
|
||||
# Publishes @richiesnitch/ads3-design-system to GitHub Packages when a version
|
||||
# tag (v*) is pushed, or on manual dispatch. Uses the workflow's built-in
|
||||
# GITHUB_TOKEN (no PAT needed) — packages:write permission is granted below.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: https://npm.pkg.github.com
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build library
|
||||
run: npm run build:lib
|
||||
|
||||
- name: Publish
|
||||
run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -6,7 +6,15 @@ This is the living architecture document for the ADS 3.0 design system. All stru
|
||||
|
||||
## 1. Overview
|
||||
|
||||
ADS 3.0 Design System is a React component library implementing the ADS 3.0 (Adaptive Design System) design language. It provides tokens, primitives, and composite components as a shared foundation. Application-specific screens and domain logic belong in downstream forks of this repo.
|
||||
ADS 3.0 Design System is a React component library implementing the ADS 3.0 (Adaptive Design System) design language. It provides tokens, primitives, and composite components as a shared foundation. Application-specific screens and domain logic belong in **consuming applications**, not in this repo.
|
||||
|
||||
### Distribution
|
||||
|
||||
ADS is distributed as a **versioned npm package** consumed by downstream apps — not by forking this repo. (The earlier fork-based model has been superseded.)
|
||||
|
||||
- Published to **GitHub Packages** as `@richiesnitch/ads3-design-system` from `Richiesnitch/ads3-design-system`; a publish-on-tag GitHub Action runs `build:lib` and `npm publish`.
|
||||
- Built via `npm run build:lib` (`vite.lib.config.ts`): bundles `src/index.ts` to ESM with React/react-dom externalised and internal `@/` imports resolved at build time; emits type declarations and copies `tokens.css` / `typography.css` into `dist/`.
|
||||
- Consumers import components from the package entry, `@richiesnitch/ads3-design-system/tokens` + `/typography` for the design-token CSS, and point Tailwind's `@source` at the built bundle to generate utilities. No `@/` alias or sibling-folder clone required.
|
||||
|
||||
---
|
||||
|
||||
@@ -85,6 +93,8 @@ Page-level layout components that define the shell and content structure. Templa
|
||||
- **DashboardPage** — PageHeader + stat cards row + responsive 2-column content grid
|
||||
- **ListPage** — PageHeader + stat cards + list header with actions + scrollable item list
|
||||
- **FormPage** — PageHeader + optional action bar + optional vertical stepper + constrained-width form content
|
||||
- **DetailPage** — PageHeader + optional action bar (e.g. tabs) + single-column constrained content for viewing records/profiles/documents
|
||||
- **CenteredPage** — TopBar (optional) + horizontally/vertically centered content, no sidebar. For login, error, onboarding flows
|
||||
|
||||
Templates have Storybook stories tagged `['autodocs', 'template']` that show realistic "recipe" compositions — full pages built from real components with sample data. These serve as reference implementations for AI coding agents.
|
||||
|
||||
|
||||
43
DESIGN.md
43
DESIGN.md
@@ -1069,6 +1069,49 @@ import { FormPage } from '@/components/templates/FormPage'
|
||||
</FormPage>
|
||||
```
|
||||
|
||||
#### DetailPage
|
||||
|
||||
Single-column detail view for records, profiles, or documents. Constrained width for readability.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `header` | `ReactNode` | — | PageHeader or custom header |
|
||||
| `actions` | `ReactNode` | — | Action bar below header (e.g. tabs, buttons) |
|
||||
| `maxWidth` | `'md' \| 'lg' \| 'xl' \| 'full'` | `'xl'` | Content area max width |
|
||||
| `children` | `ReactNode` | — | **Required.** Page content |
|
||||
|
||||
```tsx
|
||||
import { DetailPage } from '@/components/templates/DetailPage'
|
||||
|
||||
<DetailPage
|
||||
header={<PageHeader title="Alex Chen" subtitle="Senior Engineer" />}
|
||||
actions={<Tabs value={tab} onChange={setTab}><TabList><Tab value="overview">Overview</Tab></TabList></Tabs>}
|
||||
maxWidth="lg"
|
||||
>
|
||||
<Card><CardContent>Detail content</CardContent></Card>
|
||||
</DetailPage>
|
||||
```
|
||||
|
||||
#### CenteredPage
|
||||
|
||||
Full-page layout with no sidebar and centered content. Use for login, sign-up, error pages, onboarding, or any focused single-task flow.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `topBar` | `ReactNode` | — | Optional TopBar |
|
||||
| `maxWidth` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Content max width |
|
||||
| `children` | `ReactNode` | — | **Required.** Centered content |
|
||||
|
||||
```tsx
|
||||
import { CenteredPage } from '@/components/templates/CenteredPage'
|
||||
|
||||
<CenteredPage topBar={<TopBar title="" logo={<NswLogo />} />} maxWidth="md">
|
||||
<Card variant="elevated">
|
||||
<CardContent>Login form here</CardContent>
|
||||
</Card>
|
||||
</CenteredPage>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Do's and Don'ts
|
||||
|
||||
690
package-lock.json
generated
690
package-lock.json
generated
@@ -1,25 +1,24 @@
|
||||
{
|
||||
"name": "sdc-frontend",
|
||||
"version": "0.0.0",
|
||||
"name": "@richiesnitch/ads3-design-system",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sdc-frontend",
|
||||
"version": "0.0.0",
|
||||
"name": "@richiesnitch/ads3-design-system",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.19",
|
||||
"@fontsource-variable/public-sans": "^5.2.7",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"clsx": "^2.1.1",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tailwindcss": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^5.2.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@microsoft/api-extractor": "^7.58.7",
|
||||
"@storybook/addon-a11y": "^10.4.0",
|
||||
"@storybook/addon-designs": "^11.1.3",
|
||||
"@storybook/addon-docs": "^10.4.0",
|
||||
@@ -42,11 +41,18 @@
|
||||
"playwright": "^1.60.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"storybook": "^10.4.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vite": "^8.0.12",
|
||||
"vite-plugin-dts": "^5.0.2",
|
||||
"vitest": "^4.1.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
@@ -1171,6 +1177,130 @@
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/api-extractor": {
|
||||
"version": "7.58.7",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.58.7.tgz",
|
||||
"integrity": "sha512-yK6OycD46gIzLRpj6ueVUWPk1ACSpkN1LBo05gY1qPTylbWyUCanXfH7+VgkI5LJrJoRSQR5F04XuCffCXLOBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@microsoft/api-extractor-model": "7.33.8",
|
||||
"@microsoft/tsdoc": "~0.16.0",
|
||||
"@microsoft/tsdoc-config": "~0.18.1",
|
||||
"@rushstack/node-core-library": "5.23.1",
|
||||
"@rushstack/rig-package": "0.7.3",
|
||||
"@rushstack/terminal": "0.24.0",
|
||||
"@rushstack/ts-command-line": "5.3.9",
|
||||
"diff": "~8.0.2",
|
||||
"minimatch": "10.2.3",
|
||||
"resolve": "~1.22.1",
|
||||
"semver": "~7.7.4",
|
||||
"source-map": "~0.6.1",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"bin": {
|
||||
"api-extractor": "bin/api-extractor"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/api-extractor-model": {
|
||||
"version": "7.33.8",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.8.tgz",
|
||||
"integrity": "sha512-aIcoQggPyer3B6Ze3usz0YWC/oBwUHfRH5ETUsr+oT2BRA6SfTJl7IKPcPZkX4UR+PohowzW4uMxsvjrn8vm+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@microsoft/tsdoc": "~0.16.0",
|
||||
"@microsoft/tsdoc-config": "~0.18.1",
|
||||
"@rushstack/node-core-library": "5.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/api-extractor/node_modules/minimatch": {
|
||||
"version": "10.2.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz",
|
||||
"integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/api-extractor/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/api-extractor/node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/tsdoc": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz",
|
||||
"integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@microsoft/tsdoc-config": {
|
||||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.1.tgz",
|
||||
"integrity": "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@microsoft/tsdoc": "0.16.0",
|
||||
"ajv": "~8.18.0",
|
||||
"jju": "~1.4.0",
|
||||
"resolve": "~1.22.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/tsdoc-config/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
@@ -2197,6 +2327,158 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/node-core-library": {
|
||||
"version": "5.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.23.1.tgz",
|
||||
"integrity": "sha512-wlKmIKIYCKuCASbITvOxLZXepPbwXvrv7S6ig6XNWFchSyhL/E2txmVXspHY49Wu2dzf7nI27a2k/yV5BA3EiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "~8.18.0",
|
||||
"ajv-draft-04": "~1.0.0",
|
||||
"ajv-formats": "~3.0.1",
|
||||
"fs-extra": "~11.3.0",
|
||||
"import-lazy": "~4.0.0",
|
||||
"jju": "~1.4.0",
|
||||
"resolve": "~1.22.1",
|
||||
"semver": "~7.7.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/node-core-library/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
|
||||
"integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rushstack/node-core-library/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/problem-matcher": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.2.1.tgz",
|
||||
"integrity": "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/rig-package": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.3.tgz",
|
||||
"integrity": "sha512-aAA518n6wxxjCfnTAOjQnm7ngNE0FVHxHAw2pxKlIhxrMn0XQjGcXKF0oKWpjBgJOmsaJpVob/v+zr3zxgPWuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jju": "~1.4.0",
|
||||
"resolve": "~1.22.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/terminal": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.24.0.tgz",
|
||||
"integrity": "sha512-8ZQS4MMaGsv27EXCBiH7WMPkRZrffeDoIevs6z9TM5dzqiY6+Hn4evfK/G+gvgBTjfvfkHIZPQQmalmI2sM4TQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rushstack/node-core-library": "5.23.1",
|
||||
"@rushstack/problem-matcher": "0.2.1",
|
||||
"supports-color": "~8.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/terminal/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/ts-command-line": {
|
||||
"version": "5.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.9.tgz",
|
||||
"integrity": "sha512-GIHqU+sRGQ3LGWAZu1O+9Yh++qwtyNIIGuNbcWHJjBTm2qRez0cwINUHZ+pQLR8UuzZDcMajrDaNbUYoaL/XtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rushstack/terminal": "0.24.0",
|
||||
"@types/argparse": "1.0.38",
|
||||
"argparse": "~1.0.9",
|
||||
"string-argv": "~0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
@@ -2925,6 +3207,13 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/argparse": {
|
||||
"version": "1.0.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz",
|
||||
"integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
@@ -3588,6 +3877,35 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.28",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
|
||||
"integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/source-map": "2.4.28"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/source-map": {
|
||||
"version": "2.4.28",
|
||||
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
|
||||
"integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@volar/typescript": {
|
||||
"version": "2.4.28",
|
||||
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
|
||||
"integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.28",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@webcontainer/env": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@webcontainer/env/-/env-1.1.1.tgz",
|
||||
@@ -3635,6 +3953,48 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
|
||||
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/ajv": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
@@ -3660,6 +4020,16 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
@@ -3919,6 +4289,20 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/compare-versions": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
|
||||
"integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
|
||||
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@@ -4052,6 +4436,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
|
||||
"integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||
@@ -4435,6 +4829,13 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
|
||||
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -4456,6 +4857,23 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
@@ -4524,6 +4942,21 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.3.5",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
|
||||
"integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -4665,6 +5098,16 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/import-lazy": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
|
||||
"integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
@@ -4830,6 +5273,13 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/jju": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
|
||||
"integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -4914,6 +5364,13 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kolorist": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
|
||||
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -5189,6 +5646,24 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/local-pkg": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz",
|
||||
"integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mlly": "^1.7.4",
|
||||
"pkg-types": "^2.3.0",
|
||||
"quansync": "^0.2.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -5339,6 +5814,38 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
|
||||
"integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^1.3.1",
|
||||
"ufo": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly/node_modules/confbox": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
||||
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mlly/node_modules/pkg-types": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.1.8",
|
||||
"mlly": "^1.7.4",
|
||||
"pathe": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mrmime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
||||
@@ -5548,6 +6055,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -5644,6 +6158,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
|
||||
"integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.4",
|
||||
"exsolve": "^1.0.8",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||
@@ -5860,10 +6386,27 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -5902,15 +6445,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
|
||||
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
||||
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.6"
|
||||
"react": "^19.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
@@ -5965,6 +6508,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.12",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
|
||||
@@ -6113,6 +6666,13 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/sqids": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sqids/-/sqids-0.3.0.tgz",
|
||||
@@ -6207,6 +6767,16 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/string-argv": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
|
||||
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6.19"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
@@ -6500,6 +7070,13 @@
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
|
||||
"integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
@@ -6533,6 +7110,60 @@
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-dts": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.0.2.tgz",
|
||||
"integrity": "sha512-VbNiMD0LMl/t6nJueGtrCp79N7ZO1nquxj/FUybJDnKwZGsnW2wjdwBSzA3QEHujoxmxZIptsG43hL7LzXE96w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.1.4",
|
||||
"@volar/typescript": "^2.4.26",
|
||||
"compare-versions": "^6.1.1",
|
||||
"debug": "^4.4.0",
|
||||
"kolorist": "^1.8.0",
|
||||
"local-pkg": "^1.1.1",
|
||||
"magic-string": "^0.30.17",
|
||||
"unplugin": "^2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@microsoft/api-extractor": ">=7",
|
||||
"@rspack/core": "^1",
|
||||
"@vue/language-core": "~3.1.5",
|
||||
"esbuild": "*",
|
||||
"rolldown": "*",
|
||||
"rollup": ">=3",
|
||||
"typescript": ">=4",
|
||||
"vite": ">=3",
|
||||
"webpack": "^4 || ^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@microsoft/api-extractor": {
|
||||
"optional": true
|
||||
},
|
||||
"@rspack/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@vue/language-core": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
},
|
||||
"rolldown": {
|
||||
"optional": true
|
||||
},
|
||||
"rollup": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
},
|
||||
"webpack": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||
@@ -6683,6 +7314,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-dts": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-5.0.2.tgz",
|
||||
"integrity": "sha512-lNeHS+dwGju6eRmNvZQt8Shwv9j3m98hbHse/lIbLq9q3yE2DcIOBBYQEVUF6tS0kOmv+VA9Z5FqmzFnGe4U8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"unplugin-dts": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@microsoft/api-extractor": ">=7",
|
||||
"rollup": ">=3",
|
||||
"vite": ">=3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@microsoft/api-extractor": {
|
||||
"optional": true
|
||||
},
|
||||
"rollup": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz",
|
||||
@@ -6801,6 +7458,13 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webpack-virtual-modules": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||
|
||||
38
package.json
38
package.json
@@ -1,29 +1,54 @@
|
||||
{
|
||||
"name": "ads3-design-system",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"name": "@richiesnitch/ads3-design-system",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Richiesnitch/ads3-design-system.git"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.pkg.github.com"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./tokens": "./dist/tokens.css",
|
||||
"./typography": "./dist/typography.css"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"build:lib": "vite build --config vite.lib.config.ts && cp src/tokens/tokens.css src/styles/typography.css dist/",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"lucide-react": "^1.16.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.19",
|
||||
"@fontsource-variable/public-sans": "^5.2.7",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"clsx": "^2.1.1",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tailwindcss": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^5.2.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@microsoft/api-extractor": "^7.58.7",
|
||||
"@storybook/addon-a11y": "^10.4.0",
|
||||
"@storybook/addon-designs": "^11.1.3",
|
||||
"@storybook/addon-docs": "^10.4.0",
|
||||
@@ -46,10 +71,13 @@
|
||||
"playwright": "^1.60.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"storybook": "^10.4.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vite": "^8.0.12",
|
||||
"vite-plugin-dts": "^5.0.2",
|
||||
"vitest": "^4.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
23
public/nsw-logo.svg
Normal file
23
public/nsw-logo.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_167_1431)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4904 20.0612C13.3542 19.8393 13.1486 19.6376 12.8798 19.462C12.6114 19.2865 12.2456 19.1525 11.7938 19.0638L10.5361 18.803C10.1561 18.7205 9.89415 18.6137 9.75741 18.4862C9.62416 18.3617 9.55657 18.1961 9.55656 17.9941C9.55655 17.8654 9.58561 17.7483 9.64326 17.6456C9.70054 17.5427 9.78375 17.4516 9.89033 17.3748C9.99754 17.298 10.1309 17.2373 10.2863 17.1947C10.4436 17.1512 10.6203 17.1293 10.8117 17.1293C11.0636 17.1293 11.2909 17.1628 11.4872 17.2292C11.6811 17.2953 11.8419 17.4035 11.9648 17.5518C12.0879 17.7001 12.1635 17.9017 12.1894 18.1511L12.1929 18.1856L13.7534 18.1856L13.7527 18.146C13.7458 17.7219 13.6266 17.3328 13.3986 16.9898C13.1703 16.6464 12.8339 16.3706 12.3982 16.1694C11.9636 15.9688 11.4214 15.867 10.7864 15.8671C10.253 15.8671 9.76419 15.9608 9.33312 16.146C8.90088 16.3316 8.5538 16.5993 8.30217 16.942C8.0494 17.2865 7.92342 17.6987 7.92725 18.1678C7.93759 18.7401 8.1102 19.2046 8.44013 19.5491C8.76892 19.8917 9.21955 20.1198 9.77965 20.2262L11.0482 20.4866C11.2807 20.5331 11.4918 20.5941 11.6756 20.6683C11.8558 20.7409 11.9997 20.8354 12.1033 20.9491C12.2044 21.06 12.2556 21.206 12.2557 21.3819C12.2557 21.5779 12.1919 21.7434 12.0662 21.8733C11.9375 22.0062 11.7627 22.1064 11.5474 22.1722C11.3278 22.239 11.0823 22.2728 10.8176 22.2728C10.5595 22.2729 10.3216 22.2326 10.1109 22.1527C9.9012 22.0739 9.72519 21.9587 9.58771 21.8104C9.4506 21.6629 9.35662 21.4781 9.30815 21.2619L9.30125 21.2315L7.71088 21.2316L7.71508 21.2743C7.75252 21.6492 7.86824 21.9814 8.05879 22.2622C8.24896 22.5411 8.49258 22.7762 8.7828 22.9621C9.07224 23.1468 9.39679 23.2878 9.74778 23.3804C10.0979 23.4725 10.4599 23.5194 10.823 23.5193C11.4176 23.5193 11.9448 23.4248 12.3904 23.2381C12.838 23.0506 13.192 22.7925 13.4432 22.4709C13.6963 22.1471 13.8246 21.7779 13.8246 21.3727C13.8246 21.1814 13.8032 20.9686 13.7604 20.7404C13.7165 20.5103 13.6256 20.2818 13.4904 20.0612ZM2.3858 18.4422L5.78063 23.3835L7.16138 23.3834L7.16106 15.9938L5.61649 15.9939L5.6167 20.7472L2.3689 16.0106L2.35744 15.9941L0.845709 15.9941L0.846031 23.3837L2.38602 23.3836L2.3858 18.4422ZM20.4675 20.7194L21.7903 15.9934L23.3036 15.9933L21.135 23.383L19.9059 23.3831L18.5673 18.7052L17.2196 23.3832L16.0107 23.3832L13.8464 15.9937L15.3647 15.9937L16.6875 20.7188L18.0105 15.9936L19.1293 15.9935L20.4675 20.7194Z" fill="white"/>
|
||||
<path d="M11.2457 14.1603C9.42964 13.9357 7.66557 14.5223 4.56151 13.6728C4.24457 13.586 4.12774 13.9225 4.2992 14.2045C5.13321 15.5765 9.34571 14.5011 11.2541 14.3029C11.3374 14.2941 11.329 14.1707 11.2457 14.1603Z" fill="#D7153A"/>
|
||||
<path d="M19.1946 13.6722C16.0906 14.522 14.3265 13.9355 12.5104 14.1603C12.4272 14.1707 12.4184 14.2944 12.502 14.3028C14.4109 14.5009 18.6231 15.5759 19.457 14.2038C19.6283 13.9218 19.5116 13.5853 19.1946 13.6722Z" fill="#D7153A"/>
|
||||
<path d="M5.50686 11.7099C4.87563 10.7776 4.35971 9.73467 3.96025 8.5873C2.75554 8.9419 1.52259 9.45362 0.267489 10.1232C0.105208 10.2096 0.005553 10.3718 0.000597054 10.5561C-0.00397704 10.7406 0.0869094 10.9072 0.244616 11.0021C2.67513 12.4667 5.05139 13.3044 7.31879 13.4982C6.64368 13.0835 6.02468 12.4746 5.50686 11.7099Z" fill="#D7153A"/>
|
||||
<path d="M2.12068 8.59386C2.67663 8.36988 3.22763 8.17779 3.77366 8.01605C3.57813 7.37686 3.41773 6.70733 3.29092 6.0086C2.66316 5.92988 2.01364 5.87882 1.34159 5.85657C1.33586 5.85657 1.32975 5.85619 1.32402 5.85619C1.148 5.8562 0.98915 5.94532 0.89675 6.09744C0.801296 6.25417 0.797104 6.44431 0.885699 6.60564C1.27598 7.3155 1.68802 7.97811 2.12068 8.59386Z" fill="#D7153A"/>
|
||||
<path d="M8.09299 13.2336C8.46988 13.401 8.85555 13.4947 9.22934 13.5143C8.31407 12.8544 7.595 11.7354 7.18063 10.3092C6.64673 8.47307 6.46833 6.44717 6.64426 4.26145C5.92407 3.80399 5.14357 3.37571 4.30311 2.9778C4.14081 2.90097 3.95524 2.91596 3.80633 3.01777C3.65779 3.11918 3.5757 3.28705 3.58678 3.46644C3.78243 6.63782 4.59397 9.29789 5.99849 11.3725C6.58733 12.243 7.31172 12.8863 8.09299 13.2336Z" fill="#D7153A"/>
|
||||
<path d="M9.88405 2.85273C9.45322 2.32648 8.98586 1.80601 8.48142 1.29168C8.38366 1.19181 8.25766 1.1388 8.12744 1.13881C8.08086 1.13881 8.0339 1.14573 7.9873 1.15956C7.81052 1.21219 7.6826 1.35471 7.64405 1.54102C7.54555 2.01965 7.35811 2.99535 7.27529 3.96029C7.92024 4.3878 8.51594 4.8399 9.06162 5.31542C9.27309 4.50375 9.54799 3.68131 9.88405 2.85273Z" fill="#D7153A"/>
|
||||
<path d="M23.4873 10.1218C22.2321 9.45228 20.9992 8.94067 19.7944 8.58617C19.3951 9.73359 18.8792 10.7765 18.248 11.7088C17.7304 12.474 17.1113 13.0826 16.4359 13.4975C18.7033 13.3034 21.0795 12.4654 23.5099 11.0006C23.6676 10.9057 23.7588 10.7391 23.7538 10.5547C23.7497 10.3703 23.6496 10.2086 23.4873 10.1218Z" fill="#D7153A"/>
|
||||
<path d="M21.6346 8.59305C22.0673 7.97727 22.4793 7.31424 22.8695 6.60435C22.958 6.44301 22.9543 6.25325 22.8584 6.09614C22.7659 5.94441 22.6071 5.85492 22.4311 5.85493C22.4253 5.85493 22.4193 5.85493 22.4136 5.85531C21.7415 5.87762 21.0915 5.92912 20.4642 6.00751C20.3378 6.70625 20.1771 7.3758 19.9817 8.015C20.5277 8.17708 21.079 8.36951 21.6346 8.59305Z" fill="#D7153A"/>
|
||||
<path d="M16.5749 10.3091C16.1607 11.7351 15.4413 12.8544 14.5264 13.5144C14.9002 13.4948 15.2863 13.4006 15.6628 13.2331C16.444 12.8859 17.1683 12.2424 17.7572 11.3723C19.1615 9.29759 19.9728 6.63745 20.1681 3.46605C20.1792 3.28667 20.0971 3.1188 19.9485 3.0174C19.7996 2.91561 19.6141 2.90064 19.4518 2.97747C18.6113 3.37509 17.8308 3.80342 17.1107 4.26134C17.2869 6.44704 17.1082 8.47257 16.5749 10.3091Z" fill="#D7153A"/>
|
||||
<path d="M16.4796 3.95948C16.3967 2.99493 16.2092 2.01924 16.1107 1.54024C16.0724 1.35394 15.9441 1.21143 15.7674 1.15881C15.7208 1.14498 15.6738 1.13807 15.6272 1.13807C15.497 1.13808 15.371 1.1911 15.2732 1.29097C14.7689 1.80534 14.3015 2.32586 13.8708 2.85214C14.2068 3.6807 14.4818 4.50312 14.6938 5.31439C15.2394 4.83919 15.8346 4.38705 16.4796 3.95948Z" fill="#D7153A"/>
|
||||
<path d="M11.8757 8.75247C12.4862 7.53897 13.4614 6.45107 14.1937 5.76383C13.7916 4.11593 13.1725 2.56137 12.3179 0.934998C12.2297 0.767136 12.0639 0.667269 11.8757 0.667277C11.6874 0.667286 11.5221 0.767551 11.4336 0.935036C10.5703 2.56686 9.94066 4.19061 9.56004 5.76364C10.3245 6.47617 11.333 7.63813 11.8757 8.75247Z" fill="#D7153A"/>
|
||||
<path d="M14.1193 13.0585C14.7523 12.6105 15.3854 11.8299 15.7871 10.744C16.5648 8.6416 16.6452 6.29993 16.5337 4.64279C15.124 5.58974 12.8865 7.56735 12.1224 9.68163C11.7628 10.6766 11.5953 12.0318 11.8724 12.7877C11.9882 13.1039 12.1852 13.3436 12.4559 13.4576C12.866 13.6304 13.4961 13.4998 14.1193 13.0585Z" fill="#D7153A"/>
|
||||
<path d="M11.5602 9.497C11.3738 9.05911 11.1683 8.64617 10.8495 8.18215C9.94749 6.86962 8.73206 5.68578 7.21952 4.64293C7.20388 4.88877 6.98329 7.5439 7.75778 10.1398C8.34706 12.1142 9.35364 12.9185 9.90537 13.2293C10.4606 13.5416 10.9685 13.6153 11.5641 13.4662C10.9604 12.6381 11.0356 10.9386 11.5602 9.497Z" fill="#D7153A"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_167_1431">
|
||||
<rect width="24" height="23.1724" fill="white" transform="translate(0 0.667796)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
25
src/components/organisms/ApiSettings/ApiSettings.stories.tsx
Normal file
25
src/components/organisms/ApiSettings/ApiSettings.stories.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { useState } from 'react'
|
||||
import { ApiSettings } from './ApiSettings'
|
||||
import { Button } from '@/components/atoms/Button'
|
||||
|
||||
const meta: Meta<typeof ApiSettings> = {
|
||||
title: 'Organisms/ApiSettings',
|
||||
component: ApiSettings,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof ApiSettings>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(true)
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Open API settings</Button>
|
||||
<ApiSettings open={open} onClose={() => setOpen(false)} />
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
146
src/components/organisms/ApiSettings/ApiSettings.tsx
Normal file
146
src/components/organisms/ApiSettings/ApiSettings.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
} from '@/components/molecules/Dialog'
|
||||
import { Button } from '@/components/atoms/Button'
|
||||
import { Input } from '@/components/atoms/Input'
|
||||
import { Alert } from '@/components/molecules/Alert'
|
||||
import { Badge } from '@/components/atoms/Badge'
|
||||
import {
|
||||
getEndpoint,
|
||||
getApiKey,
|
||||
saveCredentials,
|
||||
testConnection,
|
||||
} from '@/lib/credentials'
|
||||
|
||||
export interface ApiSettingsProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
/** Called after credentials are saved (e.g. to refresh a "no API key" badge). */
|
||||
onSaved?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared API-settings dialog for the SDC tool suite. Reads/writes the suite-wide
|
||||
* `sdc_api_endpoint` + `sdc_api_key` so the key is entered once across all tools.
|
||||
*/
|
||||
export function ApiSettings({ open, onClose, onSaved }: ApiSettingsProps) {
|
||||
const [endpoint, setEndpoint] = useState('')
|
||||
const [apiKey, setApiKey] = useState('')
|
||||
const [status, setStatus] = useState<{ type: 'success' | 'error' | 'info'; message: string } | null>(null)
|
||||
const [testing, setTesting] = useState(false)
|
||||
const [models, setModels] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setEndpoint(getEndpoint())
|
||||
setApiKey(getApiKey())
|
||||
setStatus(null)
|
||||
setModels([])
|
||||
}
|
||||
}, [open])
|
||||
|
||||
function handleSave() {
|
||||
if (!endpoint.trim()) {
|
||||
setStatus({ type: 'error', message: 'Please enter a gateway endpoint.' })
|
||||
return
|
||||
}
|
||||
if (!apiKey.trim()) {
|
||||
setStatus({ type: 'error', message: 'Please enter an API key.' })
|
||||
return
|
||||
}
|
||||
saveCredentials(endpoint, apiKey)
|
||||
onSaved?.()
|
||||
setStatus({ type: 'success', message: 'Credentials saved. They apply across all SDC tools.' })
|
||||
}
|
||||
|
||||
async function handleTest() {
|
||||
if (!endpoint.trim() || !apiKey.trim()) {
|
||||
setStatus({ type: 'error', message: 'Enter an endpoint and key first.' })
|
||||
return
|
||||
}
|
||||
saveCredentials(endpoint, apiKey)
|
||||
onSaved?.()
|
||||
setTesting(true)
|
||||
setStatus({ type: 'info', message: 'Testing connection…' })
|
||||
setModels([])
|
||||
try {
|
||||
const result = await testConnection(endpoint, apiKey)
|
||||
setModels(result.models)
|
||||
setStatus({
|
||||
type: 'success',
|
||||
message: 'Connected. ' + result.models.length + ' model(s) available.',
|
||||
})
|
||||
} catch (e) {
|
||||
setStatus({ type: 'error', message: (e as Error).message })
|
||||
} finally {
|
||||
setTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} size="lg">
|
||||
<DialogHeader onClose={onClose}>
|
||||
<DialogTitle>API Settings</DialogTitle>
|
||||
<DialogDescription>
|
||||
Configure your gateway connection. This is shared across all SDC tools.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
variant="stacked"
|
||||
label="Gateway Endpoint"
|
||||
description="The base URL of your LiteLLM gateway"
|
||||
type="url"
|
||||
placeholder="https://your-gateway.example.com"
|
||||
value={endpoint}
|
||||
onChange={(e) => setEndpoint(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
variant="stacked"
|
||||
label="API Key"
|
||||
type="password"
|
||||
placeholder="Paste your API key here"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
/>
|
||||
|
||||
{status && (
|
||||
<Alert
|
||||
variant={status.type === 'success' ? 'success' : status.type === 'error' ? 'error' : 'info'}
|
||||
title={status.type === 'success' ? 'Success' : status.type === 'error' ? 'Error' : ''}
|
||||
onClose={() => setStatus(null)}
|
||||
>
|
||||
{status.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{models.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-small font-semibold text-text-secondary">Available Models</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{models.map((m) => (
|
||||
<Badge key={m} variant="info-light">{m}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" onClick={handleTest} loading={testing}>
|
||||
Test Connection
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
2
src/components/organisms/ApiSettings/index.ts
Normal file
2
src/components/organisms/ApiSettings/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { ApiSettings } from './ApiSettings'
|
||||
export type { ApiSettingsProps } from './ApiSettings'
|
||||
20
src/components/organisms/SdcTopBar/SdcTopBar.stories.tsx
Normal file
20
src/components/organisms/SdcTopBar/SdcTopBar.stories.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { SdcTopBar } from './SdcTopBar'
|
||||
|
||||
const meta: Meta<typeof SdcTopBar> = {
|
||||
title: 'Organisms/SdcTopBar',
|
||||
component: SdcTopBar,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof SdcTopBar>
|
||||
|
||||
export const Default: Story = {
|
||||
args: { appName: 'Status Report', activeTool: 'status-report' },
|
||||
}
|
||||
|
||||
export const Synthesiser: Story = {
|
||||
args: { appName: 'Research Synthesiser', activeTool: 'synthesiser' },
|
||||
}
|
||||
118
src/components/organisms/SdcTopBar/SdcTopBar.tsx
Normal file
118
src/components/organisms/SdcTopBar/SdcTopBar.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import { Settings, Grid3x3 } from 'lucide-react'
|
||||
import { TopBar } from '@/components/organisms/TopBar'
|
||||
import { IconButton } from '@/components/atoms/IconButton'
|
||||
import { Badge } from '@/components/atoms/Badge'
|
||||
import { Popover, PopoverTrigger, PopoverContent } from '@/components/molecules/Popover'
|
||||
import { List, ListItem, ListSubheader, ListDivider } from '@/components/atoms/List'
|
||||
import { ApiSettings } from '@/components/organisms/ApiSettings'
|
||||
import { hasCredentials as checkCredentials } from '@/lib/credentials'
|
||||
|
||||
export interface SdcTool {
|
||||
/** Stable identifier, used to mark the current tool active. */
|
||||
slug: string
|
||||
label: string
|
||||
/** Relative link from one tool folder to another, e.g. '../SDC Status Report/index.html'. */
|
||||
href: string
|
||||
group: string
|
||||
}
|
||||
|
||||
/** The SDC tool suite directory. Adding a tool here updates the menu in every app. */
|
||||
export const SDC_TOOLS: SdcTool[] = [
|
||||
{ slug: 'synthesiser', label: 'Research Synthesiser', group: 'HCD Tools', href: '../SDC Project Synthesiser/index.html' },
|
||||
{ slug: 'data-synthesis', label: 'Data Synthesis', group: 'HCD Tools', href: '../SDC Data Synthesis/index.html' },
|
||||
{ slug: 'persona', label: 'Persona Builder', group: 'HCD Tools', href: '../SDC Persona Builder/index.html' },
|
||||
{ slug: 'case-study', label: 'Case Study Generator', group: 'HCD Tools', href: '../SDC Case Study Generator/index.html' },
|
||||
{ slug: 'charter', label: 'Project Charter', group: 'Project Management', href: '../SDC Project Charter/index.html' },
|
||||
{ slug: 'timeline', label: 'Timeline Builder', group: 'Project Management', href: '../SDC Timeline Builder/index.html' },
|
||||
{ slug: 'status-report', label: 'Status Report', group: 'Project Management', href: '../SDC Status Report/index.html' },
|
||||
]
|
||||
|
||||
export interface SdcTopBarProps {
|
||||
/** App name shown top-left. */
|
||||
appName: string
|
||||
/** Slug of the current tool, marked active and non-navigating in the menu. */
|
||||
activeTool?: string
|
||||
/** Optional logo node rendered before the app name. */
|
||||
logo?: ReactNode
|
||||
/** Override the tool list (defaults to the full SDC suite). */
|
||||
tools?: SdcTool[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared top bar for the SDC tool suite: app name (left), API settings (cog), and the
|
||||
* suite app directory (grid). Wraps the ADS TopBar; uses the shared `sdc_*` credentials.
|
||||
*/
|
||||
export function SdcTopBar({ appName, activeTool, logo, tools = SDC_TOOLS }: SdcTopBarProps) {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
const [toolsOpen, setToolsOpen] = useState(false)
|
||||
const [hasCreds, setHasCreds] = useState(() => checkCredentials())
|
||||
|
||||
// Group tools preserving first-seen group order.
|
||||
const groups: { name: string; items: SdcTool[] }[] = []
|
||||
for (const t of tools) {
|
||||
let g = groups.find((x) => x.name === t.group)
|
||||
if (!g) { g = { name: t.group, items: [] }; groups.push(g) }
|
||||
g.items.push(t)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBar title={appName} logo={logo}>
|
||||
<div className="flex items-center gap-1 text-white">
|
||||
{!hasCreds && <Badge variant="warning-light">No API key</Badge>}
|
||||
|
||||
<IconButton
|
||||
icon={<Settings />}
|
||||
aria-label="API settings"
|
||||
variant="tertiary"
|
||||
className="!text-white hover:!bg-white/10"
|
||||
onClick={() => setSettingsOpen(true)}
|
||||
/>
|
||||
|
||||
<Popover placement="bottom-end" open={toolsOpen} onOpenChange={setToolsOpen}>
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
icon={<Grid3x3 />}
|
||||
aria-label="SDC AI Tools"
|
||||
variant="tertiary"
|
||||
className="!text-white hover:!bg-white/10"
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="w-64">
|
||||
<List>
|
||||
{groups.map((group) => (
|
||||
<div key={group.name}>
|
||||
<ListSubheader>{group.name}</ListSubheader>
|
||||
{group.items.map((item) => {
|
||||
const isActive = item.slug === activeTool
|
||||
return (
|
||||
<ListItem
|
||||
key={item.slug}
|
||||
active={isActive}
|
||||
href={isActive ? undefined : item.href}
|
||||
onClick={() => isActive && setToolsOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
<ListDivider />
|
||||
</div>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</TopBar>
|
||||
|
||||
<ApiSettings
|
||||
open={settingsOpen}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
onSaved={() => setHasCreds(checkCredentials())}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
2
src/components/organisms/SdcTopBar/index.ts
Normal file
2
src/components/organisms/SdcTopBar/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { SdcTopBar, SDC_TOOLS } from './SdcTopBar'
|
||||
export type { SdcTopBarProps, SdcTool } from './SdcTopBar'
|
||||
@@ -66,8 +66,8 @@ export const SideNav = forwardRef<HTMLElement, SideNavProps>(
|
||||
ref={ref}
|
||||
aria-label="Side navigation"
|
||||
className={cn(
|
||||
'flex flex-col bg-nav-bg px-2 py-2 transition-[width] duration-200',
|
||||
collapsed ? 'w-20 items-center' : 'w-[360px]',
|
||||
'flex flex-col overflow-hidden bg-nav-bg px-2 py-2 transition-[width] duration-200',
|
||||
collapsed ? 'w-20' : 'w-[360px]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -46,22 +46,7 @@ const DotsIcon = () => (
|
||||
)
|
||||
|
||||
const NswLogo = () => (
|
||||
<svg viewBox="0 0 36 35" className="size-9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M20.234 29.091c-.204-.333-.512-.636-.916-.899a4.932 4.932 0 0 0-1.629-.597l-1.886-.391c-.57-.124-.963-.285-1.168-.476-.2-.187-.302-.435-.302-.738 0-.193.044-.369.13-.523.086-.154.211-.291.371-.406.161-.115.361-.206.594-.27a2.92 2.92 0 0 1 .788-.098c.378 0 .719.05 1.013.15.291.1.532.262.717.484.184.223.297.525.336.899l.005.052h2.341l-.001-.06a3.474 3.474 0 0 0-.531-1.734 3.453 3.453 0 0 0-1.5-1.23c-.653-.302-1.466-.454-2.419-.454-.8 0-1.533.14-2.18.418-.648.278-1.169.68-1.546 1.194-.38.516-.568 1.135-.563 1.838.016.859.275 1.555.77 2.072.493.514 1.169.856 2.01 1.016l1.902.39c.35.07.666.161.942.273.27.109.486.25.642.421.151.167.228.385.228.65 0 .293-.096.542-.284.736-.193.2-.456.35-.778.449a3.527 3.527 0 0 1-1.095.15c-.387 0-.744-.06-1.06-.18a1.956 1.956 0 0 1-.784-.514 1.714 1.714 0 0 1-.42-.822l-.01-.046h-2.386l.006.064c.057.562.23 1.06.516 1.482.286.418.651.771 1.086 1.05.434.277.921.488 1.448.627a6.44 6.44 0 0 0 1.613.209c.891 0 1.682-.142 2.351-.422.671-.281 1.202-.668 1.579-1.151.38-.485.572-1.04.572-1.647 0-.287-.032-.606-.096-.949a3.178 3.178 0 0 0-.405-.948ZM3.577 26.662l5.092 7.412h2.071V22.99H8.424v7.13L3.552 23.014l-.017-.025H1.268v11.085h2.31v-7.412Zm27.123 3.417 1.984-7.09h2.27l-3.253 11.085h-1.844l-2.007-7.017-2.022 7.017H24.014L20.77 22.99h2.276l1.984 7.088 1.985-7.088h1.678l2.007 7.089Z" fill="white" />
|
||||
<path d="M16.868 20.24c-2.724-.338-5.37.542-10.027-.733-.475-.13-.65.375-.393.798 1.25 2.058 7.57.445 10.432.148.125-.013.113-.198-.012-.213Z" fill="#D7153A" />
|
||||
<path d="M28.791 19.508c-4.656 1.274-7.302.395-10.026.731-.125.016-.138.201-.013.214 2.863.297 9.182 1.91 10.432-.148.258-.423.083-.928-.393-1.06v.263Z" fill="#D7153A" />
|
||||
<path d="M8.26 16.564c-.947-1.399-1.72-2.963-2.32-4.684-1.807.531-3.657 1.3-5.54 2.303a.629.629 0 0 0-.4.65c-.007.276.13.527.366.669 3.646 2.197 7.21 3.454 10.611 3.744a7.133 7.133 0 0 1-2.718-2.682Z" fill="#D7153A" />
|
||||
<path d="M3.18 11.89c.835-.337 1.661-.625 2.48-.867a31.64 31.64 0 0 1-.724-3.011 24.71 24.71 0 0 0-2.924-.228h-.026c-.264 0-.502.133-.641.362a.637.637 0 0 0-.017.762c.586 1.065 1.204 2.059 1.852 2.982Z" fill="#D7153A" />
|
||||
<path d="M12.139 18.849c.565.251 1.143.392 1.704.421-1.373-.99-2.451-2.668-3.073-4.808-.8-2.754-1.068-5.793-.804-9.071a28.32 28.32 0 0 0-3.511-1.926.622.622 0 0 0-.746.06.636.636 0 0 0-.33.673c.294 4.757 1.511 8.747 3.617 11.86.884 1.305 1.97 2.27 3.143 2.791Z" fill="#D7153A" />
|
||||
<path d="M14.826 3.278a28.02 28.02 0 0 0-2.104-2.342.605.605 0 0 0-.531-.229.588.588 0 0 0-.515.573c-.148.718-.43 2.181-.554 3.63a27.77 27.77 0 0 1 2.68 2.033c.317-1.217.73-2.451 1.024-3.665Z" fill="#D7153A" />
|
||||
<path d="M35.23 14.183c-1.882-1.005-3.732-1.772-5.539-2.304a24.63 24.63 0 0 1-2.32 4.684 7.127 7.127 0 0 1-2.718 2.682c3.401-.29 6.966-1.548 10.611-3.744a.635.635 0 0 0 .366-.67.629.629 0 0 0-.4-.648Z" fill="#D7153A" />
|
||||
<path d="M32.451 11.889a28.137 28.137 0 0 0 1.853-2.983.637.637 0 0 0-.017-.762.618.618 0 0 0-.641-.362h-.026c-1.008.034-1.983.11-2.924.228-.19 1.048-.431 2.053-.724 3.011.819.243 1.646.531 2.48.867Z" fill="#D7153A" />
|
||||
<path d="M24.862 14.463c-.622-2.139-1.7-3.818-3.073-4.808.56.029 1.14.17 1.705.421 1.171.521 2.258 1.486 3.141 2.791 2.107 3.112 3.324 7.103 3.617 11.859a.636.636 0 0 0-.33-.673.622.622 0 0 0-.745-.06 28.382 28.382 0 0 0-3.511 1.926c.264-3.279-.003-6.318-.804-9.071v-.385Z" fill="#D7153A" />
|
||||
<path d="M24.719 4.939c-.125-1.447-.406-2.911-.554-3.63a.588.588 0 0 0-.515-.572.605.605 0 0 0-.53.229 28.145 28.145 0 0 0-2.105 2.342c.504 1.243.917 2.476 1.234 3.694a27.58 27.58 0 0 1 2.47-2.063Z" fill="#D7153A" />
|
||||
<path d="M17.813 12.128c.916-1.82 2.379-3.452 3.477-4.483a26.56 26.56 0 0 0-2.814-7.243.631.631 0 0 0-1.326 0 26.634 26.634 0 0 0-3.474 7.243c1.147 1.069 2.66 2.812 3.474 4.483h.663Z" fill="#D7153A" />
|
||||
<path d="M21.178 18.587c.95-.672 1.9-1.843 2.502-3.472 1.167-3.153 1.287-6.666 1.12-9.152-2.114 1.42-5.471 4.387-6.617 7.558-.54 1.492-.791 3.525-.375 4.66.174.474.469.833.875 1.004.616.26 1.56.063 2.495-.598Z" fill="#D7153A" />
|
||||
<path d="M17.34 13.245a11.07 11.07 0 0 0-1.066-1.973 22.052 22.052 0 0 0-5.445-5.309c-.023.369-.354 4.351.808 8.245.883 2.962 2.393 4.168 3.221 4.634.833.469 1.595.579 2.488.356-.906-1.242-.793-3.792-.006-5.954Z" fill="#D7153A" />
|
||||
</svg>
|
||||
<img src="/nsw-logo.svg" alt="NSW Government" className="h-6" />
|
||||
)
|
||||
|
||||
function IconBtn({ icon, label }: { icon: React.ReactNode; label: string }) {
|
||||
|
||||
@@ -4,14 +4,10 @@ import { AppShell } from './AppShell'
|
||||
import { TopBar } from '@/components/organisms/TopBar/TopBar'
|
||||
import { SideNav, SideNavItem, SideNavGroup, SideNavDivider } from '@/components/organisms/SideNav/SideNav'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
||||
import { PageHeader } from '@/components/organisms/PageHeader/PageHeader'
|
||||
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||
import { Menu, Search, Bell, Home, FileText, LayoutGrid, Settings, Users, Link } from 'lucide-react'
|
||||
|
||||
const NswLogo = () => (
|
||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
||||
)
|
||||
|
||||
const meta: Meta<typeof AppShell> = {
|
||||
title: 'Templates/AppShell',
|
||||
component: AppShell,
|
||||
@@ -29,44 +25,39 @@ export default meta
|
||||
|
||||
type Story = StoryObj<typeof AppShell>
|
||||
|
||||
const SampleTopBar = ({ onMenuClick }: { onMenuClick?: () => void }) => (
|
||||
<TopBar
|
||||
title="My Application"
|
||||
leading={<IconButton icon={<Menu />} aria-label="Toggle menu" variant="tertiary" onClick={onMenuClick} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<IconButton icon={<Search />} aria-label="Search" variant="tertiary" />
|
||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
||||
<Avatar initials="MM" size="sm" />
|
||||
</TopBar>
|
||||
)
|
||||
|
||||
const SampleSideNav = ({ collapsed }: { collapsed: boolean }) => (
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>My details</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavGroup icon={<Users />} label="PDP" defaultOpen>
|
||||
<SideNavItem>My PDP</SideNavItem>
|
||||
<SideNavItem>PDP guide</SideNavItem>
|
||||
<SideNavItem>Management</SideNavItem>
|
||||
</SideNavGroup>
|
||||
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
||||
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
||||
</SideNav>
|
||||
)
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={<SampleTopBar onMenuClick={() => setCollapsed(!collapsed)} />}
|
||||
sideNav={<SampleSideNav collapsed={collapsed} />}
|
||||
topBar={
|
||||
<TopBar
|
||||
title="My Application"
|
||||
leading={<TopBarAction icon={<Menu />} label="Toggle menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<TopBarAction icon={<Search />} label="Search" />
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="AB" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />} active>Home</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavGroup icon={<Users />} label="Team" defaultOpen>
|
||||
<SideNavItem>Members</SideNavItem>
|
||||
<SideNavItem>Roles</SideNavItem>
|
||||
</SideNavGroup>
|
||||
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
||||
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
||||
</SideNav>
|
||||
}
|
||||
sideNavCollapsed={collapsed}
|
||||
>
|
||||
<PageHeader title="Dashboard" subtitle="Welcome back, Myra McKay" />
|
||||
<PageHeader title="Dashboard" subtitle="Welcome back" />
|
||||
<div className="p-6">
|
||||
<div className="rounded-lg border border-border bg-surface p-8 text-center text-text-secondary">
|
||||
Page content goes here
|
||||
@@ -80,8 +71,20 @@ export const Default: Story = {
|
||||
export const Collapsed: Story = {
|
||||
render: () => (
|
||||
<AppShell
|
||||
topBar={<SampleTopBar />}
|
||||
sideNav={<SampleSideNav collapsed />}
|
||||
topBar={
|
||||
<TopBar title="My Application" leading={<TopBarAction icon={<Menu />} label="Menu" />} logo={<NswLogo />}>
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="AB" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed>
|
||||
<SideNavItem icon={<Home />} active>Home</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
||||
</SideNav>
|
||||
}
|
||||
sideNavCollapsed
|
||||
>
|
||||
<PageHeader title="Dashboard" subtitle="SideNav collapsed to icon-only mode" />
|
||||
|
||||
108
src/components/templates/CenteredPage/CenteredPage.stories.tsx
Normal file
108
src/components/templates/CenteredPage/CenteredPage.stories.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { CenteredPage } from './CenteredPage'
|
||||
import { TopBar } from '@/components/organisms/TopBar/TopBar'
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/molecules/Card/Card'
|
||||
import { Input } from '@/components/atoms/Input/Input'
|
||||
import { Button } from '@/components/atoms/Button/Button'
|
||||
import { Checkbox } from '@/components/atoms/Checkbox/Checkbox'
|
||||
import { Alert } from '@/components/molecules/Alert/Alert'
|
||||
import { NswLogo } from '@/components/templates/_story-helpers'
|
||||
|
||||
const meta: Meta<typeof CenteredPage> = {
|
||||
title: 'Templates/CenteredPage',
|
||||
component: CenteredPage,
|
||||
tags: ['autodocs', 'template'],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Full-page layout with no sidebar and horizontally/vertically centered content. Use for login, sign-up, error pages, onboarding, or any focused single-task flow.',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof CenteredPage>
|
||||
|
||||
export const Login: Story = {
|
||||
name: 'Login page',
|
||||
render: () => (
|
||||
<CenteredPage
|
||||
topBar={<TopBar title="" leading={<div className="flex size-14 items-center justify-center"><NswLogo /></div>} />}
|
||||
>
|
||||
<Card variant="elevated">
|
||||
<CardHeader>
|
||||
<CardTitle>Sign in</CardTitle>
|
||||
<CardDescription>Enter your credentials to access your account.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Input label="Email address" type="email" placeholder="you@example.com" />
|
||||
<Input label="Password" type="password" placeholder="Enter your password" />
|
||||
<div className="flex items-center justify-between">
|
||||
<Checkbox label="Remember me" />
|
||||
<a href="#" className="text-small text-info hover:underline">Forgot password?</a>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-3">
|
||||
<Button className="w-full">Sign in</Button>
|
||||
<p className="text-center text-small text-text-secondary">
|
||||
Don't have an account? <a href="#" className="text-info hover:underline">Create one</a>
|
||||
</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</CenteredPage>
|
||||
),
|
||||
}
|
||||
|
||||
export const ErrorPage: Story = {
|
||||
name: 'Error page',
|
||||
render: () => (
|
||||
<CenteredPage
|
||||
topBar={<TopBar title="" leading={<div className="flex size-14 items-center justify-center"><NswLogo /></div>} />}
|
||||
maxWidth="sm"
|
||||
>
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<p className="text-[72px] font-bold leading-none text-primary">404</p>
|
||||
<h1 className="mt-4 text-h2 font-bold text-text">Page not found</h1>
|
||||
<p className="mt-2 text-body text-text-secondary">
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<div className="mt-6 flex gap-3">
|
||||
<Button variant="secondary">Go back</Button>
|
||||
<Button>Home</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CenteredPage>
|
||||
),
|
||||
}
|
||||
|
||||
export const Onboarding: Story = {
|
||||
name: 'Onboarding step',
|
||||
render: () => (
|
||||
<CenteredPage
|
||||
topBar={<TopBar title="Getting Started" leading={<div className="flex size-14 items-center justify-center"><NswLogo /></div>} />}
|
||||
maxWidth="lg"
|
||||
>
|
||||
<Card variant="surface">
|
||||
<CardHeader>
|
||||
<CardTitle>Welcome to the platform</CardTitle>
|
||||
<CardDescription>Let's set up your workspace. This will only take a minute.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Alert variant="info" title="Step 1 of 3">
|
||||
Tell us about your organisation so we can customise your experience.
|
||||
</Alert>
|
||||
<Input label="Organisation name" placeholder="Enter your organisation name" />
|
||||
<Input label="Your role" placeholder="e.g. Manager, Coordinator" />
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full justify-between">
|
||||
<Button variant="tertiary">Skip for now</Button>
|
||||
<Button>Continue</Button>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</CenteredPage>
|
||||
),
|
||||
}
|
||||
35
src/components/templates/CenteredPage/CenteredPage.tsx
Normal file
35
src/components/templates/CenteredPage/CenteredPage.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface CenteredPageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/** TopBar component rendered fixed at the top */
|
||||
topBar?: ReactNode
|
||||
/** Horizontally and vertically centered content */
|
||||
children: ReactNode
|
||||
/** Max width of the content area */
|
||||
maxWidth?: 'sm' | 'md' | 'lg' | 'xl'
|
||||
}
|
||||
|
||||
const maxWidthStyles = {
|
||||
sm: 'max-w-md',
|
||||
md: 'max-w-xl',
|
||||
lg: 'max-w-2xl',
|
||||
xl: 'max-w-4xl',
|
||||
}
|
||||
|
||||
export const CenteredPage = forwardRef<HTMLDivElement, CenteredPageProps>(
|
||||
({ topBar, maxWidth = 'md', className, children, ...props }, ref) => {
|
||||
return (
|
||||
<div ref={ref} className={cn('flex h-screen flex-col bg-bg', className)} {...props}>
|
||||
{topBar && <div className="shrink-0">{topBar}</div>}
|
||||
|
||||
<main className="flex flex-1 items-center justify-center overflow-y-auto p-6">
|
||||
<div className={cn('w-full', maxWidthStyles[maxWidth])}>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
CenteredPage.displayName = 'CenteredPage'
|
||||
2
src/components/templates/CenteredPage/index.ts
Normal file
2
src/components/templates/CenteredPage/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { CenteredPage } from './CenteredPage'
|
||||
export type { CenteredPageProps } from './CenteredPage'
|
||||
@@ -9,12 +9,8 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/molecules
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/molecules/Accordion/Accordion'
|
||||
import { Badge } from '@/components/atoms/Badge/Badge'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, CheckCircle, Clock, Info } from 'lucide-react'
|
||||
|
||||
const NswLogo = () => (
|
||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
||||
)
|
||||
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, CheckCircle, Clock, BarChart3 } from 'lucide-react'
|
||||
|
||||
const meta: Meta<typeof DashboardPage> = {
|
||||
title: 'Templates/DashboardPage',
|
||||
@@ -33,68 +29,87 @@ export default meta
|
||||
|
||||
type Story = StoryObj<typeof DashboardPage>
|
||||
|
||||
export const ProfessionalPathway: Story = {
|
||||
name: 'Professional Pathway Dashboard',
|
||||
export const WithAppShell: Story = {
|
||||
name: 'Full page',
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={
|
||||
<TopBar
|
||||
title=""
|
||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
||||
title="Project Hub"
|
||||
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
||||
<Avatar initials="MM" size="sm" />
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="AB" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>My details</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Resources</SideNavItem>
|
||||
<SideNavItem icon={<Home />} active>Overview</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>Team</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Projects</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Reports</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavItem icon={<Users />}>Accreditation</SideNavItem>
|
||||
<SideNavItem icon={<BarChart3 />}>Analytics</SideNavItem>
|
||||
</SideNav>
|
||||
}
|
||||
sideNavCollapsed={collapsed}
|
||||
>
|
||||
<DashboardPage
|
||||
header={
|
||||
<PageHeader title="Myra McKay" subtitle="Accreditation Level: Proficient Teacher" theme="dark">
|
||||
<div className="mt-2 text-small text-white/80">
|
||||
Maroubra Junction Public School
|
||||
</div>
|
||||
</PageHeader>
|
||||
header={<PageHeader title="Overview" subtitle="Your workspace at a glance" theme="dark" />}
|
||||
stats={
|
||||
<>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><Clock size={20} /></div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">142</p>
|
||||
<p className="text-small text-text-secondary">Hours logged</p>
|
||||
<p className="text-caption text-text-secondary">Target 200h</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-success/10 text-success"><CheckCircle size={20} /></div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">24</p>
|
||||
<p className="text-small text-text-secondary">Tasks completed</p>
|
||||
<p className="text-caption text-text-secondary">This month</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><BarChart3 size={20} /></div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">8</p>
|
||||
<p className="text-small text-text-secondary">Active projects</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Card variant="surface">
|
||||
<CardHeader>
|
||||
<CardTitle>Steps to be taken</CardTitle>
|
||||
<CardTitle>Pending actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="s1">
|
||||
<AccordionTrigger>Ensure you have completed the minimum requirements of your teaching degree as stated by NESA.</AccordionTrigger>
|
||||
<AccordionContent>Details about teaching degree requirements.</AccordionContent>
|
||||
<AccordionTrigger>Complete onboarding checklist</AccordionTrigger>
|
||||
<AccordionContent>Review and complete all required onboarding items.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="s2">
|
||||
<AccordionTrigger>Apply for your Working With Children Check (WWCC).</AccordionTrigger>
|
||||
<AccordionContent>Information about WWCC application.</AccordionContent>
|
||||
<AccordionTrigger>Submit quarterly report</AccordionTrigger>
|
||||
<AccordionContent>Your Q2 report is due by end of month.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="s3">
|
||||
<AccordionTrigger>Create an eTAMS account and submit required documentation to NESA.</AccordionTrigger>
|
||||
<AccordionContent>Steps for eTAMS registration.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="s4">
|
||||
<AccordionTrigger>Pay your NESA fee.</AccordionTrigger>
|
||||
<AccordionContent>Payment details.</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="s5">
|
||||
<AccordionTrigger>Complete Mandatory Training.</AccordionTrigger>
|
||||
<AccordionContent>Training module details.</AccordionContent>
|
||||
<AccordionTrigger>Review team permissions</AccordionTrigger>
|
||||
<AccordionContent>Audit team member access levels.</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
@@ -103,29 +118,30 @@ export const ProfessionalPathway: Story = {
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card variant="surface">
|
||||
<CardHeader>
|
||||
<CardTitle>Mandatory Training Reminders</CardTitle>
|
||||
<CardTitle>Compliance status</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between border-b border-border pb-3">
|
||||
<span className="text-body font-medium">Aboriginal Cultural Education</span>
|
||||
<Badge variant="success" leftIcon={<CheckCircle size={14} />}>Certified</Badge>
|
||||
<span className="text-body font-medium">Security training</span>
|
||||
<Badge variant="success" leftIcon={<CheckCircle size={14} />}>Complete</Badge>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
<span className="text-body font-medium">Data privacy certification</span>
|
||||
<Badge variant="warning">Due soon</Badge>
|
||||
</div>
|
||||
<p className="mt-4 text-small text-text-secondary">
|
||||
Please consult the Mandatory Training Hub for role specific training, or contact the MyPL Helpdesk for queries regarding training.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card variant="elevated" className="bg-info/5">
|
||||
<CardContent className="flex gap-4 p-5">
|
||||
<Avatar initials="MK" size="lg" />
|
||||
<Avatar initials="HR" size="lg" />
|
||||
<div className="text-small">
|
||||
<p className="font-medium text-text">
|
||||
Hi I am Martha. I got my conditional accreditation recently through NESA. These links really helped me through the process.
|
||||
Welcome to the team! Here are some resources to help you get started.
|
||||
</p>
|
||||
<div className="mt-3 flex flex-col gap-1">
|
||||
<a href="#" className="text-info hover:underline">The resources that helped me</a>
|
||||
<a href="#" className="text-info hover:underline">FAQ (questions I had)</a>
|
||||
<a href="#" className="text-info hover:underline">Getting started guide</a>
|
||||
<a href="#" className="text-info hover:underline">Frequently asked questions</a>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -138,7 +154,7 @@ export const ProfessionalPathway: Story = {
|
||||
}
|
||||
|
||||
export const Standalone: Story = {
|
||||
name: 'Without AppShell',
|
||||
name: 'Content only',
|
||||
render: () => (
|
||||
<DashboardPage
|
||||
header={<PageHeader title="Dashboard" subtitle="Overview of your activity" />}
|
||||
@@ -146,36 +162,19 @@ export const Standalone: Story = {
|
||||
<>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
||||
<Clock size={20} />
|
||||
</div>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><Clock size={20} /></div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">21h</p>
|
||||
<p className="text-small text-text-secondary">Total hours logged</p>
|
||||
<p className="text-caption text-text-secondary">Target 100h</p>
|
||||
<p className="text-small text-text-secondary">Hours logged</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
||||
<Clock size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">18h</p>
|
||||
<p className="text-small text-text-secondary">NESA Registered PD</p>
|
||||
<p className="text-caption text-text-secondary">Target 60h</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
||||
<Info size={20} />
|
||||
</div>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><BarChart3 size={20} /></div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">5</p>
|
||||
<p className="text-small text-text-secondary">Activities logged</p>
|
||||
<p className="text-small text-text-secondary">Activities</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -183,10 +182,10 @@ export const Standalone: Story = {
|
||||
}
|
||||
>
|
||||
<Card variant="surface">
|
||||
<CardContent className="p-8 text-center text-text-secondary">Left column content</CardContent>
|
||||
<CardContent className="p-8 text-center text-text-secondary">Left column</CardContent>
|
||||
</Card>
|
||||
<Card variant="surface">
|
||||
<CardContent className="p-8 text-center text-text-secondary">Right column content</CardContent>
|
||||
<CardContent className="p-8 text-center text-text-secondary">Right column</CardContent>
|
||||
</Card>
|
||||
</DashboardPage>
|
||||
),
|
||||
|
||||
163
src/components/templates/DetailPage/DetailPage.stories.tsx
Normal file
163
src/components/templates/DetailPage/DetailPage.stories.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { useState } from 'react'
|
||||
import { DetailPage } from './DetailPage'
|
||||
import { AppShell } from '@/components/templates/AppShell/AppShell'
|
||||
import { TopBar } from '@/components/organisms/TopBar/TopBar'
|
||||
import { SideNav, SideNavItem, SideNavGroup, SideNavDivider } from '@/components/organisms/SideNav/SideNav'
|
||||
import { PageHeader } from '@/components/organisms/PageHeader/PageHeader'
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/molecules/Card/Card'
|
||||
import { Badge } from '@/components/atoms/Badge/Badge'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { Button } from '@/components/atoms/Button/Button'
|
||||
import { Tabs, TabList, Tab, TabPanel } from '@/components/atoms/Tabs/Tabs'
|
||||
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Settings, Mail, Phone, MapPin, Edit } from 'lucide-react'
|
||||
|
||||
const meta: Meta<typeof DetailPage> = {
|
||||
title: 'Templates/DetailPage',
|
||||
component: DetailPage,
|
||||
tags: ['autodocs', 'template'],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Detail page template for viewing a single record, profile, or document. Single-column layout with constrained max-width for readability.',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof DetailPage>
|
||||
|
||||
const InfoRow = ({ label, value, icon }: { label: string; value: string; icon: React.ReactNode }) => (
|
||||
<div className="flex items-center gap-3 py-3">
|
||||
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-info/10 text-info [&>svg]:size-4">{icon}</span>
|
||||
<div>
|
||||
<p className="text-caption text-text-secondary">{label}</p>
|
||||
<p className="text-body text-text">{value}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const ProfileView: Story = {
|
||||
name: 'Profile detail',
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState('overview')
|
||||
return (
|
||||
<AppShell
|
||||
topBar={
|
||||
<TopBar
|
||||
title="Team Directory"
|
||||
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="AB" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />}>Home</SideNavItem>
|
||||
<SideNavItem icon={<Users />} active>Team</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Projects</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavItem icon={<Settings />}>Settings</SideNavItem>
|
||||
</SideNav>
|
||||
}
|
||||
sideNavCollapsed={collapsed}
|
||||
>
|
||||
<DetailPage
|
||||
header={
|
||||
<PageHeader title="Alex Chen" subtitle="Senior Engineer — Platform Team" theme="light">
|
||||
<div className="mt-2 flex items-center gap-3">
|
||||
<Badge variant="success">Active</Badge>
|
||||
<Badge variant="info-light">Full-time</Badge>
|
||||
</div>
|
||||
</PageHeader>
|
||||
}
|
||||
actions={
|
||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||
<TabList>
|
||||
<Tab value="overview">Overview</Tab>
|
||||
<Tab value="projects">Projects</Tab>
|
||||
<Tab value="activity">Activity</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
}
|
||||
>
|
||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||
<TabPanel value="overview">
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<Card variant="surface" className="lg:col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Contact information</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="divide-y divide-border">
|
||||
<InfoRow icon={<Mail />} label="Email" value="alex.chen@example.com" />
|
||||
<InfoRow icon={<Phone />} label="Phone" value="+61 2 9876 5432" />
|
||||
<InfoRow icon={<MapPin />} label="Location" value="Sydney CBD, Level 12" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card variant="surface">
|
||||
<CardHeader>
|
||||
<CardTitle>Quick actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-3">
|
||||
<Button variant="secondary" leftIcon={<Mail size={18} />} className="w-full justify-start">Send email</Button>
|
||||
<Button variant="secondary" leftIcon={<Edit size={18} />} className="w-full justify-start">Edit profile</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="projects">
|
||||
<Card variant="surface">
|
||||
<CardContent className="p-8 text-center text-text-secondary">Project list content</CardContent>
|
||||
</Card>
|
||||
</TabPanel>
|
||||
<TabPanel value="activity">
|
||||
<Card variant="surface">
|
||||
<CardContent className="p-8 text-center text-text-secondary">Activity feed content</CardContent>
|
||||
</Card>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</DetailPage>
|
||||
</AppShell>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export const DocumentView: Story = {
|
||||
name: 'Document detail',
|
||||
render: () => (
|
||||
<DetailPage
|
||||
header={<PageHeader title="Project Brief" subtitle="Created 15 March 2024" noBackground />}
|
||||
maxWidth="lg"
|
||||
>
|
||||
<Card variant="surface">
|
||||
<CardContent className="space-y-4 p-6">
|
||||
<h2 className="text-h3 font-bold text-text">Overview</h2>
|
||||
<p className="text-body leading-relaxed text-text-secondary">
|
||||
This document outlines the scope, objectives, and timeline for the upcoming platform migration project.
|
||||
The project aims to consolidate three legacy systems into a single unified platform.
|
||||
</p>
|
||||
<h3 className="text-h4 font-bold text-text">Objectives</h3>
|
||||
<ul className="list-disc space-y-2 pl-6 text-body text-text-secondary">
|
||||
<li>Reduce operational costs by 40% through system consolidation</li>
|
||||
<li>Improve data consistency across all business units</li>
|
||||
<li>Provide a modern, accessible user interface</li>
|
||||
<li>Enable real-time reporting and analytics</li>
|
||||
</ul>
|
||||
<h3 className="text-h4 font-bold text-text">Timeline</h3>
|
||||
<p className="text-body leading-relaxed text-text-secondary">
|
||||
The project is planned across three phases over 18 months, with the first phase targeting
|
||||
core data migration and the second phase focusing on user-facing features.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</DetailPage>
|
||||
),
|
||||
}
|
||||
43
src/components/templates/DetailPage/DetailPage.tsx
Normal file
43
src/components/templates/DetailPage/DetailPage.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface DetailPageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/** PageHeader or custom header section */
|
||||
header?: ReactNode
|
||||
/** Action bar below the header (e.g. tabs, buttons) */
|
||||
actions?: ReactNode
|
||||
/** Single-column content area (max-w constrained for readability) */
|
||||
children: ReactNode
|
||||
/** Max width of the content area */
|
||||
maxWidth?: 'md' | 'lg' | 'xl' | 'full'
|
||||
}
|
||||
|
||||
const maxWidthStyles = {
|
||||
md: 'max-w-2xl',
|
||||
lg: 'max-w-3xl',
|
||||
xl: 'max-w-5xl',
|
||||
full: '',
|
||||
}
|
||||
|
||||
export const DetailPage = forwardRef<HTMLDivElement, DetailPageProps>(
|
||||
({ header, actions, maxWidth = 'xl', className, children, ...props }, ref) => {
|
||||
return (
|
||||
<div ref={ref} className={cn('flex flex-col', className)} {...props}>
|
||||
{header}
|
||||
|
||||
{actions && (
|
||||
<div className="flex items-center gap-4 border-b border-border px-6 py-3">
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 p-6">
|
||||
<div className={cn(maxWidthStyles[maxWidth])}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
DetailPage.displayName = 'DetailPage'
|
||||
2
src/components/templates/DetailPage/index.ts
Normal file
2
src/components/templates/DetailPage/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { DetailPage } from './DetailPage'
|
||||
export type { DetailPageProps } from './DetailPage'
|
||||
@@ -12,12 +12,8 @@ import { Select } from '@/components/atoms/Select/Select'
|
||||
import { Button } from '@/components/atoms/Button/Button'
|
||||
import { Badge } from '@/components/atoms/Badge/Badge'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Link, ArrowRight } from 'lucide-react'
|
||||
|
||||
const NswLogo = () => (
|
||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
||||
)
|
||||
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||
import { Menu, Bell, Home, FileText, LayoutGrid, Users, Link, ArrowRight, Settings } from 'lucide-react'
|
||||
|
||||
const meta: Meta<typeof FormPage> = {
|
||||
title: 'Templates/FormPage',
|
||||
@@ -36,35 +32,34 @@ export default meta
|
||||
|
||||
type Story = StoryObj<typeof FormPage>
|
||||
|
||||
export const PDPDetails: Story = {
|
||||
name: 'PDP Form',
|
||||
export const WithStepper: Story = {
|
||||
name: 'With stepper',
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={
|
||||
<TopBar
|
||||
title="Performance and development plan"
|
||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
||||
title="Application Portal"
|
||||
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
||||
<Avatar initials="DW" size="sm" />
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="SR" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />}>My status</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>My details</SideNavItem>
|
||||
<SideNavItem icon={<Home />}>Home</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>Profile</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavItem icon={<Link />}>Resources</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>My documents & links</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Documents</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavGroup icon={<FileText />} label="PDP" defaultOpen active>
|
||||
<SideNavItem active>My PDP</SideNavItem>
|
||||
<SideNavItem>PDP guide</SideNavItem>
|
||||
<SideNavItem>Management</SideNavItem>
|
||||
<SideNavItem>Useful links</SideNavItem>
|
||||
<SideNavGroup icon={<FileText />} label="Applications" defaultOpen active>
|
||||
<SideNavItem active>New application</SideNavItem>
|
||||
<SideNavItem>Guidelines</SideNavItem>
|
||||
<SideNavItem>History</SideNavItem>
|
||||
<SideNavItem>Support</SideNavItem>
|
||||
</SideNavGroup>
|
||||
</SideNav>
|
||||
@@ -73,10 +68,9 @@ export const PDPDetails: Story = {
|
||||
>
|
||||
<FormPage
|
||||
header={
|
||||
<PageHeader title="Siya Ram" subtitle="Role title goes here" theme="dark">
|
||||
<PageHeader title="New Application" subtitle="Submit your application for review" theme="dark">
|
||||
<div className="mt-2 flex items-center gap-4">
|
||||
<Badge variant="warning">Plan - In progress</Badge>
|
||||
<span className="text-small text-white/80">Date commenced: dd-mm-yyyy</span>
|
||||
<Badge variant="warning">In progress</Badge>
|
||||
</div>
|
||||
</PageHeader>
|
||||
}
|
||||
@@ -85,77 +79,60 @@ export const PDPDetails: Story = {
|
||||
<Select
|
||||
label=""
|
||||
variant="stacked"
|
||||
options={[{ value: '2026', label: '2026 - PDP Siya Ram' }]}
|
||||
options={[{ value: '2026', label: '2026 — Application draft' }]}
|
||||
defaultValue="2026"
|
||||
/>
|
||||
<Button variant="secondary">More actions</Button>
|
||||
</>
|
||||
}
|
||||
steps={[
|
||||
{ label: 'Your PDP details', status: 'current' },
|
||||
{ label: 'Create your PDP', status: 'upcoming' },
|
||||
{ label: 'Notify your PDP supervisor', status: 'upcoming' },
|
||||
{ label: 'Your details', status: 'current' },
|
||||
{ label: 'Supporting documents', status: 'upcoming' },
|
||||
{ label: 'Review & submit', status: 'upcoming' },
|
||||
]}
|
||||
>
|
||||
<Card variant="surface">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<div>
|
||||
<h2 className="text-h3 font-bold text-text">Welcome to your Performance and Development Plan (PDP)</h2>
|
||||
<h2 className="text-h3 font-bold text-text">Your details</h2>
|
||||
<p className="mt-2 text-body text-text-secondary">
|
||||
Once your goals are drafted and you're ready to share them, you can notify your PDP supervisor. Head to the Digital PDP page on the intranet to find key resources to help you complete your PDP.
|
||||
Provide the information below to begin your application. You can save and return at any time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Alert variant="info" title="Your PDP details">
|
||||
Fill in the details below to get started with your PDP.
|
||||
<Alert variant="info" title="Before you start">
|
||||
Make sure you have your identification documents and contact details ready.
|
||||
</Alert>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Select
|
||||
label="PDP year"
|
||||
options={[
|
||||
{ value: '2026', label: '2026' },
|
||||
{ value: '2025', label: '2025' },
|
||||
]}
|
||||
defaultValue="2026"
|
||||
/>
|
||||
<Input label="Full name" placeholder="Enter your full name" />
|
||||
<Input label="Email address" type="email" placeholder="you@example.com" />
|
||||
|
||||
<div>
|
||||
<p className="text-body font-semibold text-text">Middle leader role(s)</p>
|
||||
<p className="mb-2 text-small text-text-secondary">Some text about middle leader roles</p>
|
||||
<p className="text-body font-semibold text-text">Role information</p>
|
||||
<p className="mb-2 text-small text-text-secondary">Select the role that best describes your position.</p>
|
||||
<Select
|
||||
label="Middle leader role type (optional)"
|
||||
label="Role type"
|
||||
options={[
|
||||
{ value: 'deputy', label: 'Deputy Principal' },
|
||||
{ value: 'head', label: 'Head Teacher' },
|
||||
{ value: 'asst', label: 'Assistant Principal' },
|
||||
{ value: 'manager', label: 'Manager' },
|
||||
{ value: 'coordinator', label: 'Coordinator' },
|
||||
{ value: 'specialist', label: 'Specialist' },
|
||||
]}
|
||||
defaultValue="deputy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-body font-semibold text-text">Add your PDP supervisor's details here</p>
|
||||
<p className="text-body font-semibold text-text">Supervisor details</p>
|
||||
<p className="mb-2 text-small text-text-secondary">
|
||||
Note: if your supervisor's name does not appear when you search for them, ask them to access the Digital PDP using their credentials, then try again.
|
||||
Enter your supervisor's contact details for verification.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<Input label="PDP Supervisor's email" error="PDP Supervisor's email" defaultValue="dhoni.mahi@det.nsw.edu.au" />
|
||||
<Input label="PDP Supervisor work location" error="PDP Supervisor work location" defaultValue="Work location goes here" />
|
||||
<Input label="Supervisor email" type="email" placeholder="supervisor@example.com" />
|
||||
<Input label="Supervisor location" placeholder="Office or site name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-body font-semibold text-text">Add your school or work location.</p>
|
||||
<p className="mb-2 text-small text-text-secondary">
|
||||
If you don't work in a school, add 'Education Office' as your work location.
|
||||
</p>
|
||||
<Input label="Your school or work location" error="Your school or work location" defaultValue="Work location goes here" />
|
||||
</div>
|
||||
|
||||
<p className="text-small text-text-secondary">
|
||||
<strong>Note:</strong> As the school leader, your principal can view all the POPs in the school.
|
||||
</p>
|
||||
<Input label="Work location" placeholder="Your primary work location" />
|
||||
|
||||
<div className="flex justify-start pt-2">
|
||||
<Button rightIcon={<ArrowRight size={18} />}>Proceed</Button>
|
||||
@@ -170,7 +147,7 @@ export const PDPDetails: Story = {
|
||||
}
|
||||
|
||||
export const SimpleForm: Story = {
|
||||
name: 'Simple Form (no stepper)',
|
||||
name: 'Simple form (no stepper)',
|
||||
render: () => (
|
||||
<FormPage
|
||||
header={<PageHeader title="Create Account" subtitle="Set up your profile to get started" />}
|
||||
@@ -182,8 +159,8 @@ export const SimpleForm: Story = {
|
||||
<Select
|
||||
label="Role"
|
||||
options={[
|
||||
{ value: 'teacher', label: 'Teacher' },
|
||||
{ value: 'principal', label: 'Principal' },
|
||||
{ value: 'viewer', label: 'Viewer' },
|
||||
{ value: 'editor', label: 'Editor' },
|
||||
{ value: 'admin', label: 'Administrator' },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -10,32 +10,24 @@ import { Badge } from '@/components/atoms/Badge/Badge'
|
||||
import { Tag } from '@/components/atoms/Tag/Tag'
|
||||
import { Button } from '@/components/atoms/Button/Button'
|
||||
import { Avatar } from '@/components/atoms/Avatar/Avatar'
|
||||
import { IconButton } from '@/components/atoms/IconButton/IconButton'
|
||||
import { NswLogo, TopBarAction } from '@/components/templates/_story-helpers'
|
||||
import { Menu, Bell, Home, LayoutGrid, FileText, Users, Clock, BarChart3, Plus, Check } from 'lucide-react'
|
||||
|
||||
const NswLogo = () => (
|
||||
<div className="flex size-7 items-center justify-center rounded bg-white/20 text-caption font-bold text-white">NSW</div>
|
||||
)
|
||||
|
||||
const ActivityItem = ({ title, hours, date }: { title: string; hours: string; date: string }) => (
|
||||
const ListItem = ({ title, subtitle, status, date, tags }: { title: string; subtitle: string; status: string; date: string; tags: string[] }) => (
|
||||
<div className="flex flex-col gap-2 px-6 py-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<a href="#" className="text-body font-semibold text-info hover:underline">{title}</a>
|
||||
<div className="flex shrink-0 flex-col items-end gap-1 text-small text-text-secondary">
|
||||
<span>{hours}</span>
|
||||
<span>{date}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Tag color="navy" variant="filled" size="sm">NSW DoE</Tag>
|
||||
<Badge variant="success" leftIcon={<Check size={14} />}>Registered</Badge>
|
||||
<Tag color="blue" variant="filled" size="sm">S1</Tag>
|
||||
<Tag color="orange" variant="filled" size="sm">s4</Tag>
|
||||
<Tag color="green" variant="filled" size="sm">S6</Tag>
|
||||
<Badge variant="success" leftIcon={<Check size={14} />}>{status}</Badge>
|
||||
{tags.map((tag) => (
|
||||
<Tag key={tag} color="blue" variant="filled" size="sm">{tag}</Tag>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-small text-text-secondary">
|
||||
Lorem dolor sit amet, consectetur adipiscing elit. Donec condimentum nulla gravida pretium libero. Proin in felis consectetur, laoreet est eu, consectetur mi.
|
||||
</p>
|
||||
<p className="text-small text-text-secondary">{subtitle}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -56,74 +48,52 @@ export default meta
|
||||
|
||||
type Story = StoryObj<typeof ListPage>
|
||||
|
||||
export const PDLog: Story = {
|
||||
name: 'PD Log',
|
||||
export const WithAppShell: Story = {
|
||||
name: 'Full page',
|
||||
render: () => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
return (
|
||||
<AppShell
|
||||
topBar={
|
||||
<TopBar
|
||||
title=""
|
||||
leading={<IconButton icon={<Menu />} aria-label="Menu" variant="tertiary" onClick={() => setCollapsed(!collapsed)} />}
|
||||
title="Activity Tracker"
|
||||
leading={<TopBarAction icon={<Menu />} label="Menu" onClick={() => setCollapsed(!collapsed)} />}
|
||||
logo={<NswLogo />}
|
||||
>
|
||||
<IconButton icon={<Bell />} aria-label="Notifications" variant="tertiary" />
|
||||
<Avatar initials="JW" size="sm" />
|
||||
<TopBarAction icon={<Bell />} label="Notifications" />
|
||||
<Avatar initials="JD" size="sm" />
|
||||
</TopBar>
|
||||
}
|
||||
sideNav={
|
||||
<SideNav collapsed={collapsed}>
|
||||
<SideNavItem icon={<Home />} active>My status</SideNavItem>
|
||||
<SideNavItem icon={<Home />} active>Home</SideNavItem>
|
||||
<SideNavItem icon={<LayoutGrid />}>Workspace</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>My details</SideNavItem>
|
||||
<SideNavItem icon={<FileText />}>Reports</SideNavItem>
|
||||
<SideNavDivider />
|
||||
<SideNavItem icon={<Users />}>Accreditation</SideNavItem>
|
||||
<SideNavItem icon={<Users />}>Team</SideNavItem>
|
||||
</SideNav>
|
||||
}
|
||||
sideNavCollapsed={collapsed}
|
||||
>
|
||||
<ListPage
|
||||
header={
|
||||
<PageHeader title="Jane Williamson's Workspace" subtitle="Accreditation Level: Maintaining Proficient Teacher" theme="dark">
|
||||
<div className="mt-2 text-small text-white/80">
|
||||
Maroubra Junction Public School
|
||||
</div>
|
||||
</PageHeader>
|
||||
}
|
||||
header={<PageHeader title="Activity Log" subtitle="Track and manage your recorded activities" theme="dark" />}
|
||||
stats={
|
||||
<>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
||||
<Clock size={20} />
|
||||
</div>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><Clock size={20} /></div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">21h</p>
|
||||
<p className="text-small text-text-secondary">Total hours logged</p>
|
||||
<p className="text-h3 font-bold text-text">64h</p>
|
||||
<p className="text-small text-text-secondary">Total hours</p>
|
||||
<p className="text-caption text-text-secondary">Target 100h</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
||||
<Clock size={20} />
|
||||
</div>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info"><BarChart3 size={20} /></div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">18h</p>
|
||||
<p className="text-small text-text-secondary">NESA Registered PD</p>
|
||||
<p className="text-caption text-text-secondary">Target 60h</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card variant="surface" className="min-w-[180px] flex-1">
|
||||
<CardContent className="flex items-center gap-4 p-5">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
|
||||
<BarChart3 size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-h3 font-bold text-text">5</p>
|
||||
<p className="text-h3 font-bold text-text">12</p>
|
||||
<p className="text-small text-text-secondary">Activities logged</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -133,17 +103,17 @@ export const PDLog: Story = {
|
||||
listHeader={
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-h4 font-bold text-text">My PD Log</h2>
|
||||
<p className="text-small text-text-secondary">Log every professional learning activity — NESA Registered and school-based.</p>
|
||||
<h2 className="text-h4 font-bold text-text">Recent activities</h2>
|
||||
<p className="text-small text-text-secondary">All recorded activities for the current period.</p>
|
||||
</div>
|
||||
<Button leftIcon={<Plus size={18} />}>Add Activity</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
||||
<ActivityItem title="Trauma-informed practice" hours="8h" date="2024-02-20" />
|
||||
<ListItem title="Quarterly compliance review" subtitle="Completed annual review of team compliance requirements and documentation." status="Approved" date="2024-03-15" tags={['Compliance', 'Q1']} />
|
||||
<ListItem title="Team workshop: Data governance" subtitle="Facilitated a half-day workshop on data governance best practices." status="Approved" date="2024-03-10" tags={['Training', 'Data']} />
|
||||
<ListItem title="System migration planning" subtitle="Documented migration plan for the legacy CRM to the new platform." status="Approved" date="2024-02-28" tags={['Infrastructure', 'Planning']} />
|
||||
<ListItem title="New starter onboarding session" subtitle="Conducted orientation for three new team members joining the project." status="Approved" date="2024-02-20" tags={['Onboarding']} />
|
||||
</ListPage>
|
||||
</AppShell>
|
||||
)
|
||||
@@ -151,10 +121,10 @@ export const PDLog: Story = {
|
||||
}
|
||||
|
||||
export const Standalone: Story = {
|
||||
name: 'Without AppShell',
|
||||
name: 'Content only',
|
||||
render: () => (
|
||||
<ListPage
|
||||
header={<PageHeader title="Activity Log" subtitle="Your professional development activities" />}
|
||||
header={<PageHeader title="Activity Log" subtitle="Your recent activities" />}
|
||||
listHeader={
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-h4 font-bold text-text">Activities</h2>
|
||||
@@ -162,8 +132,8 @@ export const Standalone: Story = {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ActivityItem title="Sample activity" hours="4h" date="2024-03-15" />
|
||||
<ActivityItem title="Another activity" hours="2h" date="2024-03-10" />
|
||||
<ListItem title="Sample activity" subtitle="A brief description of this activity." status="Complete" date="2024-03-15" tags={['Example']} />
|
||||
<ListItem title="Another activity" subtitle="Another brief description." status="Complete" date="2024-03-10" tags={['Example']} />
|
||||
</ListPage>
|
||||
),
|
||||
}
|
||||
|
||||
18
src/components/templates/_story-helpers.tsx
Normal file
18
src/components/templates/_story-helpers.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { type ReactNode } from 'react'
|
||||
|
||||
export const NswLogo = () => (
|
||||
<img src="/nsw-logo.svg" alt="NSW Government" className="h-6" />
|
||||
)
|
||||
|
||||
export function TopBarAction({ icon, label, onClick }: { icon: ReactNode; label: string; onClick?: () => void }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={label}
|
||||
onClick={onClick}
|
||||
className="flex size-12 items-center justify-center rounded-full text-white/80 transition-colors hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
<span className="size-6 [&>svg]:size-full">{icon}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
160
src/index.ts
Normal file
160
src/index.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
// Utilities
|
||||
export { cn } from './lib/utils'
|
||||
|
||||
// SDC suite: shared credentials (sdc_api_key / sdc_api_endpoint)
|
||||
export {
|
||||
getApiKey,
|
||||
getEndpoint,
|
||||
saveCredentials,
|
||||
hasCredentials,
|
||||
migrateLegacyCredentials,
|
||||
testConnection,
|
||||
} from './lib/credentials'
|
||||
export type { TestConnectionResult } from './lib/credentials'
|
||||
|
||||
// Atoms
|
||||
export { Button } from './components/atoms/Button'
|
||||
export type { ButtonProps } from './components/atoms/Button'
|
||||
|
||||
export { IconButton } from './components/atoms/IconButton'
|
||||
export type { IconButtonProps } from './components/atoms/IconButton'
|
||||
|
||||
export { Input } from './components/atoms/Input'
|
||||
export type { InputProps } from './components/atoms/Input'
|
||||
|
||||
export { Textarea } from './components/atoms/Textarea'
|
||||
export type { TextareaProps } from './components/atoms/Textarea'
|
||||
|
||||
export { Select } from './components/atoms/Select'
|
||||
export type { SelectProps, SelectOption } from './components/atoms/Select'
|
||||
|
||||
export { Autocomplete } from './components/atoms/Autocomplete'
|
||||
export type { AutocompleteProps, AutocompleteOption } from './components/atoms/Autocomplete'
|
||||
|
||||
export { Checkbox } from './components/atoms/Checkbox'
|
||||
export type { CheckboxProps } from './components/atoms/Checkbox'
|
||||
|
||||
export { Radio, RadioGroup } from './components/atoms/Radio'
|
||||
export type { RadioProps, RadioGroupProps } from './components/atoms/Radio'
|
||||
|
||||
export { Switch } from './components/atoms/Switch'
|
||||
export type { SwitchProps } from './components/atoms/Switch'
|
||||
|
||||
export { Slider, RangeSlider } from './components/atoms/Slider'
|
||||
export type { SliderProps, RangeSliderProps } from './components/atoms/Slider'
|
||||
|
||||
export { FileInput } from './components/atoms/FileInput'
|
||||
export type { FileInputProps } from './components/atoms/FileInput'
|
||||
|
||||
export { Badge } from './components/atoms/Badge'
|
||||
export type { BadgeProps } from './components/atoms/Badge'
|
||||
|
||||
export { Tag } from './components/atoms/Tag'
|
||||
export type { TagProps, TagColor } from './components/atoms/Tag'
|
||||
|
||||
export { Chip } from './components/atoms/Chip'
|
||||
export type { ChipProps, ChipColor } from './components/atoms/Chip'
|
||||
|
||||
export { Tabs, TabList, Tab, TabPanel } from './components/atoms/Tabs'
|
||||
export type { TabsProps, TabListProps, TabProps, TabPanelProps } from './components/atoms/Tabs'
|
||||
|
||||
export { List, ListItem, ListSubheader, ListDivider } from './components/atoms/List'
|
||||
export type { ListProps, ListItemProps, ListSubheaderProps, ListDividerProps } from './components/atoms/List'
|
||||
|
||||
export { Avatar } from './components/atoms/Avatar'
|
||||
export type { AvatarProps } from './components/atoms/Avatar'
|
||||
|
||||
export { Tooltip } from './components/atoms/Tooltip'
|
||||
export type { TooltipProps } from './components/atoms/Tooltip'
|
||||
|
||||
// Molecules
|
||||
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './components/molecules/Card'
|
||||
export type {
|
||||
CardProps,
|
||||
CardHeaderProps,
|
||||
CardTitleProps,
|
||||
CardDescriptionProps,
|
||||
CardContentProps,
|
||||
CardFooterProps,
|
||||
} from './components/molecules/Card'
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './components/molecules/Accordion'
|
||||
export type {
|
||||
AccordionProps,
|
||||
AccordionItemProps,
|
||||
AccordionTriggerProps,
|
||||
AccordionContentProps,
|
||||
} from './components/molecules/Accordion'
|
||||
|
||||
export { Alert } from './components/molecules/Alert'
|
||||
export type { AlertProps, AlertVariant } from './components/molecules/Alert'
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
} from './components/molecules/Dialog'
|
||||
export type {
|
||||
DialogProps,
|
||||
DialogHeaderProps,
|
||||
DialogTitleProps,
|
||||
DialogDescriptionProps,
|
||||
DialogContentProps,
|
||||
DialogFooterProps,
|
||||
DialogCloseProps,
|
||||
} from './components/molecules/Dialog'
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverClose } from './components/molecules/Popover'
|
||||
export type {
|
||||
PopoverProps,
|
||||
PopoverTriggerProps,
|
||||
PopoverContentProps,
|
||||
PopoverCloseProps,
|
||||
} from './components/molecules/Popover'
|
||||
|
||||
export { DataTable } from './components/molecules/DataTable'
|
||||
export type { DataTableProps, DataTableColumn } from './components/molecules/DataTable'
|
||||
|
||||
// Organisms
|
||||
export { TopBar } from './components/organisms/TopBar'
|
||||
export type { TopBarProps } from './components/organisms/TopBar'
|
||||
|
||||
export { SdcTopBar, SDC_TOOLS } from './components/organisms/SdcTopBar'
|
||||
export type { SdcTopBarProps, SdcTool } from './components/organisms/SdcTopBar'
|
||||
|
||||
export { ApiSettings } from './components/organisms/ApiSettings'
|
||||
export type { ApiSettingsProps } from './components/organisms/ApiSettings'
|
||||
|
||||
export { SideNav, SideNavItem, SideNavGroup, SideNavDivider } from './components/organisms/SideNav'
|
||||
export type {
|
||||
SideNavProps,
|
||||
SideNavItemProps,
|
||||
SideNavGroupProps,
|
||||
SideNavDividerProps,
|
||||
} from './components/organisms/SideNav'
|
||||
|
||||
export { PageHeader } from './components/organisms/PageHeader'
|
||||
export type { PageHeaderProps } from './components/organisms/PageHeader'
|
||||
|
||||
// Templates
|
||||
export { AppShell } from './components/templates/AppShell'
|
||||
export type { AppShellProps } from './components/templates/AppShell'
|
||||
|
||||
export { DashboardPage } from './components/templates/DashboardPage'
|
||||
export type { DashboardPageProps } from './components/templates/DashboardPage'
|
||||
|
||||
export { ListPage } from './components/templates/ListPage'
|
||||
export type { ListPageProps } from './components/templates/ListPage'
|
||||
|
||||
export { FormPage } from './components/templates/FormPage'
|
||||
export type { FormPageProps, FormPageStep } from './components/templates/FormPage'
|
||||
|
||||
export { DetailPage } from './components/templates/DetailPage'
|
||||
export type { DetailPageProps } from './components/templates/DetailPage'
|
||||
|
||||
export { CenteredPage } from './components/templates/CenteredPage'
|
||||
export type { CenteredPageProps } from './components/templates/CenteredPage'
|
||||
65
src/lib/credentials.ts
Normal file
65
src/lib/credentials.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// Suite-wide API credentials, shared across all SDC tools on one origin via localStorage.
|
||||
// Enter the key once (in any tool) and every tool picks it up.
|
||||
|
||||
const LS_ENDPOINT = 'sdc_api_endpoint'
|
||||
const LS_KEY = 'sdc_api_key'
|
||||
|
||||
export function getEndpoint(): string {
|
||||
return (localStorage.getItem(LS_ENDPOINT) ?? '').replace(/\/+$/, '')
|
||||
}
|
||||
|
||||
export function getApiKey(): string {
|
||||
return localStorage.getItem(LS_KEY) ?? ''
|
||||
}
|
||||
|
||||
export function saveCredentials(endpoint: string, apiKey: string): void {
|
||||
localStorage.setItem(LS_ENDPOINT, endpoint.replace(/\/+$/, ''))
|
||||
localStorage.setItem(LS_KEY, apiKey)
|
||||
}
|
||||
|
||||
export function hasCredentials(): boolean {
|
||||
return !!(getEndpoint() && getApiKey())
|
||||
}
|
||||
|
||||
// One-time migration: if the unified key isn't set yet but a tool's old per-app key exists,
|
||||
// copy it across so existing users aren't logged out. Call once on app startup with the
|
||||
// tool's legacy localStorage key names. Returns true if a migration happened.
|
||||
export function migrateLegacyCredentials(legacyApiKeyName: string, legacyEndpointName?: string): boolean {
|
||||
if (getApiKey()) return false
|
||||
const oldKey = localStorage.getItem(legacyApiKeyName)
|
||||
if (!oldKey) return false
|
||||
const oldEndpoint = legacyEndpointName ? localStorage.getItem(legacyEndpointName) : null
|
||||
saveCredentials((oldEndpoint ?? getEndpoint()).replace(/\/+$/, ''), oldKey)
|
||||
return true
|
||||
}
|
||||
|
||||
export interface TestConnectionResult {
|
||||
models: string[]
|
||||
}
|
||||
|
||||
// Generic OpenAI-compatible credential check: lists models at <endpoint>/v1/models.
|
||||
export async function testConnection(endpoint: string, apiKey: string): Promise<TestConnectionResult> {
|
||||
const base = endpoint.replace(/\/+$/, '')
|
||||
if (!base) throw new Error('Please enter a gateway endpoint.')
|
||||
if (!apiKey) throw new Error('Please enter an API key.')
|
||||
|
||||
let res: Response
|
||||
try {
|
||||
res = await fetch(base + '/v1/models', {
|
||||
headers: { Authorization: 'Bearer ' + apiKey },
|
||||
})
|
||||
} catch {
|
||||
throw new Error('Could not reach the gateway. Check the endpoint URL and your network.')
|
||||
}
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
throw new Error('Authentication failed (HTTP ' + res.status + '). Check your API key.')
|
||||
}
|
||||
if (!res.ok) {
|
||||
throw new Error('Connection failed (HTTP ' + res.status + ').')
|
||||
}
|
||||
const data = await res.json().catch(() => ({}))
|
||||
const models: string[] = Array.isArray(data?.data)
|
||||
? data.data.map((m: { id?: string }) => m.id).filter((id: unknown): id is string => typeof id === 'string')
|
||||
: []
|
||||
return { models }
|
||||
}
|
||||
47
vite.lib.config.ts
Normal file
47
vite.lib.config.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
// Library build for distribution as an installable package (@geljic/ads3-design-system).
|
||||
// Kept separate from vite.config.ts so the Storybook + Vitest setup there stays untouched.
|
||||
// Build with: npm run build:lib
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
dts({
|
||||
tsconfigPath: './tsconfig.app.json',
|
||||
// Emit `dist/index.d.ts` (+ per-file declarations) rather than nesting under dist/src.
|
||||
entryRoot: 'src',
|
||||
include: ['src'],
|
||||
exclude: [
|
||||
'src/**/*.stories.tsx',
|
||||
'src/**/*.test.tsx',
|
||||
'src/main.tsx',
|
||||
'src/App.tsx',
|
||||
'src/**/_story-helpers*',
|
||||
],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
// Resolve ADS's 97 internal `@/...` imports at build time so they never leak to consumers.
|
||||
alias: { '@': resolve(__dirname, 'src') },
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
formats: ['es'],
|
||||
fileName: () => 'index.js',
|
||||
},
|
||||
rollupOptions: {
|
||||
// React + lucide-react are peer deps (consumer apps already have them) — externalise to avoid
|
||||
// bundling a second copy. Everything else (clsx, tailwind-merge, @floating-ui) is small and bundled.
|
||||
external: ['react', 'react-dom', 'react/jsx-runtime', 'lucide-react'],
|
||||
output: { preserveModules: false },
|
||||
},
|
||||
sourcemap: true,
|
||||
emptyOutDir: true,
|
||||
// Library output only — don't copy public/ (favicons, logos) into the package.
|
||||
copyPublicDir: false,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user