@geomak/ui 7.18.5 → 7.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/ui-auth-shell.md +48 -0
- package/.claude/commands/ui-bootstrap.md +36 -0
- package/.claude/commands/ui-find.md +12 -0
- package/.claude/commands/ui-form.md +38 -0
- package/.claude/commands/ui-lookup.md +13 -0
- package/.claude/commands/ui-modal.md +50 -0
- package/.claude/commands/ui-page.md +36 -0
- package/.claude/commands/ui-scaffold.md +73 -0
- package/.claude/commands/ui-story.md +30 -0
- package/.claude/commands/ui-table.md +38 -0
- package/.claude/skills/oxygen-ui/SKILL.md +402 -0
- package/README.md +74 -34
- package/dist/{chunk-4V4U2W7K.cjs → chunk-4WD27VQX.cjs} +2 -2
- package/dist/chunk-4WD27VQX.cjs.map +1 -0
- package/dist/{chunk-KAFJJO5O.js → chunk-ILDMP2Q2.js} +2 -2
- package/dist/chunk-ILDMP2Q2.js.map +1 -0
- package/dist/{chunk-CNUDNGJM.cjs → chunk-LY3MZYWU.cjs} +3 -3
- package/dist/chunk-LY3MZYWU.cjs.map +1 -0
- package/dist/{chunk-DXOWXLKK.js → chunk-VM2725HH.js} +3 -3
- package/dist/chunk-VM2725HH.js.map +1 -0
- package/dist/icons/index.cjs +105 -105
- package/dist/icons/index.d.cts +2 -2
- package/dist/icons/index.d.ts +2 -2
- package/dist/icons/index.js +1 -1
- package/dist/{index-BktppxXp.d.ts → index-B8P5qbWt.d.cts} +5 -5
- package/dist/{index-BktppxXp.d.cts → index-B8P5qbWt.d.ts} +5 -5
- package/dist/index.cjs +29 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +237 -237
- package/dist/index.d.ts +237 -237
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/dist/tokens/index.cjs +4 -4
- package/dist/tokens/index.d.cts +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.js +1 -1
- package/package.json +10 -3
- package/scripts/setup-claude.mjs +136 -0
- package/dist/chunk-4V4U2W7K.cjs.map +0 -1
- package/dist/chunk-CNUDNGJM.cjs.map +0 -1
- package/dist/chunk-DXOWXLKK.js.map +0 -1
- package/dist/chunk-KAFJJO5O.js.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate a complete authenticated app shell with SecureLayout, sidebar, routing, and optional Wizard tour
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Generate an authenticated app shell for: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
Parse the argument as `[AppName] [route1,route2,...]`. Examples:
|
|
8
|
+
```
|
|
9
|
+
VesOPS dashboard,vessels,settings,reports
|
|
10
|
+
MyApp dashboard,users
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
AppName defaults to `App`. Routes default to `dashboard,settings`.
|
|
14
|
+
|
|
15
|
+
## What this generates
|
|
16
|
+
|
|
17
|
+
A single `AppRoutes.tsx` file (or updates an existing one) with:
|
|
18
|
+
|
|
19
|
+
- `AppShell` with `TopBar` (brand + `ThemeSwitch`) and `sidebarSections`
|
|
20
|
+
- `SecureLayout` parent route guarding all provided routes
|
|
21
|
+
- Lazy-loaded placeholder pages for each route
|
|
22
|
+
- `useRef` arrays + icon wrapper spans for a `Wizard` onboarding tour
|
|
23
|
+
- A `Wizard` wrapping `AppShell` with `storageKey` based on the app name
|
|
24
|
+
|
|
25
|
+
## Steps
|
|
26
|
+
|
|
27
|
+
1. **Fetch the bootstrap code**: Call `get_app_bootstrap` with `router: true`
|
|
28
|
+
and the app name to get the reference implementation.
|
|
29
|
+
|
|
30
|
+
2. **Call `get_component` for `secure-layout`** to confirm current prop names
|
|
31
|
+
(isAuthenticated, fallback, loadingFallback).
|
|
32
|
+
|
|
33
|
+
3. **Generate `AppRoutes.tsx`** with:
|
|
34
|
+
- One `useRef<HTMLSpanElement>` per route for the Wizard tour
|
|
35
|
+
- `sidebarSections` array with one item per route; icon is a `<span ref={...}>` wrapping
|
|
36
|
+
an appropriate `<Icon.*>` (pick the most relevant icon per route name)
|
|
37
|
+
- `wizardSteps` array with title + description for each route
|
|
38
|
+
- `SecureLayout` wrapping all provided routes as child routes
|
|
39
|
+
- Each route uses `lazy(() => import('../pages/RouteNamePage'))` with a `Suspense` fallback
|
|
40
|
+
- `Wizard` wrapping `AppShell` with `steps={wizardSteps}` and `storageKey="{appName}-tour"`
|
|
41
|
+
|
|
42
|
+
4. **Generate stub page files** for each route under `src/pages/` (e.g. `DashboardPage.tsx`)
|
|
43
|
+
if they do not already exist. Each stub is a minimal component with a heading.
|
|
44
|
+
|
|
45
|
+
5. **Report** what was created and list:
|
|
46
|
+
- Which icons were guessed (developer may want to swap them)
|
|
47
|
+
- How to wire real authentication (what to replace the placeholder check with)
|
|
48
|
+
- How to add a route: duplicate an entry in `sidebarSections`, add a `useRef`, add a `wizardStep`, add a `<Route>`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate the full app bootstrap for a new project using OxygenUI
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Bootstrap a new app using OxygenUI: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
Parse the argument as an optional app name (e.g. `MyApp`, `VesOPS`). Defaults to `App`.
|
|
8
|
+
|
|
9
|
+
## What this generates
|
|
10
|
+
|
|
11
|
+
Four files that give a new project a working, production-ready shell:
|
|
12
|
+
|
|
13
|
+
| File | Purpose |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `src/main.tsx` | Entry point, mounts Bootstrap into the DOM |
|
|
16
|
+
| `src/Bootstrap.tsx` | Full provider stack in the correct nesting order |
|
|
17
|
+
| `src/approutes/AppRoutes.tsx` | AppShell + SecureLayout + lazy routes skeleton |
|
|
18
|
+
| `src/utils/brandTokens.ts` | Token override object for custom branding |
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
1. **Fetch the bootstrap code**: Call the `oxygen-ui` MCP tool `get_app_bootstrap`
|
|
23
|
+
with `router: true`, `reactQuery: true`, `customTheme: true`, and the app name.
|
|
24
|
+
|
|
25
|
+
2. **Check what exists**: Before writing, check if any of the four target files
|
|
26
|
+
already exist. If they do, report the conflict and ask the user how to proceed
|
|
27
|
+
rather than overwriting.
|
|
28
|
+
|
|
29
|
+
3. **Write the four files** with the content from the tool, substituting the app name
|
|
30
|
+
throughout (component names, display strings, localStorage keys).
|
|
31
|
+
|
|
32
|
+
4. **Report** what was created and list the next steps:
|
|
33
|
+
- Add real routes to `AppRoutes.tsx`
|
|
34
|
+
- Wire real authentication (replace the `localStorage.getItem('token')` check)
|
|
35
|
+
- Populate `BRAND_TOKENS` if custom branding is needed
|
|
36
|
+
- Run `yarn add @tanstack/react-query react-router-dom` if not already installed
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Search OxygenUI for components matching a description or use-case
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Search OxygenUI for components that match: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
1. Call the `oxygen-ui` MCP tool `find_component` with the argument as the query.
|
|
8
|
+
2. Present the results as a concise table: **Name** · slug · category · description.
|
|
9
|
+
3. For the top 1-2 results, briefly describe why they match and what the user
|
|
10
|
+
should look for in the docs (e.g. key props, integration patterns).
|
|
11
|
+
4. If the search finds nothing, suggest alternative queries and offer to call
|
|
12
|
+
`list_components` to browse all available entries.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate a fully-wired OxygenUI Form from a field specification
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Generate a wired OxygenUI form for: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
Parse the argument as a space-separated list of `field-name:input-type` pairs.
|
|
8
|
+
Append `?` to mark a field as optional (no required rule). Examples:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
email:text name:text role:dropdown notify:switch terms:checkbox
|
|
12
|
+
start:date end:date priority:slider tags:tags
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Supported input types: `text`, `textarea`, `password`, `number`, `email`,
|
|
16
|
+
`dropdown`, `switch`, `checkbox`, `radio`, `slider`, `date`, `daterange`,
|
|
17
|
+
`time`, `tags`, `color`, `rating`, `autocomplete`, `treeselect`
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
1. **Resolve binders**: For each field, call the `oxygen-ui` MCP tool
|
|
22
|
+
`get_form_binding` with the input type. This returns the correct binder
|
|
23
|
+
method, value type, and a paste-ready snippet.
|
|
24
|
+
|
|
25
|
+
2. **Generate the form component**: Produce a self-contained React component:
|
|
26
|
+
- `useForm` with `defaultValues` typed to match each field
|
|
27
|
+
- One `FormField` per field with the correct binder
|
|
28
|
+
- Required rules on all non-optional (`?`) fields
|
|
29
|
+
- Email pattern rule if the type is `email`
|
|
30
|
+
- A submit `Button`
|
|
31
|
+
- `onFinish` stub with a `TODO` comment
|
|
32
|
+
|
|
33
|
+
3. **Output inline**: Deliver the component as a code block. Do not write to a
|
|
34
|
+
file unless the user explicitly asks to save it.
|
|
35
|
+
|
|
36
|
+
4. **Offer next steps**: After the form, suggest:
|
|
37
|
+
- Adding the form inside a Modal (`/ui-modal form-in-modal`)
|
|
38
|
+
- Making it part of a full page (`/ui-page crud EntityName`)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Look up a specific OxygenUI component, full props API and usage examples
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Look up the OxygenUI component or hook: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
1. Call the `oxygen-ui` MCP tool `get_component` with the appropriate slug.
|
|
8
|
+
If the slug is not obvious, call `find_component` first with the argument as
|
|
9
|
+
the query, then call `get_component` with the best-matching slug.
|
|
10
|
+
2. Return the full documentation clearly formatted, props table, usage examples,
|
|
11
|
+
and any caveats or recipes that are relevant.
|
|
12
|
+
3. If the component accepts children or sub-components (e.g. `Modal.Body`),
|
|
13
|
+
highlight the composition pattern.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate the complete state + Modal setup for a named OxygenUI modal pattern
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Generate a Modal pattern for: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
Parse the argument as one of: `confirm-delete`, `form-in-modal`, `detail-view`, `alert`
|
|
8
|
+
|
|
9
|
+
## Patterns
|
|
10
|
+
|
|
11
|
+
### confirm-delete
|
|
12
|
+
State + PopConfirm-style Modal for deleting a named item. Includes:
|
|
13
|
+
- `open` + `target` state
|
|
14
|
+
- `onDelete(item)` setter
|
|
15
|
+
- `onConfirm` async handler with `useNotification` feedback
|
|
16
|
+
- Full Modal JSX with cancel/confirm buttons
|
|
17
|
+
|
|
18
|
+
### form-in-modal
|
|
19
|
+
State + Modal containing a Form. Includes:
|
|
20
|
+
- `open` + `selected` state (null = create, item = edit)
|
|
21
|
+
- `openCreate` / `openEdit(item)` / `closeModal` helpers
|
|
22
|
+
- `useEffect` to reset the form when `open` changes
|
|
23
|
+
- Form with two example fields (developer fills in actual fields)
|
|
24
|
+
- Modal with `onOk={form.submit}` wiring
|
|
25
|
+
|
|
26
|
+
### detail-view
|
|
27
|
+
Read-only Modal that displays a selected item's details. Includes:
|
|
28
|
+
- `open` + `item` state
|
|
29
|
+
- Trigger pattern
|
|
30
|
+
- Modal with a descriptive layout (label/value pairs using Typography)
|
|
31
|
+
|
|
32
|
+
### alert
|
|
33
|
+
Simple informational Modal with a single confirm button and an optional icon.
|
|
34
|
+
|
|
35
|
+
## Steps
|
|
36
|
+
|
|
37
|
+
1. **Fetch component API**: Call `get_component` with slug `modal` to confirm
|
|
38
|
+
the current prop names (onClose vs onOpenChange may differ across versions).
|
|
39
|
+
|
|
40
|
+
2. **Generate the pattern**: Produce the complete code block:
|
|
41
|
+
- State declarations (paste above the return statement)
|
|
42
|
+
- Helper functions
|
|
43
|
+
- The Modal JSX (paste into the render tree)
|
|
44
|
+
- Any supporting imports
|
|
45
|
+
|
|
46
|
+
3. **Output inline** as a clearly labelled code block with sections:
|
|
47
|
+
`// --- STATE ---`, `// --- HANDLERS ---`, `// --- JSX ---`
|
|
48
|
+
|
|
49
|
+
4. **Offer next steps**: Suggest wrapping the form-in-modal inside a full
|
|
50
|
+
CRUD page with `/ui-page crud EntityName`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scaffold a complete page component using OxygenUI patterns
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scaffold a complete page for: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
Parse the argument as `<type> [EntityName]` where type is one of:
|
|
8
|
+
`crud`, `dashboard`, `settings`, `detail`, `multi-step-form`
|
|
9
|
+
|
|
10
|
+
EntityName is optional (e.g. `crud Vessel`, `detail Order`, `dashboard`).
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Look up the pattern**: Call the `oxygen-ui` MCP tool `get_pattern` with the type
|
|
15
|
+
and entity name. This returns the full recipe: components needed, state to declare,
|
|
16
|
+
wiring steps, and a complete code example.
|
|
17
|
+
|
|
18
|
+
2. **Resolve any unfamiliar props**: If the pattern uses a component whose API you are
|
|
19
|
+
not certain about, call `get_component` with the slug before writing code.
|
|
20
|
+
|
|
21
|
+
3. **Determine the output path**
|
|
22
|
+
- Check if `src/pages/` exists; if not, check `src/views/` or `src/screens/`
|
|
23
|
+
- File name: `{EntityName}Page.tsx` (e.g. `VesselsPage.tsx`, `DashboardPage.tsx`)
|
|
24
|
+
|
|
25
|
+
4. **Write the file**: Generate the complete, self-contained page component:
|
|
26
|
+
- TypeScript interface for the entity (if applicable)
|
|
27
|
+
- All imports from `@geomak/ui`
|
|
28
|
+
- All state declarations
|
|
29
|
+
- All event handlers wired up (create, edit, delete, save, etc.)
|
|
30
|
+
- Full JSX tree matching the pattern
|
|
31
|
+
- Replace all `api.*` calls with `TODO: replace with your API call`
|
|
32
|
+
|
|
33
|
+
5. **Report** what was created and list:
|
|
34
|
+
- Which `api.*` calls need to be wired to real endpoints
|
|
35
|
+
- Which types/interfaces need to be expanded
|
|
36
|
+
- Any optional enhancements (e.g. "add Drawer instead of Modal for more context")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scaffold a new OxygenUI component, implementation + story + MDX guide
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scaffold a new OxygenUI component named: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
Parse the argument as `ComponentName [category]` where category is optional
|
|
8
|
+
(defaults to the most appropriate one based on the component name).
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. **Plan**: Determine:
|
|
13
|
+
- Target directory: `src/components/{core|inputs|forms|layout|marketing}/`
|
|
14
|
+
- Story title: `"Category/ComponentName"`
|
|
15
|
+
- Whether the component wraps a Radix primitive or is fully custom
|
|
16
|
+
|
|
17
|
+
2. **Check for overlap**, Call `find_component` to confirm no existing component
|
|
18
|
+
already covers this use case before creating anything.
|
|
19
|
+
|
|
20
|
+
3. **Scaffold files**: Create the following three files:
|
|
21
|
+
|
|
22
|
+
### `ComponentName.tsx`
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import React from 'react'
|
|
26
|
+
|
|
27
|
+
export interface ComponentNameProps {
|
|
28
|
+
// props here
|
|
29
|
+
className?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function ComponentName({ className, ...props }: ComponentNameProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div className={className}>
|
|
35
|
+
{/* implementation */}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default ComponentName
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `ComponentName.stories.tsx`
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import React from 'react'
|
|
47
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
48
|
+
import ComponentName from './ComponentName'
|
|
49
|
+
|
|
50
|
+
const meta: Meta<typeof ComponentName> = {
|
|
51
|
+
title: 'Category/ComponentName',
|
|
52
|
+
component: ComponentName,
|
|
53
|
+
parameters: { layout: 'centered' },
|
|
54
|
+
tags: ['autodocs'],
|
|
55
|
+
}
|
|
56
|
+
export default meta
|
|
57
|
+
type Story = StoryObj<typeof ComponentName>
|
|
58
|
+
|
|
59
|
+
export const Default: Story = {
|
|
60
|
+
args: {},
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `src/docs/ComponentName.mdx`
|
|
65
|
+
|
|
66
|
+
Use the MDX guide structure with HTML tables (not GFM markdown tables).
|
|
67
|
+
Use the exported `th / td / td0 / tbl` style constants from Accordion.mdx as a template.
|
|
68
|
+
|
|
69
|
+
4. **Export**, Add the new component to `src/index.ts` in the correct section
|
|
70
|
+
(Core components, Input components, etc.).
|
|
71
|
+
|
|
72
|
+
5. **Report** what was created and what the developer should fill in next
|
|
73
|
+
(implementation logic, additional stories, tests).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Add or improve Storybook stories for an existing OxygenUI component
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Add or improve Storybook stories for the OxygenUI component: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
1. **Read the component**: Find and read:
|
|
10
|
+
- The component source file (`src/components/**/$ARGUMENTS.tsx` or similar)
|
|
11
|
+
- The existing story file if one exists
|
|
12
|
+
- The MDX guide if one exists in `src/docs/`
|
|
13
|
+
|
|
14
|
+
2. **Identify gaps**: Look for:
|
|
15
|
+
- Props that have no story coverage
|
|
16
|
+
- State combinations (loading, error, empty, disabled)
|
|
17
|
+
- Controlled vs uncontrolled usage
|
|
18
|
+
- Composition patterns (compound components with sub-components)
|
|
19
|
+
- Responsive or theme-sensitive behaviour
|
|
20
|
+
|
|
21
|
+
3. **Write stories** following the CSF3 format used throughout the codebase:
|
|
22
|
+
- `Default`: the simplest working example with sensible args
|
|
23
|
+
- One story per major variant or visual state
|
|
24
|
+
- Use `render` functions for complex compositions
|
|
25
|
+
- Use `play` functions only when testing interactions (focus, type, click)
|
|
26
|
+
- Prefer realistic content that reflects actual usage in the VesOPS portal
|
|
27
|
+
|
|
28
|
+
4. **Verify** that story `title` matches the existing Storybook sidebar path.
|
|
29
|
+
|
|
30
|
+
5. **Report** which stories were added and what each one demonstrates.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate a typed OxygenUI Table component with columns, actions, and optional CRUD wiring
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Generate a typed Table for: **$ARGUMENTS**
|
|
6
|
+
|
|
7
|
+
Parse the argument as `EntityName [col:type ...]`. Column type hints are optional.
|
|
8
|
+
Examples:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Vessel name:string flag:string dwt:number status:badge actions:menu
|
|
12
|
+
Order id:number customer:string total:number status:badge
|
|
13
|
+
User
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Column types: `string`, `number`, `boolean`, `badge`, `date`, `link`, `menu`
|
|
17
|
+
- `badge` renders a `<Badge>` with tone based on the value
|
|
18
|
+
- `menu` adds an actions column with Edit and Delete buttons + PopConfirm on delete
|
|
19
|
+
- Any unlisted type defaults to a plain text renderer
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
|
|
23
|
+
1. **Fetch Table API**: Call `get_component` with slug `table` to confirm the
|
|
24
|
+
exact `TableColumn<T>` shape and any props that have changed.
|
|
25
|
+
|
|
26
|
+
2. **Generate the component**: Produce:
|
|
27
|
+
- TypeScript interface for the entity with all specified columns
|
|
28
|
+
- `TableColumn<Entity>[]` array with correct renderers for typed columns
|
|
29
|
+
- For `badge` columns: a `<Badge>` renderer with a sensible tone map
|
|
30
|
+
- For `menu` columns: Edit button + PopConfirm-wrapped Delete button, wired to
|
|
31
|
+
`onEdit` and `onDelete` callback props
|
|
32
|
+
- Table component with `hasSearch`, `pagination`, and `getRowKey={r => r.id}`
|
|
33
|
+
|
|
34
|
+
3. **Output inline** as a complete, importable component.
|
|
35
|
+
|
|
36
|
+
4. **Offer extensions**: After the output, offer to:
|
|
37
|
+
- Add a create/edit Modal + Form (`/ui-modal form-in-modal` or `/ui-page crud EntityName`)
|
|
38
|
+
- Wrap it in a full CRUD page (`/ui-page crud EntityName`)
|