@wangs-ui/skills 1.0.36

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 ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import { i as listSkills, n as updateSkills, r as addSkills, t as removeSkills } from "./src-C22mKmnQ.js";
3
+ import path from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ //#region bin.ts
6
+ var HELP_TEXT = `
7
+ \x1b[1m\x1b[36m🚀 Wangs UI Skills CLI\x1b[0m
8
+ Install, update, and manage modular AI agent skills for Wangs UI React applications.
9
+
10
+ \x1b[1mUsage:\x1b[0m
11
+ npx @wangs-ui/skills [command] [options]
12
+ wangs-ui-skills [command] [options]
13
+
14
+ \x1b[1mCommands:\x1b[0m
15
+ \x1b[36mlist\x1b[0m List all available and installed skills
16
+ \x1b[36madd\x1b[0m [skills...] Install specified skills (interactive if none provided)
17
+ \x1b[36mupdate\x1b[0m [skills...] Update installed skills to latest versions
18
+ \x1b[36mremove\x1b[0m <skills...> Remove specified skills from agent environment
19
+
20
+ \x1b[1mOptions:\x1b[0m
21
+ --target <dir> Target directory (default: current working directory)
22
+ -h, --help Show help message
23
+ -v, --version Show version
24
+
25
+ \x1b[1mExamples:\x1b[0m
26
+ npx @wangs-ui/skills list
27
+ npx @wangs-ui/skills add create-form
28
+ npx @wangs-ui/skills add wangs-ui-components data-table dialog-modal
29
+ npx @wangs-ui/skills update
30
+ npx @wangs-ui/skills remove create-form
31
+ `;
32
+ async function main() {
33
+ const args = process.argv.slice(2);
34
+ if (args.includes("--help") || args.includes("-h")) {
35
+ console.log(HELP_TEXT);
36
+ return;
37
+ }
38
+ if (args.includes("--version") || args.includes("-v")) {
39
+ console.log("1.0.0");
40
+ return;
41
+ }
42
+ const parsed = parseArgs({
43
+ args,
44
+ options: { target: { type: "string" } },
45
+ allowPositionals: true,
46
+ strict: false
47
+ });
48
+ const baseDir = parsed.values.target ? path.resolve(parsed.values.target) : process.cwd();
49
+ const { positionals } = parsed;
50
+ const command = positionals[0] || "list";
51
+ const skillArgs = positionals.slice(1);
52
+ switch (command) {
53
+ case "list":
54
+ case "ls":
55
+ listSkills(baseDir);
56
+ break;
57
+ case "add":
58
+ case "install":
59
+ case "i":
60
+ await addSkills(skillArgs, baseDir);
61
+ break;
62
+ case "update":
63
+ case "up":
64
+ updateSkills(skillArgs, baseDir);
65
+ break;
66
+ case "remove":
67
+ case "rm":
68
+ case "delete":
69
+ removeSkills(skillArgs, baseDir);
70
+ break;
71
+ default:
72
+ console.log(`\x1b[31mUnknown command: "${command}"\x1b[0m`);
73
+ console.log(HELP_TEXT);
74
+ process.exit(1);
75
+ }
76
+ }
77
+ try {
78
+ await main();
79
+ } catch (err) {
80
+ console.error("Error running @wangs-ui/skills:", err);
81
+ process.exit(1);
82
+ }
83
+ //#endregion
84
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { a as getAgentSkillDirs, c as isSkillInstalled, d as getSkillsDir, f as loadAllSkills, i as listSkills, l as removeSkill, n as updateSkills, o as getInstalledSkills, r as addSkills, s as installSkill, t as removeSkills, u as getSkill } from "./src-C22mKmnQ.js";
2
+ export { addSkills, getAgentSkillDirs, getInstalledSkills, getSkill, getSkillsDir, installSkill, isSkillInstalled, listSkills, loadAllSkills, removeSkill, removeSkills, updateSkills };
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: create-form
3
+ description: Real-world patterns for building strongly-typed forms, multipart file uploads, server validation mapping, and dirty tracking with @wangs-ui/form.
4
+ ---
5
+
6
+ # Skill: Real-World Form Workflows with `@wangs-ui/form`
7
+
8
+ Use this skill when building CRUD forms, data entry dialogs, multi-field settings pages, or multipart forms in Wangs UI applications.
9
+
10
+ ---
11
+
12
+ ## 1. MCP Inspection Step (Before Building Fields)
13
+
14
+ Before implementing specific input controls, query the MCP server to inspect their exact prop signatures:
15
+
16
+ - `get-documentation({ id: "select" })` — Check option formats (`{ label, value }` vs object).
17
+ - `get-documentation({ id: "datepicker" })` — Check `selectionMode` (`single`, `range`, `multiple`) and date format props.
18
+ - `get-documentation({ id: "multiselect" })` — Check chips display and filter behavior.
19
+ - `get-documentation({ id: "fileupload" })` — Check accepted mime types and upload handlers.
20
+
21
+ ---
22
+
23
+ ## 2. Recipe 1: Standard CRUD Form with Server Validation Mapping
24
+
25
+ Real-world forms must handle async submission, loading state, and map backend validation errors back into `<Field>` errors:
26
+
27
+ ```tsx
28
+ import React, { useState } from 'react';
29
+ import Card from '@wangs-ui/react-core/primitive/card';
30
+ import Button from '@wangs-ui/react-core/primitive/button';
31
+ import InputText from '@wangs-ui/react-core/primitive/inputtext';
32
+ import Select from '@wangs-ui/react-core/primitive/select';
33
+ import { Form, Field } from '@wangs-ui/react-core';
34
+ import { useFormControl } from '@wangs-ui/form';
35
+ import { useI18n } from '@wangs-ui/react-i18n';
36
+
37
+ interface UserFormValues {
38
+ fullName: string;
39
+ email: string;
40
+ role: string;
41
+ }
42
+
43
+ export default function UserForm({ onSuccess }: { onSuccess?: () => void }) {
44
+ const { t } = useI18n();
45
+ const formControl = useFormControl<UserFormValues>({ type: 'json' });
46
+ const [isSubmitting, setIsSubmitting] = useState(false);
47
+
48
+ const roleOptions = [
49
+ { label: t('Administrator'), value: 'admin' },
50
+ { label: t('Operator'), value: 'operator' },
51
+ { label: t('Viewer'), value: 'viewer' },
52
+ ];
53
+
54
+ const handleSubmit = async (values: UserFormValues) => {
55
+ setIsSubmitting(true);
56
+ try {
57
+ // Execute API call: await api.createUser(values);
58
+ onSuccess?.();
59
+ } catch (err: any) {
60
+ // Map server validation error directly to field
61
+ if (err?.fieldErrors?.email) {
62
+ formControl.setError('email', {
63
+ type: 'server',
64
+ message: err.fieldErrors.email,
65
+ });
66
+ }
67
+ } finally {
68
+ setIsSubmitting(false);
69
+ }
70
+ };
71
+
72
+ return (
73
+ <Card className="p-6">
74
+ <Form control={formControl} onSubmit={handleSubmit} className="flex flex-col gap-m">
75
+ <h2 className="heading-2">{t('User Information')}</h2>
76
+
77
+ {/* Text Field */}
78
+ <Field<string>
79
+ name="fullName"
80
+ label={t('Full Name')}
81
+ required
82
+ rules={{ required: t('Full name is required') }}
83
+ >
84
+ {(field) => (
85
+ <InputText {...field} placeholder={t('e.g. Jane Doe')} value={field.value || ''} />
86
+ )}
87
+ </Field>
88
+
89
+ {/* Email Field with Regex Validation */}
90
+ <Field<string>
91
+ name="email"
92
+ label={t('Email Address')}
93
+ required
94
+ rules={{
95
+ required: t('Email is required'),
96
+ pattern: {
97
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
98
+ message: t('Enter a valid email address'),
99
+ },
100
+ }}
101
+ >
102
+ {(field) => (
103
+ <InputText {...field} placeholder={t('name@company.com')} value={field.value || ''} />
104
+ )}
105
+ </Field>
106
+
107
+ {/* Select Dropdown Field */}
108
+ <Field<string>
109
+ name="role"
110
+ label={t('Access Role')}
111
+ required
112
+ rules={{ required: t('Role selection is required') }}
113
+ >
114
+ {(field) => (
115
+ <Select
116
+ {...field}
117
+ options={roleOptions}
118
+ placeholder={t('Select role')}
119
+ value={field.value}
120
+ onChange={(e) => field.onChange(e.value)}
121
+ />
122
+ )}
123
+ </Field>
124
+
125
+ {/* Form Actions */}
126
+ <div className="flex justify-end gap-s pt-s">
127
+ <Button
128
+ type="button"
129
+ variant="text"
130
+ label={t('Reset')}
131
+ onClick={() => formControl.reset()}
132
+ disabled={isSubmitting}
133
+ />
134
+ <Button type="submit" label={t('Save User')} severity="primary" loading={isSubmitting} />
135
+ </div>
136
+ </Form>
137
+ </Card>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## 3. Recipe 2: Multipart Form (File Upload + Metadata)
145
+
146
+ For file uploads (avatar, attachment, documents), set `type: 'formdata'`:
147
+
148
+ ```tsx
149
+ import { useFormControl } from '@wangs-ui/form';
150
+
151
+ interface ProfileUploadValues {
152
+ displayName: string;
153
+ avatar: File | null;
154
+ }
155
+
156
+ // Generates FormData under the hood
157
+ const formControl = useFormControl<ProfileUploadValues>({ type: 'formdata' });
158
+ ```
159
+
160
+ ---
161
+
162
+ ## 4. Key Implementation Rules
163
+
164
+ 1. **Always Use `<Form>` & `<Field>`**: Never bind raw uncontrolled inputs.
165
+ 2. **Translate All Labels & Error Messages**: Wrap text in `t('...')`.
166
+ 3. **Handle Loading State**: Disable reset buttons and set `loading={isSubmitting}` on submit buttons.
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: data-table
3
+ description: Real-world patterns for building full CRUD DataTables with server pagination, search filters, batch actions, and confirmation modals.
4
+ ---
5
+
6
+ # Skill: Real-World DataTable & Filter Workflows
7
+
8
+ Use this skill when building administrative grids, filtered listing pages, or management dashboards with `@wangs-ui/react-core`.
9
+
10
+ ---
11
+
12
+ ## 1. MCP Inspection Step (Before Building Table)
13
+
14
+ Query the MCP server to inspect supported features and slots:
15
+
16
+ - `get-documentation({ id: "datatable" })` — Check `paginator`, `lazy`, `onPage`, `onSort`, `selectionMode`, and `dataKey`.
17
+ - `get-documentation({ id: "column" })` — Check `body` template, `sortable`, `frozen`, and `headerStyle`.
18
+ - `get-documentation({ id: "tag" })` — Check severity colors for status pill badges (`success`, `warning`, `danger`, `info`).
19
+
20
+ ---
21
+
22
+ ## 2. Recipe: Full CRUD Data Grid with Filter Toolbar & Batch Actions
23
+
24
+ ```tsx
25
+ import React, { useState } from 'react';
26
+ import DataTable, { Column } from '@wangs-ui/react-core/primitive/datatable';
27
+ import Button from '@wangs-ui/react-core/primitive/button';
28
+ import InputText from '@wangs-ui/react-core/primitive/inputtext';
29
+ import Select from '@wangs-ui/react-core/primitive/select';
30
+ import Tag from '@wangs-ui/react-core/primitive/tag';
31
+ import Dialog from '@wangs-ui/react-core/primitive/dialog';
32
+ import Card from '@wangs-ui/react-core/primitive/card';
33
+ import { useI18n } from '@wangs-ui/react-i18n';
34
+ import { SearchLine, DeleteBin6Line, EditLine, AddLine } from '@wangs-ui/react-icons';
35
+
36
+ interface CustomerRecord {
37
+ id: string;
38
+ name: string;
39
+ email: string;
40
+ status: 'active' | 'pending' | 'suspended';
41
+ createdAt: string;
42
+ }
43
+
44
+ export default function CustomerManagementView() {
45
+ const { t } = useI18n();
46
+ const [records, setRecords] = useState<CustomerRecord[]>([]);
47
+ const [loading, setLoading] = useState(false);
48
+ const [searchQuery, setSearchQuery] = useState('');
49
+ const [selectedStatus, setSelectedStatus] = useState<string | null>(null);
50
+ const [selectedRows, setSelectedRows] = useState<CustomerRecord[]>([]);
51
+ const [deleteTarget, setDeleteTarget] = useState<CustomerRecord | null>(null);
52
+
53
+ // Status Badge Template
54
+ const statusTemplate = (row: CustomerRecord) => {
55
+ const severityMap: Record<string, 'success' | 'warning' | 'danger'> = {
56
+ active: 'success',
57
+ pending: 'warning',
58
+ suspended: 'danger',
59
+ };
60
+ return <Tag value={t(row.status)} severity={severityMap[row.status] || 'info'} />;
61
+ };
62
+
63
+ // Row Actions Template
64
+ const actionsTemplate = (row: CustomerRecord) => (
65
+ <div className="flex items-center gap-xs">
66
+ <Button
67
+ variant="text"
68
+ icon={<EditLine />}
69
+ aria-label={t('Edit')}
70
+ onClick={() => console.log('Edit', row.id)}
71
+ />
72
+ <Button
73
+ variant="text"
74
+ severity="danger"
75
+ icon={<DeleteBin6Line />}
76
+ aria-label={t('Delete')}
77
+ onClick={() => setDeleteTarget(row)}
78
+ />
79
+ </div>
80
+ );
81
+
82
+ return (
83
+ <Card className="flex flex-col gap-m p-6">
84
+ {/* 1. Filter & Search Toolbar */}
85
+ <div className="flex flex-wrap items-center justify-between gap-s">
86
+ <div className="flex flex-wrap items-center gap-s">
87
+ <InputText
88
+ value={searchQuery}
89
+ onChange={(e) => setSearchQuery(e.target.value)}
90
+ placeholder={t('Search customer...')}
91
+ className="w-64"
92
+ />
93
+ <Select
94
+ value={selectedStatus}
95
+ options={[
96
+ { label: t('All Statuses'), value: null },
97
+ { label: t('Active'), value: 'active' },
98
+ { label: t('Pending'), value: 'pending' },
99
+ { label: t('Suspended'), value: 'suspended' },
100
+ ]}
101
+ onChange={(e) => setSelectedStatus(e.value)}
102
+ placeholder={t('Filter status')}
103
+ />
104
+ </div>
105
+
106
+ <Button label={t('Add Customer')} icon={<AddLine />} severity="primary" />
107
+ </div>
108
+
109
+ {/* 2. Batch Selection Action Bar */}
110
+ {selectedRows.length > 0 && (
111
+ <div className="flex items-center justify-between rounded bg-primary-50 px-4 py-2 text-primary-900">
112
+ <span className="p font-medium">
113
+ {t('{{count}} items selected', { count: selectedRows.length })}
114
+ </span>
115
+ <Button
116
+ size="small"
117
+ severity="danger"
118
+ label={t('Delete Selected')}
119
+ icon={<DeleteBin6Line />}
120
+ onClick={() => console.log('Batch delete', selectedRows)}
121
+ />
122
+ </div>
123
+ )}
124
+
125
+ {/* 3. Paginated DataTable */}
126
+ <DataTable
127
+ value={records}
128
+ loading={loading}
129
+ selection={selectedRows}
130
+ onSelectionChange={(e) => setSelectedRows(e.value)}
131
+ dataKey="id"
132
+ paginator
133
+ rows={10}
134
+ rowsPerPageOptions={[10, 25, 50]}
135
+ emptyMessage={t('No customers found')}
136
+ >
137
+ <Column selectionMode="multiple" headerStyle={{ width: '3rem' }} />
138
+ <Column field="name" header={t('Customer Name')} sortable />
139
+ <Column field="email" header={t('Email')} sortable />
140
+ <Column field="status" header={t('Status')} body={statusTemplate} sortable />
141
+ <Column header={t('Actions')} body={actionsTemplate} headerStyle={{ width: '6rem' }} />
142
+ </DataTable>
143
+
144
+ {/* 4. Delete Confirmation Dialog */}
145
+ <Dialog
146
+ visible={!!deleteTarget}
147
+ onHide={() => setDeleteTarget(null)}
148
+ header={t('Delete Customer')}
149
+ footer={
150
+ <div className="flex justify-end gap-xs">
151
+ <Button variant="text" label={t('Cancel')} onClick={() => setDeleteTarget(null)} />
152
+ <Button
153
+ severity="danger"
154
+ label={t('Delete')}
155
+ onClick={() => {
156
+ // Execute delete API
157
+ setDeleteTarget(null);
158
+ }}
159
+ />
160
+ </div>
161
+ }
162
+ >
163
+ <p className="p">
164
+ {t('Are you sure you want to delete {{name}}?', { name: deleteTarget?.name })}
165
+ </p>
166
+ </Dialog>
167
+ </Card>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## 3. Mandatory Best Practices
175
+
176
+ 1. **Always Supply `dataKey`**: Never enable row selection without `dataKey="id"`.
177
+ 2. **Translate All Headers & Messages**: Pass table headers and empty state messages into `t()`.
178
+ 3. **Control Batch Action Appearance**: Show batch action banner only when `selectedRows.length > 0`.
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: dialog-modal
3
+ description: Real-world patterns for modal forms, async confirmation workflows, and multi-step dialogs with Wangs UI Dialog and Modal components.
4
+ ---
5
+
6
+ # Skill: Real-World Dialog & Modal Workflows
7
+
8
+ Use this skill when building interactive modals, create/edit modal forms, destructive action confirmations, or slide-in overlay panels.
9
+
10
+ ---
11
+
12
+ ## 1. MCP Inspection Step (Before Implementing Overlays)
13
+
14
+ Query the MCP server to check overlay configuration and animation options:
15
+
16
+ - `get-documentation({ id: "dialog" })` — Check `header`, `footer`, `visible`, `onHide`, `modal`, and `dismissableMask`.
17
+ - `get-documentation({ id: "modal" })` — Check fullscreen modes, size variants, and slide-in drawer options.
18
+ - `get-documentation({ id: "toast" })` — Check severity toasts (`success`, `error`, `info`, `warn`) to trigger after modal actions.
19
+
20
+ ---
21
+
22
+ ## 2. Recipe: Create/Edit Form inside a Modal Dialog
23
+
24
+ This 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:
25
+
26
+ ```tsx
27
+ import React, { useState, useEffect } from 'react';
28
+ import Dialog from '@wangs-ui/react-core/primitive/dialog';
29
+ import Button from '@wangs-ui/react-core/primitive/button';
30
+ import InputText from '@wangs-ui/react-core/primitive/inputtext';
31
+ import { Form, Field } from '@wangs-ui/react-core';
32
+ import { useFormControl } from '@wangs-ui/form';
33
+ import { useI18n } from '@wangs-ui/react-i18n';
34
+
35
+ interface EditItemModel {
36
+ title: string;
37
+ code: string;
38
+ }
39
+
40
+ interface ItemModalProps {
41
+ visible: boolean;
42
+ item?: EditItemModel | null;
43
+ onHide: () => void;
44
+ onSaved: (item: EditItemModel) => void;
45
+ }
46
+
47
+ export default function ItemFormModal({ visible, item, onHide, onSaved }: ItemModalProps) {
48
+ const { t } = useI18n();
49
+ const formControl = useFormControl<EditItemModel>({ type: 'json' });
50
+ const [saving, setSaving] = useState(false);
51
+
52
+ // Sync form values when modal opens or item changes
53
+ useEffect(() => {
54
+ if (visible) {
55
+ formControl.reset(item || { title: '', code: '' });
56
+ }
57
+ }, [visible, item]);
58
+
59
+ const handleFormSubmit = async (values: EditItemModel) => {
60
+ setSaving(true);
61
+ try {
62
+ // Execute API call: await api.save(values);
63
+ onSaved(values);
64
+ onHide();
65
+ } finally {
66
+ setSaving(false);
67
+ }
68
+ };
69
+
70
+ const footerActions = (
71
+ <div className="flex justify-end gap-xs">
72
+ <Button type="button" variant="text" label={t('Cancel')} onClick={onHide} disabled={saving} />
73
+ <Button
74
+ type="submit"
75
+ form="modal-item-form"
76
+ label={item ? t('Save Changes') : t('Create Item')}
77
+ severity="primary"
78
+ loading={saving}
79
+ />
80
+ </div>
81
+ );
82
+
83
+ return (
84
+ <Dialog
85
+ visible={visible}
86
+ onHide={() => !saving && onHide()}
87
+ header={item ? t('Edit Item') : t('New Item')}
88
+ footer={footerActions}
89
+ style={{ width: '450px' }}
90
+ modal
91
+ >
92
+ <Form
93
+ id="modal-item-form"
94
+ control={formControl}
95
+ onSubmit={handleFormSubmit}
96
+ className="flex flex-col gap-m pt-xs"
97
+ >
98
+ <Field<string>
99
+ name="title"
100
+ label={t('Item Title')}
101
+ required
102
+ rules={{ required: t('Title is required') }}
103
+ >
104
+ {(field) => (
105
+ <InputText {...field} placeholder={t('Enter title')} value={field.value || ''} />
106
+ )}
107
+ </Field>
108
+
109
+ <Field<string>
110
+ name="code"
111
+ label={t('Item Code')}
112
+ required
113
+ rules={{ required: t('Code is required') }}
114
+ >
115
+ {(field) => (
116
+ <InputText {...field} placeholder={t('e.g. SKU-100')} value={field.value || ''} />
117
+ )}
118
+ </Field>
119
+ </Form>
120
+ </Dialog>
121
+ );
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 3. Mandatory Best Practices
128
+
129
+ 1. **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.
130
+ 2. **Prevent Close During Mutation**: Guard `onHide={() => !saving && onHide()}` to prevent accidental dismissal during in-flight network requests.
131
+ 3. **Always Reset on Open**: Sync initial values in an effect keyed on `visible`.
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: i18n-usage
3
+ description: Real-world internationalization, currency formatting, localized date pickers, and plural interpolation with @wangs-ui/react-i18n.
4
+ ---
5
+
6
+ # Skill: Application Internationalization & Formatting
7
+
8
+ Use this skill when handling multi-language UI, currency inputs, localized dates, or dynamic sentence translations.
9
+
10
+ ---
11
+
12
+ ## 1. MCP Inspection Step (Before Localizing Complex Components)
13
+
14
+ Query the MCP server to inspect component-specific localization props:
15
+
16
+ - `get-documentation({ id: "currencyinput" })` — Check currency prefix, locale formatting, and min/max constraints.
17
+ - `get-documentation({ id: "datepicker" })` — Check month/day names, firstDayOfWeek, and dateFormat options.
18
+ - `get-documentation({ id: "languageswitcher" })` — Check language picker dropdown variants.
19
+
20
+ ---
21
+
22
+ ## 2. Recipe: Localized Currency, Date, and Pluralization Flow
23
+
24
+ ```tsx
25
+ import React, { useState } from 'react';
26
+ import Card from '@wangs-ui/react-core/primitive/card';
27
+ import CurrencyInput from '@wangs-ui/react-core/primitive/currencyinput';
28
+ import DatePicker from '@wangs-ui/react-core/primitive/datepicker';
29
+ import { useI18n } from '@wangs-ui/react-i18n';
30
+
31
+ export default function InvoiceSummary() {
32
+ const { t, currentLocale, setLocale } = useI18n();
33
+ const [amount, setAmount] = useState<number | null>(1500000);
34
+ const [dueDate, setDueDate] = useState<Date | null>(new Date());
35
+ const itemCount = 5;
36
+
37
+ return (
38
+ <Card className="flex flex-col gap-m p-6">
39
+ <div className="flex items-center justify-between">
40
+ <h2 className="heading-2">{t('Invoice Summary')}</h2>
41
+ {/* Language selector toggle */}
42
+ <button
43
+ className="text-primary-600 underline text-sm"
44
+ onClick={() => setLocale(currentLocale === 'en' ? 'id' : 'en')}
45
+ >
46
+ {currentLocale === 'en' ? 'Bahasa Indonesia' : 'English'}
47
+ </button>
48
+ </div>
49
+
50
+ {/* 1. Currency Formatting Input */}
51
+ <div className="flex flex-col gap-xs">
52
+ <label className="heading-4">{t('Total Amount')}</label>
53
+ <CurrencyInput
54
+ value={amount}
55
+ onValueChange={(e) => setAmount(e.value ?? null)}
56
+ currency={currentLocale === 'id' ? 'IDR' : 'USD'}
57
+ locale={currentLocale === 'id' ? 'id-ID' : 'en-US'}
58
+ />
59
+ </div>
60
+
61
+ {/* 2. Localized Date Picker */}
62
+ <div className="flex flex-col gap-xs">
63
+ <label className="heading-4">{t('Payment Due Date')}</label>
64
+ <DatePicker
65
+ value={dueDate}
66
+ onChange={(e) => setDueDate(e.value as Date)}
67
+ dateFormat={currentLocale === 'id' ? 'dd/mm/yy' : 'mm/dd/yy'}
68
+ showIcon
69
+ />
70
+ </div>
71
+
72
+ {/* 3. Parameterized Translation */}
73
+ <p className="p text-secondary-600">
74
+ {t('Invoice includes {{count}} billed line items.', { count: itemCount })}
75
+ </p>
76
+ </Card>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 3. Mandatory Translation Rules
84
+
85
+ 1. **Sentence Keys in Natural English**: Always write `t('Invoice Summary')` instead of artificial dotted paths like `t('invoice.summary.title')`.
86
+ 2. **Dynamic Variables in Double Braces**: Always use `t('Hello, {{name}}', { name })`.
87
+ 3. **No Concatenation**: Never write `t('Total:') + ' ' + total`. Use `t('Total: {{total}}', { total })`.