@wangs-ui/create-react-app 1.0.36 → 1.0.38
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/dist/bin.js +89 -42
- package/package.json +2 -2
- package/template/package.json +4 -4
package/dist/bin.js
CHANGED
|
@@ -1122,46 +1122,60 @@ function copyProjectTemplate(options) {
|
|
|
1122
1122
|
}
|
|
1123
1123
|
}
|
|
1124
1124
|
//#endregion
|
|
1125
|
-
//#region ../skills/dist/src-
|
|
1125
|
+
//#region ../skills/dist/src-jXDDgaDm.js
|
|
1126
|
+
var SKILL_default$5 = "---\nname: create-form\ndescription: Real-world patterns for building strongly-typed forms, multipart file uploads, server validation mapping, and dirty tracking with @wangs-ui/form.\n---\n\n# Skill: Real-World Form Workflows with `@wangs-ui/form`\n\nUse this skill when building CRUD forms, data entry dialogs, multi-field settings pages, or multipart forms in Wangs UI applications.\n\n---\n\n## 1. MCP Inspection Step (Before Building Fields)\n\nBefore implementing specific input controls, query the MCP server to inspect their exact prop signatures:\n\n- `get-documentation({ id: \"select\" })` — Check option formats (`{ label, value }` vs object).\n- `get-documentation({ id: \"datepicker\" })` — Check `selectionMode` (`single`, `range`, `multiple`) and date format props.\n- `get-documentation({ id: \"multiselect\" })` — Check chips display and filter behavior.\n- `get-documentation({ id: \"fileupload\" })` — Check accepted mime types and upload handlers.\n\n---\n\n## 2. Recipe 1: Standard CRUD Form with Server Validation Mapping\n\nReal-world forms must handle async submission, loading state, and map backend validation errors back into `<Field>` errors:\n\n```tsx\nimport React, { useState } from 'react';\nimport Card from '@wangs-ui/react-core/primitive/card';\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport InputText from '@wangs-ui/react-core/primitive/inputtext';\nimport Select from '@wangs-ui/react-core/primitive/select';\nimport { Form, Field } from '@wangs-ui/react-core';\nimport { useFormControl } from '@wangs-ui/form';\nimport { useI18n } from '@wangs-ui/react-i18n';\n\ninterface UserFormValues {\n fullName: string;\n email: string;\n role: string;\n}\n\nexport default function UserForm({ onSuccess }: { onSuccess?: () => void }) {\n const { t } = useI18n();\n const formControl = useFormControl<UserFormValues>({ type: 'json' });\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n const roleOptions = [\n { label: t('Administrator'), value: 'admin' },\n { label: t('Operator'), value: 'operator' },\n { label: t('Viewer'), value: 'viewer' },\n ];\n\n const handleSubmit = async (values: UserFormValues) => {\n setIsSubmitting(true);\n try {\n // Execute API call: await api.createUser(values);\n onSuccess?.();\n } catch (err: any) {\n // Map server validation error directly to field\n if (err?.fieldErrors?.email) {\n formControl.setError('email', {\n type: 'server',\n message: err.fieldErrors.email,\n });\n }\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <Card className=\"p-6\">\n <Form control={formControl} onSubmit={handleSubmit} className=\"flex flex-col gap-m\">\n <h2 className=\"heading-2\">{t('User Information')}</h2>\n\n {/* Text Field */}\n <Field<string>\n name=\"fullName\"\n label={t('Full Name')}\n required\n rules={{ required: t('Full name is required') }}\n >\n {(field) => (\n <InputText {...field} placeholder={t('e.g. Jane Doe')} value={field.value || ''} />\n )}\n </Field>\n\n {/* Email Field with Regex Validation */}\n <Field<string>\n name=\"email\"\n label={t('Email Address')}\n required\n rules={{\n required: t('Email is required'),\n pattern: {\n value: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,\n message: t('Enter a valid email address'),\n },\n }}\n >\n {(field) => (\n <InputText {...field} placeholder={t('name@company.com')} value={field.value || ''} />\n )}\n </Field>\n\n {/* Select Dropdown Field */}\n <Field<string>\n name=\"role\"\n label={t('Access Role')}\n required\n rules={{ required: t('Role selection is required') }}\n >\n {(field) => (\n <Select\n {...field}\n options={roleOptions}\n placeholder={t('Select role')}\n value={field.value}\n onChange={(e) => field.onChange(e.value)}\n />\n )}\n </Field>\n\n {/* Form Actions */}\n <div className=\"flex justify-end gap-s pt-s\">\n <Button\n type=\"button\"\n variant=\"text\"\n label={t('Reset')}\n onClick={() => formControl.reset()}\n disabled={isSubmitting}\n />\n <Button type=\"submit\" label={t('Save User')} severity=\"primary\" loading={isSubmitting} />\n </div>\n </Form>\n </Card>\n );\n}\n```\n\n---\n\n## 3. Recipe 2: Multipart Form (File Upload + Metadata)\n\nFor file uploads (avatar, attachment, documents), set `type: 'formdata'`:\n\n```tsx\nimport { useFormControl } from '@wangs-ui/form';\n\ninterface ProfileUploadValues {\n displayName: string;\n avatar: File | null;\n}\n\n// Generates FormData under the hood\nconst formControl = useFormControl<ProfileUploadValues>({ type: 'formdata' });\n```\n\n---\n\n## 4. Key Implementation Rules\n\n1. **Always Use `<Form>` & `<Field>`**: Never bind raw uncontrolled inputs.\n2. **Translate All Labels & Error Messages**: Wrap text in `t('...')`.\n3. **Handle Loading State**: Disable reset buttons and set `loading={isSubmitting}` on submit buttons.\n";
|
|
1127
|
+
var SKILL_default$4 = "---\nname: data-table\ndescription: Real-world patterns for building full CRUD DataTables with server pagination, search filters, batch actions, and confirmation modals.\n---\n\n# Skill: Real-World DataTable & Filter Workflows\n\nUse this skill when building administrative grids, filtered listing pages, or management dashboards with `@wangs-ui/react-core`.\n\n---\n\n## 1. MCP Inspection Step (Before Building Table)\n\nQuery the MCP server to inspect supported features and slots:\n\n- `get-documentation({ id: \"datatable\" })` — Check `paginator`, `lazy`, `onPage`, `onSort`, `selectionMode`, and `dataKey`.\n- `get-documentation({ id: \"column\" })` — Check `body` template, `sortable`, `frozen`, and `headerStyle`.\n- `get-documentation({ id: \"tag\" })` — Check severity colors for status pill badges (`success`, `warning`, `danger`, `info`).\n\n---\n\n## 2. Recipe: Full CRUD Data Grid with Filter Toolbar & Batch Actions\n\n```tsx\nimport React, { useState } from 'react';\nimport DataTable, { Column } from '@wangs-ui/react-core/primitive/datatable';\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport InputText from '@wangs-ui/react-core/primitive/inputtext';\nimport Select from '@wangs-ui/react-core/primitive/select';\nimport Tag from '@wangs-ui/react-core/primitive/tag';\nimport Dialog from '@wangs-ui/react-core/primitive/dialog';\nimport Card from '@wangs-ui/react-core/primitive/card';\nimport { useI18n } from '@wangs-ui/react-i18n';\nimport { SearchLine, DeleteBin6Line, EditLine, AddLine } from '@wangs-ui/react-icons';\n\ninterface CustomerRecord {\n id: string;\n name: string;\n email: string;\n status: 'active' | 'pending' | 'suspended';\n createdAt: string;\n}\n\nexport default function CustomerManagementView() {\n const { t } = useI18n();\n const [records, setRecords] = useState<CustomerRecord[]>([]);\n const [loading, setLoading] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedStatus, setSelectedStatus] = useState<string | null>(null);\n const [selectedRows, setSelectedRows] = useState<CustomerRecord[]>([]);\n const [deleteTarget, setDeleteTarget] = useState<CustomerRecord | null>(null);\n\n // Status Badge Template\n const statusTemplate = (row: CustomerRecord) => {\n const severityMap: Record<string, 'success' | 'warning' | 'danger'> = {\n active: 'success',\n pending: 'warning',\n suspended: 'danger',\n };\n return <Tag value={t(row.status)} severity={severityMap[row.status] || 'info'} />;\n };\n\n // Row Actions Template\n const actionsTemplate = (row: CustomerRecord) => (\n <div className=\"flex items-center gap-xs\">\n <Button\n variant=\"text\"\n icon={<EditLine />}\n aria-label={t('Edit')}\n onClick={() => console.log('Edit', row.id)}\n />\n <Button\n variant=\"text\"\n severity=\"danger\"\n icon={<DeleteBin6Line />}\n aria-label={t('Delete')}\n onClick={() => setDeleteTarget(row)}\n />\n </div>\n );\n\n return (\n <Card className=\"flex flex-col gap-m p-6\">\n {/* 1. Filter & Search Toolbar */}\n <div className=\"flex flex-wrap items-center justify-between gap-s\">\n <div className=\"flex flex-wrap items-center gap-s\">\n <InputText\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={t('Search customer...')}\n className=\"w-64\"\n />\n <Select\n value={selectedStatus}\n options={[\n { label: t('All Statuses'), value: null },\n { label: t('Active'), value: 'active' },\n { label: t('Pending'), value: 'pending' },\n { label: t('Suspended'), value: 'suspended' },\n ]}\n onChange={(e) => setSelectedStatus(e.value)}\n placeholder={t('Filter status')}\n />\n </div>\n\n <Button label={t('Add Customer')} icon={<AddLine />} severity=\"primary\" />\n </div>\n\n {/* 2. Batch Selection Action Bar */}\n {selectedRows.length > 0 && (\n <div className=\"flex items-center justify-between rounded bg-primary-50 px-4 py-2 text-primary-900\">\n <span className=\"p font-medium\">\n {t('{{count}} items selected', { count: selectedRows.length })}\n </span>\n <Button\n size=\"small\"\n severity=\"danger\"\n label={t('Delete Selected')}\n icon={<DeleteBin6Line />}\n onClick={() => console.log('Batch delete', selectedRows)}\n />\n </div>\n )}\n\n {/* 3. Paginated DataTable */}\n <DataTable\n value={records}\n loading={loading}\n selection={selectedRows}\n onSelectionChange={(e) => setSelectedRows(e.value)}\n dataKey=\"id\"\n paginator\n rows={10}\n rowsPerPageOptions={[10, 25, 50]}\n emptyMessage={t('No customers found')}\n >\n <Column selectionMode=\"multiple\" headerStyle={{ width: '3rem' }} />\n <Column field=\"name\" header={t('Customer Name')} sortable />\n <Column field=\"email\" header={t('Email')} sortable />\n <Column field=\"status\" header={t('Status')} body={statusTemplate} sortable />\n <Column header={t('Actions')} body={actionsTemplate} headerStyle={{ width: '6rem' }} />\n </DataTable>\n\n {/* 4. Delete Confirmation Dialog */}\n <Dialog\n visible={!!deleteTarget}\n onHide={() => setDeleteTarget(null)}\n header={t('Delete Customer')}\n footer={\n <div className=\"flex justify-end gap-xs\">\n <Button variant=\"text\" label={t('Cancel')} onClick={() => setDeleteTarget(null)} />\n <Button\n severity=\"danger\"\n label={t('Delete')}\n onClick={() => {\n // Execute delete API\n setDeleteTarget(null);\n }}\n />\n </div>\n }\n >\n <p className=\"p\">\n {t('Are you sure you want to delete {{name}}?', { name: deleteTarget?.name })}\n </p>\n </Dialog>\n </Card>\n );\n}\n```\n\n---\n\n## 3. Mandatory Best Practices\n\n1. **Always Supply `dataKey`**: Never enable row selection without `dataKey=\"id\"`.\n2. **Translate All Headers & Messages**: Pass table headers and empty state messages into `t()`.\n3. **Control Batch Action Appearance**: Show batch action banner only when `selectedRows.length > 0`.\n";
|
|
1128
|
+
var SKILL_default$3 = "---\nname: dialog-modal\ndescription: Real-world patterns for modal forms, async confirmation workflows, and multi-step dialogs with Wangs UI Dialog and Modal components.\n---\n\n# Skill: Real-World Dialog & Modal Workflows\n\nUse this skill when building interactive modals, create/edit modal forms, destructive action confirmations, or slide-in overlay panels.\n\n---\n\n## 1. MCP Inspection Step (Before Implementing Overlays)\n\nQuery the MCP server to check overlay configuration and animation options:\n\n- `get-documentation({ id: \"dialog\" })` — Check `header`, `footer`, `visible`, `onHide`, `modal`, and `dismissableMask`.\n- `get-documentation({ id: \"modal\" })` — Check fullscreen modes, size variants, and slide-in drawer options.\n- `get-documentation({ id: \"toast\" })` — Check severity toasts (`success`, `error`, `info`, `warn`) to trigger after modal actions.\n\n---\n\n## 2. Recipe: Create/Edit Form inside a Modal Dialog\n\nThis real-world recipe coordinates a modal wrapper with an embedded `@wangs-ui/form`, handles saving state, prevents accidental dismiss while saving, and resets state upon close:\n\n```tsx\nimport React, { useState, useEffect } from 'react';\nimport Dialog from '@wangs-ui/react-core/primitive/dialog';\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport InputText from '@wangs-ui/react-core/primitive/inputtext';\nimport { Form, Field } from '@wangs-ui/react-core';\nimport { useFormControl } from '@wangs-ui/form';\nimport { useI18n } from '@wangs-ui/react-i18n';\n\ninterface EditItemModel {\n title: string;\n code: string;\n}\n\ninterface ItemModalProps {\n visible: boolean;\n item?: EditItemModel | null;\n onHide: () => void;\n onSaved: (item: EditItemModel) => void;\n}\n\nexport default function ItemFormModal({ visible, item, onHide, onSaved }: ItemModalProps) {\n const { t } = useI18n();\n const formControl = useFormControl<EditItemModel>({ type: 'json' });\n const [saving, setSaving] = useState(false);\n\n // Sync form values when modal opens or item changes\n useEffect(() => {\n if (visible) {\n formControl.reset(item || { title: '', code: '' });\n }\n }, [visible, item]);\n\n const handleFormSubmit = async (values: EditItemModel) => {\n setSaving(true);\n try {\n // Execute API call: await api.save(values);\n onSaved(values);\n onHide();\n } finally {\n setSaving(false);\n }\n };\n\n const footerActions = (\n <div className=\"flex justify-end gap-xs\">\n <Button type=\"button\" variant=\"text\" label={t('Cancel')} onClick={onHide} disabled={saving} />\n <Button\n type=\"submit\"\n form=\"modal-item-form\"\n label={item ? t('Save Changes') : t('Create Item')}\n severity=\"primary\"\n loading={saving}\n />\n </div>\n );\n\n return (\n <Dialog\n visible={visible}\n onHide={() => !saving && onHide()}\n header={item ? t('Edit Item') : t('New Item')}\n footer={footerActions}\n style={{ width: '450px' }}\n modal\n >\n <Form\n id=\"modal-item-form\"\n control={formControl}\n onSubmit={handleFormSubmit}\n className=\"flex flex-col gap-m pt-xs\"\n >\n <Field<string>\n name=\"title\"\n label={t('Item Title')}\n required\n rules={{ required: t('Title is required') }}\n >\n {(field) => (\n <InputText {...field} placeholder={t('Enter title')} value={field.value || ''} />\n )}\n </Field>\n\n <Field<string>\n name=\"code\"\n label={t('Item Code')}\n required\n rules={{ required: t('Code is required') }}\n >\n {(field) => (\n <InputText {...field} placeholder={t('e.g. SKU-100')} value={field.value || ''} />\n )}\n </Field>\n </Form>\n </Dialog>\n );\n}\n```\n\n---\n\n## 3. Mandatory Best Practices\n\n1. **Decouple Submit Button from Form Body**: Use `form=\"modal-item-form\"` on the submit button inside `footer` so actions stay neatly aligned in the footer bar.\n2. **Prevent Close During Mutation**: Guard `onHide={() => !saving && onHide()}` to prevent accidental dismissal during in-flight network requests.\n3. **Always Reset on Open**: Sync initial values in an effect keyed on `visible`.\n";
|
|
1129
|
+
var SKILL_default$2 = "---\nname: i18n-usage\ndescription: Real-world internationalization, currency formatting, localized date pickers, and plural interpolation with @wangs-ui/react-i18n.\n---\n\n# Skill: Application Internationalization & Formatting\n\nUse this skill when handling multi-language UI, currency inputs, localized dates, or dynamic sentence translations.\n\n---\n\n## 1. MCP Inspection Step (Before Localizing Complex Components)\n\nQuery the MCP server to inspect component-specific localization props:\n\n- `get-documentation({ id: \"currencyinput\" })` — Check currency prefix, locale formatting, and min/max constraints.\n- `get-documentation({ id: \"datepicker\" })` — Check month/day names, firstDayOfWeek, and dateFormat options.\n- `get-documentation({ id: \"languageswitcher\" })` — Check language picker dropdown variants.\n\n---\n\n## 2. Recipe: Localized Currency, Date, and Pluralization Flow\n\n```tsx\nimport React, { useState } from 'react';\nimport Card from '@wangs-ui/react-core/primitive/card';\nimport CurrencyInput from '@wangs-ui/react-core/primitive/currencyinput';\nimport DatePicker from '@wangs-ui/react-core/primitive/datepicker';\nimport { useI18n } from '@wangs-ui/react-i18n';\n\nexport default function InvoiceSummary() {\n const { t, currentLocale, setLocale } = useI18n();\n const [amount, setAmount] = useState<number | null>(1500000);\n const [dueDate, setDueDate] = useState<Date | null>(new Date());\n const itemCount = 5;\n\n return (\n <Card className=\"flex flex-col gap-m p-6\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"heading-2\">{t('Invoice Summary')}</h2>\n {/* Language selector toggle */}\n <button\n className=\"text-primary-600 underline text-sm\"\n onClick={() => setLocale(currentLocale === 'en' ? 'id' : 'en')}\n >\n {currentLocale === 'en' ? 'Bahasa Indonesia' : 'English'}\n </button>\n </div>\n\n {/* 1. Currency Formatting Input */}\n <div className=\"flex flex-col gap-xs\">\n <label className=\"heading-4\">{t('Total Amount')}</label>\n <CurrencyInput\n value={amount}\n onValueChange={(e) => setAmount(e.value ?? null)}\n currency={currentLocale === 'id' ? 'IDR' : 'USD'}\n locale={currentLocale === 'id' ? 'id-ID' : 'en-US'}\n />\n </div>\n\n {/* 2. Localized Date Picker */}\n <div className=\"flex flex-col gap-xs\">\n <label className=\"heading-4\">{t('Payment Due Date')}</label>\n <DatePicker\n value={dueDate}\n onChange={(e) => setDueDate(e.value as Date)}\n dateFormat={currentLocale === 'id' ? 'dd/mm/yy' : 'mm/dd/yy'}\n showIcon\n />\n </div>\n\n {/* 3. Parameterized Translation */}\n <p className=\"p text-secondary-600\">\n {t('Invoice includes {{count}} billed line items.', { count: itemCount })}\n </p>\n </Card>\n );\n}\n```\n\n---\n\n## 3. Mandatory Translation Rules\n\n1. **Sentence Keys in Natural English**: Always write `t('Invoice Summary')` instead of artificial dotted paths like `t('invoice.summary.title')`.\n2. **Dynamic Variables in Double Braces**: Always use `t('Hello, {{name}}', { name })`.\n3. **No Concatenation**: Never write `t('Total:') + ' ' + total`. Use `t('Total: {{total}}', { total })`.\n";
|
|
1130
|
+
var SKILL_default$1 = "---\nname: layout-navigation\ndescription: Guidelines and patterns for page layout, sidebar navigation, breadcrumbs, and tabs using Wangs UI layout blocks.\n---\n\n# Skill: Layout & Navigation Structure\n\nUse this skill when constructing application shells, multi-level sidebars, page headers, breadcrumbs, or tabbed views with `@wangs-ui/react-core`.\n\n---\n\n## 1. App Shell Pattern\n\n```tsx\nimport React from 'react';\nimport AppLayout from '@wangs-ui/react-core/blocks/applayout';\nimport Sidebar from '@wangs-ui/react-core/blocks/sidebar';\nimport Breadcrumb from '@wangs-ui/react-core/primitive/breadcrumb';\nimport { HomeLine, UserLine, SettingsLine } from '@wangs-ui/react-icons';\nimport { useI18n } from '@wangs-ui/react-i18n';\n\nexport default function MainAppShell({ children }: { children: React.ReactNode }) {\n const { t } = useI18n();\n\n const navigationItems = [\n { label: t('Dashboard'), icon: <HomeLine />, href: '/dashboard' },\n { label: t('Users'), icon: <UserLine />, href: '/users' },\n { label: t('Settings'), icon: <SettingsLine />, href: '/settings' },\n ];\n\n return (\n <AppLayout\n sidebar={<Sidebar items={navigationItems} />}\n header={\n <header className=\"flex h-14 items-center justify-between border-b border-secondary-200 px-6\">\n <Breadcrumb model={[{ label: t('Home') }, { label: t('Dashboard') }]} />\n </header>\n }\n >\n <main className=\"p-6\">{children}</main>\n </AppLayout>\n );\n}\n```\n\n---\n\n## 2. Best Practices\n\n1. **Page Title & Breadcrumb Alignment**:\n Every page view inside the layout should provide clear `.heading-1` hierarchy and synchronized breadcrumbs.\n2. **Spacing Grid Consistency**:\n Use consistent outer container padding (`p-6` / `p-xxl`) across views.\n3. **Tabbed Subviews**:\n When separating complex forms or detail views, use `<Tabs>` component with controlled tab index.\n\n---\n\n## 3. MCP Navigation & Block Inspection\n\nTo inspect complete navigation options, badge counters, collapsible sidebars, or responsive header controls:\n\n- Call MCP tool `get-documentation({ id: \"sidebar\" })` or `get-documentation({ id: \"breadcrumb\" })` to view full configuration options and live stories.\n";
|
|
1131
|
+
var SKILL_default = "---\nname: wangs-ui-components\ndescription: Foundational rules, subpath imports, design tokens, and the MCP Discovery Protocol for building React apps with Wangs UI.\n---\n\n# Skill: Wangs UI Component Fundamentals & MCP Protocol\n\nUse this skill whenever you write or modify UI components using Wangs UI (`@wangs-ui/react-core`, `@wangs-ui/react-icons`, `@wangs-ui/react-presets`).\n\n---\n\n## 1. The MCP Discovery Protocol (Mandatory Before Writing Code)\n\nDo **NOT** guess component props, Pass-Through (`pt`) slots, or event names. Follow this discovery protocol:\n\n```mermaid\ngraph TD\n A[Identify Component Needed] --> B[Call get-documentation id]\n B --> C{Need live story / variant?}\n C -->|Yes| D[Call get-documentation-for-story]\n C -->|No| E[Check Graphify: query_graph]\n D --> E\n E --> F[Implement Component with Subpath Imports]\n```\n\n1. **Step 1: Inspect Props & Types**:\n Call `get-documentation({ id: \"<component-name>\" })` (e.g. `button`, `inputtext`, `datatable`) to get the exact prop interfaces, severity variants, and sizes.\n2. **Step 2: Inspect Live Usage & Slots**:\n Call `get-documentation-for-story({ id: \"<component-name>\", storyName: \"<variant>\" })` to view how props, icons, and pass-through (`pt`) classes are composed in real code.\n3. **Step 3: Inspect Codebase Relationships**:\n Call `query_graph({ query: \"<ComponentName>\" })` to see how other parts of the monorepo compose this component.\n\n---\n\n## 2. Subpath Modular Imports (Mandatory)\n\nAlways import via specific subpaths to guarantee tree-shaking and avoid bundling entire packages:\n\n```tsx\n// Primitives\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport Card from '@wangs-ui/react-core/primitive/card';\nimport InputText from '@wangs-ui/react-core/primitive/inputtext';\nimport Select from '@wangs-ui/react-core/primitive/select';\nimport Tag from '@wangs-ui/react-core/primitive/tag';\n\n// Providers & Hooks\nimport { WangsUiProvider } from '@wangs-ui/react-core/api';\nimport { useI18n } from '@wangs-ui/react-i18n';\n\n// Icons\nimport { SearchLine, AddLine, DeleteBin6Line, CheckLine } from '@wangs-ui/react-icons';\n```\n\n---\n\n## 3. Strict Primitive Substitution Rule\n\nNever write raw HTML when a Wangs UI primitive exists:\n\n| Forbidden Raw HTML | Mandatory Wangs UI Component | Subpath Import |\n| :------------------------ | :--------------------------- | :------------------------------------------- |\n| `<button>` | `Button` | `@wangs-ui/react-core/primitive/button` |\n| `<input type=\"text\">` | `InputText` | `@wangs-ui/react-core/primitive/inputtext` |\n| `<input type=\"number\">` | `InputNumber` | `@wangs-ui/react-core/primitive/inputnumber` |\n| `<input type=\"checkbox\">` | `Checkbox` | `@wangs-ui/react-core/primitive/checkbox` |\n| `<select>` | `Select` | `@wangs-ui/react-core/primitive/select` |\n| `<dialog>` / modal | `Dialog` / `Modal` | `@wangs-ui/react-core/primitive/dialog` |\n| `<table>` | `DataTable` | `@wangs-ui/react-core/primitive/datatable` |\n| Container box | `Card` | `@wangs-ui/react-core/primitive/card` |\n| Pill badge | `Tag` / `Badge` | `@wangs-ui/react-core/primitive/tag` |\n\n---\n\n## 4. Typography Scale & 4px Spacing Tokens\n\n### Typography Helper Classes\n\n- `.heading-1` — Page title (22px, 600)\n- `.heading-2` — Section / Card title (18px, 600)\n- `.heading-3` — Sub-header (16px, 500)\n- `.heading-4` — Field label (14px, 500)\n- `.heading-5` — Small group header (12px, 600)\n- `.p` — Body copy (12px, 500)\n\n### 4px Spacing Tokens\n\n- Gap: `gap-xs` (4px), `gap-s` (6px), `gap-md` (8px), `gap-m` (12px), `gap-l` (16px), `gap-xl` (20px), `gap-xxl` (24px)\n- Padding: `p-xs`, `p-s`, `p-md`, `p-m`, `p-l`, `p-xl`, `p-xxl`\n";
|
|
1126
1132
|
var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
|
|
1127
|
-
|
|
1133
|
+
var EMBEDDED_SKILLS_RAW = {
|
|
1134
|
+
"create-form": SKILL_default$5,
|
|
1135
|
+
"data-table": SKILL_default$4,
|
|
1136
|
+
"dialog-modal": SKILL_default$3,
|
|
1137
|
+
"i18n-usage": SKILL_default$2,
|
|
1138
|
+
"layout-navigation": SKILL_default$1,
|
|
1139
|
+
"wangs-ui-components": SKILL_default
|
|
1140
|
+
};
|
|
1141
|
+
function parseSkillContent(id, content) {
|
|
1142
|
+
let name = id;
|
|
1143
|
+
let description = "Wangs UI consumer skill";
|
|
1144
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
1145
|
+
if (frontmatterMatch) {
|
|
1146
|
+
const fm = frontmatterMatch[1];
|
|
1147
|
+
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
1148
|
+
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
1149
|
+
if (nameMatch) name = nameMatch[1].trim();
|
|
1150
|
+
if (descMatch) description = descMatch[1].trim();
|
|
1151
|
+
}
|
|
1152
|
+
return {
|
|
1153
|
+
id,
|
|
1154
|
+
name,
|
|
1155
|
+
description,
|
|
1156
|
+
content
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function loadAllSkills() {
|
|
1160
|
+
if (Object.keys(EMBEDDED_SKILLS_RAW).length > 0) return Object.entries(EMBEDDED_SKILLS_RAW).map(([id, content]) => parseSkillContent(id, content));
|
|
1128
1161
|
const candidates = [
|
|
1129
1162
|
path.resolve(__dirname$1, "skills"),
|
|
1130
1163
|
path.resolve(__dirname$1, "../skills"),
|
|
1131
1164
|
path.resolve(__dirname$1, "../../skills")
|
|
1132
1165
|
];
|
|
1133
|
-
for (const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
for (const entry of entries) if (entry.isDirectory()) {
|
|
1142
|
-
const skillMdPath = path.join(skillsDir, entry.name, "SKILL.md");
|
|
1143
|
-
if (fs.existsSync(skillMdPath)) {
|
|
1144
|
-
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
1145
|
-
const { name: entryName } = entry;
|
|
1146
|
-
let name = entryName;
|
|
1147
|
-
let description = "Wangs UI consumer skill";
|
|
1148
|
-
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
1149
|
-
if (frontmatterMatch) {
|
|
1150
|
-
const fm = frontmatterMatch[1];
|
|
1151
|
-
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
1152
|
-
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
1153
|
-
if (nameMatch) name = nameMatch[1].trim();
|
|
1154
|
-
if (descMatch) description = descMatch[1].trim();
|
|
1166
|
+
for (const skillsDir of candidates) if (fs.existsSync(skillsDir)) {
|
|
1167
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
1168
|
+
const skills = [];
|
|
1169
|
+
for (const entry of entries) if (entry.isDirectory()) {
|
|
1170
|
+
const skillMdPath = path.join(skillsDir, entry.name, "SKILL.md");
|
|
1171
|
+
if (fs.existsSync(skillMdPath)) {
|
|
1172
|
+
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
1173
|
+
skills.push(parseSkillContent(entry.name, content));
|
|
1155
1174
|
}
|
|
1156
|
-
skills.push({
|
|
1157
|
-
id: entry.name,
|
|
1158
|
-
name,
|
|
1159
|
-
description,
|
|
1160
|
-
content
|
|
1161
|
-
});
|
|
1162
1175
|
}
|
|
1176
|
+
if (skills.length > 0) return skills;
|
|
1163
1177
|
}
|
|
1164
|
-
return
|
|
1178
|
+
return [];
|
|
1165
1179
|
}
|
|
1166
1180
|
function getAgentSkillDirs(baseDir = process.cwd()) {
|
|
1167
1181
|
const dirs = [];
|
|
@@ -1190,7 +1204,7 @@ function installSkill(skill, baseDir = process.cwd()) {
|
|
|
1190
1204
|
//#endregion
|
|
1191
1205
|
//#region src/utils/mcpInstaller.ts
|
|
1192
1206
|
function setupAgentConfigurations(options) {
|
|
1193
|
-
const { targetDir, templateDir, agents } = options;
|
|
1207
|
+
const { targetDir, templateDir, agents, skills } = options;
|
|
1194
1208
|
const configuredAgents = [];
|
|
1195
1209
|
const agentsTemplateDir = path.join(templateDir, "agents");
|
|
1196
1210
|
const agentsMdSrc = path.join(agentsTemplateDir, "AGENTS.md");
|
|
@@ -1234,8 +1248,9 @@ function setupAgentConfigurations(options) {
|
|
|
1234
1248
|
configuredAgents.push("Antigravity IDE & CLI (.agents/mcp_config.json)");
|
|
1235
1249
|
}
|
|
1236
1250
|
const allSkills = loadAllSkills();
|
|
1237
|
-
|
|
1238
|
-
|
|
1251
|
+
const selectedSkills = skills !== void 0 ? allSkills.filter((s) => skills.includes(s.id)) : allSkills;
|
|
1252
|
+
for (const skill of selectedSkills) installSkill(skill, targetDir);
|
|
1253
|
+
if (selectedSkills.length > 0) configuredAgents.push(`Wangs UI Consumer Skills (${selectedSkills.map((s) => s.id).join(", ")})`);
|
|
1239
1254
|
return configuredAgents;
|
|
1240
1255
|
}
|
|
1241
1256
|
//#endregion
|
|
@@ -1270,7 +1285,7 @@ function getRunCommand(pm, script) {
|
|
|
1270
1285
|
//#region src/index.ts
|
|
1271
1286
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1272
1287
|
function generateProject(options) {
|
|
1273
|
-
const { projectName, targetDir, preset, agents } = options;
|
|
1288
|
+
const { projectName, targetDir, preset, agents, skills } = options;
|
|
1274
1289
|
let templateDir = path.resolve(__dirname, "../template");
|
|
1275
1290
|
if (!fs.existsSync(templateDir)) templateDir = path.resolve(__dirname, "template");
|
|
1276
1291
|
const s = spinner();
|
|
@@ -1290,7 +1305,8 @@ function generateProject(options) {
|
|
|
1290
1305
|
if (agents.length > 0) configuredAgents = setupAgentConfigurations({
|
|
1291
1306
|
targetDir,
|
|
1292
1307
|
templateDir,
|
|
1293
|
-
agents
|
|
1308
|
+
agents,
|
|
1309
|
+
skills
|
|
1294
1310
|
});
|
|
1295
1311
|
s.stop("Project structure initialized.");
|
|
1296
1312
|
const pm = detectPackageManager();
|
|
@@ -1306,7 +1322,8 @@ function generateProject(options) {
|
|
|
1306
1322
|
//#endregion
|
|
1307
1323
|
//#region src/prompts.ts
|
|
1308
1324
|
async function promptUser(initialOptions) {
|
|
1309
|
-
let { projectName, preset, agents } = initialOptions;
|
|
1325
|
+
let { projectName, preset, agents, skills } = initialOptions;
|
|
1326
|
+
const allSkills = loadAllSkills();
|
|
1310
1327
|
if (initialOptions.yes) return {
|
|
1311
1328
|
projectName: projectName || "my-wangs-app",
|
|
1312
1329
|
preset: "fixedasset",
|
|
@@ -1315,9 +1332,10 @@ async function promptUser(initialOptions) {
|
|
|
1315
1332
|
"opencode",
|
|
1316
1333
|
"claude",
|
|
1317
1334
|
"kilo"
|
|
1318
|
-
]
|
|
1335
|
+
],
|
|
1336
|
+
skills: skills || allSkills.map((s) => s.id)
|
|
1319
1337
|
};
|
|
1320
|
-
intro(
|
|
1338
|
+
intro(`\x1b[36m🚀 Wangs UI React App Scaffolder\x1b[0m [2m(v1.0.38)[0m`);
|
|
1321
1339
|
if (!projectName) {
|
|
1322
1340
|
const nameResponse = await text({
|
|
1323
1341
|
message: "What is your project name?",
|
|
@@ -1390,10 +1408,28 @@ async function promptUser(initialOptions) {
|
|
|
1390
1408
|
}
|
|
1391
1409
|
agents = agentsResponse;
|
|
1392
1410
|
}
|
|
1411
|
+
if (!skills && agents.length > 0 && allSkills.length > 0) {
|
|
1412
|
+
const skillsResponse = await multiselect({
|
|
1413
|
+
message: "Select Wangs UI AI Skills to install: (Space to select/deselect, Enter to submit)",
|
|
1414
|
+
options: allSkills.map((s) => ({
|
|
1415
|
+
value: s.id,
|
|
1416
|
+
label: s.id,
|
|
1417
|
+
hint: s.description
|
|
1418
|
+
})),
|
|
1419
|
+
initialValues: allSkills.map((s) => s.id),
|
|
1420
|
+
required: false
|
|
1421
|
+
});
|
|
1422
|
+
if (isCancel(skillsResponse)) {
|
|
1423
|
+
cancel("Project scaffolding cancelled.");
|
|
1424
|
+
process$1.exit(0);
|
|
1425
|
+
}
|
|
1426
|
+
skills = skillsResponse;
|
|
1427
|
+
}
|
|
1393
1428
|
return {
|
|
1394
1429
|
projectName,
|
|
1395
1430
|
preset: "fixedasset",
|
|
1396
|
-
agents
|
|
1431
|
+
agents,
|
|
1432
|
+
skills: skills || allSkills.map((s) => s.id)
|
|
1397
1433
|
};
|
|
1398
1434
|
}
|
|
1399
1435
|
//#endregion
|
|
@@ -1440,15 +1476,26 @@ async function main() {
|
|
|
1440
1476
|
"claude",
|
|
1441
1477
|
"antigravity"
|
|
1442
1478
|
].includes(a));
|
|
1479
|
+
} else if (arg.startsWith("--skill=") || arg.startsWith("--skills=")) {
|
|
1480
|
+
const val = arg.split("=")[1];
|
|
1481
|
+
if (val === "all") options.skills = void 0;
|
|
1482
|
+
else if (val === "none") options.skills = [];
|
|
1483
|
+
else options.skills = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1484
|
+
} else if ((arg === "--skill" || arg === "--skills") && args[i + 1]) {
|
|
1485
|
+
const val = args[++i];
|
|
1486
|
+
if (val === "all") options.skills = void 0;
|
|
1487
|
+
else if (val === "none") options.skills = [];
|
|
1488
|
+
else options.skills = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1443
1489
|
} else if (!arg.startsWith("-") && !options.projectName) options.projectName = arg;
|
|
1444
1490
|
}
|
|
1445
|
-
const { projectName, preset, agents } = await promptUser(options);
|
|
1491
|
+
const { projectName, preset, agents, skills } = await promptUser(options);
|
|
1446
1492
|
const targetDir = path.resolve(process$1.cwd(), projectName);
|
|
1447
1493
|
generateProject({
|
|
1448
1494
|
projectName: path.basename(targetDir),
|
|
1449
1495
|
targetDir,
|
|
1450
1496
|
preset,
|
|
1451
|
-
agents
|
|
1497
|
+
agents,
|
|
1498
|
+
skills
|
|
1452
1499
|
});
|
|
1453
1500
|
}
|
|
1454
1501
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wangs-ui/create-react-app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.38",
|
|
4
4
|
"description": "Scaffold a modern React app with Wangs UI, Vite 8, Oxlint, Oxfmt, and AI Agent MCP integration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antigravity",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@clack/prompts": "^1.7.0",
|
|
54
|
-
"@wangs-ui/skills": "1.0.
|
|
54
|
+
"@wangs-ui/skills": "1.0.38"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {},
|
|
57
57
|
"scripts": {
|
package/template/package.json
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"format:check": "oxfmt --check"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@wangs-ui/react-core": "^1.0.
|
|
17
|
-
"@wangs-ui/react-i18n": "^1.0.
|
|
18
|
-
"@wangs-ui/react-icons": "^1.0.
|
|
19
|
-
"@wangs-ui/react-presets": "^1.0.
|
|
16
|
+
"@wangs-ui/react-core": "^1.0.38",
|
|
17
|
+
"@wangs-ui/react-i18n": "^1.0.38",
|
|
18
|
+
"@wangs-ui/react-icons": "^1.0.38",
|
|
19
|
+
"@wangs-ui/react-presets": "^1.0.38",
|
|
20
20
|
"clsx": "^2.1.1",
|
|
21
21
|
"react": "^19.2.7",
|
|
22
22
|
"react-dom": "^19.2.7",
|