noecosystem-design 0.3.0 → 0.4.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.
@@ -58,7 +58,7 @@ const preview: Preview = {
58
58
  data-locale={meta.locale}
59
59
  style={{
60
60
  minHeight: '100vh',
61
- padding: '2rem',
61
+ padding: context.parameters.layout === 'fullscreen' ? 0 : '2rem',
62
62
  background: 'var(--noe-color-background)',
63
63
  color: 'var(--noe-color-foreground)',
64
64
  }}
@@ -1,7 +1,22 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
3
  import { AppFrame } from '../../../../packages/ui/src/app-frame';
4
- import { ChartSquare, ReceiptSearch, WalletMoney } from '../../../../packages/icons/src';
4
+ import { Breadcrumb, PageHeader } from '../../../../packages/ui/src/page-header';
5
+ import { Button } from '../../../../packages/ui/src/button';
6
+ import { HeaderNavigation } from '../../../../packages/ui/src/header-navigation';
7
+ import { Input } from '../../../../packages/ui/src/input';
8
+ import { Select } from '../../../../packages/ui/src/select';
9
+ import { DataTable, type DataColumn } from '../../../../packages/data-ui/src/data-table';
10
+ import { FilterBar } from '../../../../packages/data-ui/src/filter-bar';
11
+ import {
12
+ Add,
13
+ BoxSearch,
14
+ ChartSquare,
15
+ ReceiptSearch,
16
+ Setting2,
17
+ WalletMoney,
18
+ } from '../../../../packages/icons/src';
19
+ import { NocfoLogo } from '../../../../packages/icons/src/nocfo-logo';
5
20
  import { SidebarNavigation } from '../../../../packages/ui/src/sidebar-navigation';
6
21
 
7
22
  const navigation = (
@@ -17,6 +32,7 @@ const navigation = (
17
32
  ],
18
33
  },
19
34
  ]}
35
+ wrapLabels
20
36
  />
21
37
  );
22
38
 
@@ -75,6 +91,196 @@ function FinanceAppFrameDemo() {
75
91
  );
76
92
  }
77
93
 
94
+ type BankAccountRow = {
95
+ id: string;
96
+ bank: string;
97
+ company: string;
98
+ country: string;
99
+ branch: string;
100
+ code: string;
101
+ accountNumber: string;
102
+ iban: string;
103
+ };
104
+
105
+ const bankAccountColumns: DataColumn<BankAccountRow>[] = [
106
+ { key: 'bank', header: 'بانک / Bank', sortable: true },
107
+ { key: 'company', header: 'شرکت مالک / Owner Company', sortable: true },
108
+ { key: 'country', header: 'کشور / Country', sortable: true },
109
+ { key: 'branch', header: 'شعبه / Branch', sortable: true },
110
+ { key: 'code', header: 'کد حساب / Account Code', technical: true },
111
+ { key: 'accountNumber', header: 'شماره حساب / Account Number', technical: true },
112
+ { key: 'iban', header: 'شبا / IBAN', technical: true },
113
+ ];
114
+
115
+ const bankAccountRows: BankAccountRow[] = [
116
+ {
117
+ id: '1',
118
+ bank: 'Emirates NBD',
119
+ company: 'بازرگانی امین برتر اقتصاد ملل',
120
+ country: 'امارات متحده عربی',
121
+ branch: 'Deira Branch',
122
+ code: 'ACC-AMIN-AED',
123
+ accountNumber: 'AE-ENBD-AED-1000006',
124
+ iban: 'AE5700001234567',
125
+ },
126
+ {
127
+ id: '2',
128
+ bank: 'Emirates NBD',
129
+ company: 'بازرگانی امین برتر اقتصاد ملل',
130
+ country: 'امارات متحده عربی',
131
+ branch: 'Deira Branch',
132
+ code: 'ACC-AMIN-USD',
133
+ accountNumber: 'AE-ENBD-USD-1000004',
134
+ iban: 'AE5600001234567',
135
+ },
136
+ {
137
+ id: '3',
138
+ bank: 'Ziraat Bankası',
139
+ company: 'ارمغان صنعت میلا',
140
+ country: 'ترکیه',
141
+ branch: 'Kadıköy Branch',
142
+ code: 'ARMAGHAN-EUR',
143
+ accountNumber: 'TR-ZIR-EUR-1000005',
144
+ iban: 'TR7600012345678',
145
+ },
146
+ ];
147
+
148
+ const financeNavigation = (
149
+ <SidebarNavigation
150
+ className="text-sm"
151
+ label="ناوبری NOCFO"
152
+ sections={[
153
+ {
154
+ id: 'overview',
155
+ items: [{ href: '#overview', icon: <ChartSquare />, label: 'نمای کلی / Overview' }],
156
+ },
157
+ {
158
+ id: 'settings',
159
+ label: 'تنظیمات / Settings',
160
+ items: [
161
+ { href: '#companies', label: 'شرکت‌ها / Companies' },
162
+ { href: '#countries', label: 'کشورها / Countries' },
163
+ { href: '#banks', label: 'بانک‌ها / Banks' },
164
+ { href: '#accounts', label: 'حساب‌های بانکی / Bank Accounts', current: true },
165
+ { href: '#currencies', label: 'ارزها / Currencies' },
166
+ { href: '#transaction-types', label: 'نوع تراکنش / Transaction Types' },
167
+ { href: '#transaction-natures', label: 'ماهیت تراکنش‌ها / Transaction Natures' },
168
+ { href: '#descriptions', label: 'شرح‌های استاندارد / Standard Descriptions' },
169
+ { href: '#rates', label: 'نرخ تسعیر / Exchange Rates' },
170
+ ],
171
+ },
172
+ {
173
+ id: 'operations',
174
+ items: [
175
+ { href: '#operations', icon: <ReceiptSearch />, label: 'عملیات / Operations' },
176
+ { href: '#security', icon: <Setting2 />, label: 'امنیت / Security' },
177
+ { href: '#modules', icon: <WalletMoney />, label: 'زیرسیستم‌ها / Modules' },
178
+ ],
179
+ },
180
+ ]}
181
+ />
182
+ );
183
+
184
+ function NocfoBankAccountsWorkspace() {
185
+ return (
186
+ <div data-theme="dark" dir="rtl" lang="fa" style={{ height: '100dvh' }}>
187
+ <AppFrame
188
+ className="h-full min-h-0 overflow-hidden"
189
+ header={
190
+ <HeaderNavigation
191
+ actions={
192
+ <>
193
+ <Button size="sm" variant="outline">
194
+ دوشنبه ۱۰ شهریور ۱۴۰۵
195
+ </Button>
196
+ <Select aria-label="سال مالی" defaultValue="1405" className="h-9">
197
+ <option value="1405">سال مالی ۱۴۰۵–۱۴۰۶</option>
198
+ </Select>
199
+ <span className="hidden text-xs text-muted-foreground md:inline">
200
+ fse.prepare@example.com / مدیر
201
+ </span>
202
+ </>
203
+ }
204
+ brand={<NocfoLogo style={{ inlineSize: '6.25rem' }} />}
205
+ className="bg-surface-sunken"
206
+ label="نوار ابزار NOCFO"
207
+ />
208
+ }
209
+ mobileNavigation={financeNavigation}
210
+ navigation={financeNavigation}
211
+ variant="finance"
212
+ >
213
+ <div className="flex min-w-0 flex-col gap-3">
214
+ <PageHeader
215
+ actions={
216
+ <Button variant="secondary">
217
+ <Add />
218
+ افزودن حساب بانکی / Add Bank Account
219
+ </Button>
220
+ }
221
+ breadcrumb={
222
+ <Breadcrumb
223
+ items={[
224
+ { label: 'بانکداری شرکتی', href: '#corporate-banking' },
225
+ { label: 'تنظیمات', href: '#settings' },
226
+ { label: 'حساب‌های بانکی / Bank Accounts', current: true },
227
+ ]}
228
+ />
229
+ }
230
+ title="حساب‌های بانکی / Bank Accounts"
231
+ />
232
+ <FilterBar
233
+ filters={
234
+ <div className="grid w-full grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-4">
235
+ {[
236
+ 'شرکت مالک / Owner Company',
237
+ 'کشور',
238
+ 'بانک',
239
+ 'شعبه',
240
+ 'ارز پایه / Base Currency',
241
+ 'نوع حساب / Account Type',
242
+ 'کارگزار / عامل',
243
+ ].map((label) => (
244
+ <Select aria-label={label} key={label} defaultValue="">
245
+ <option value="">{label}</option>
246
+ </Select>
247
+ ))}
248
+ </div>
249
+ }
250
+ label="فیلتر حساب‌های بانکی"
251
+ search={
252
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
253
+ <Input aria-label="جستجوی شماره حساب" placeholder="جستجوی شماره حساب" />
254
+ <Input aria-label="جستجوی شبا" placeholder="جستجوی IBAN / شبا" />
255
+ </div>
256
+ }
257
+ />
258
+ <section
259
+ className="min-w-0 rounded-lg bg-surface p-3"
260
+ aria-label="ابزار و جدول حساب‌های بانکی"
261
+ >
262
+ <div className="mb-3 flex items-center gap-2">
263
+ <BoxSearch className="size-4 text-muted-foreground" />
264
+ <Input
265
+ aria-label="جستجو در حساب‌های بانکی"
266
+ className="ms-auto max-w-sm"
267
+ placeholder="جستجو در حساب‌های بانکی / Search accounts"
268
+ />
269
+ </div>
270
+ <DataTable
271
+ caption="حساب‌های بانکی"
272
+ columns={bankAccountColumns}
273
+ density="compact"
274
+ empty={<span>حساب بانکی پیدا نشد</span>}
275
+ rows={bankAccountRows}
276
+ />
277
+ </section>
278
+ </div>
279
+ </AppFrame>
280
+ </div>
281
+ );
282
+ }
283
+
78
284
  const meta = {
79
285
  title: 'Catalog/Patterns/Pages/App Frame',
80
286
  component: AppFrameDemo,
@@ -88,3 +294,9 @@ export const Default: Story = {};
88
294
  export const Finance: Story = {
89
295
  render: () => <FinanceAppFrameDemo />,
90
296
  };
297
+
298
+ export const NocfoBankAccounts: Story = {
299
+ name: 'Template — NOCFO bank accounts workspace',
300
+ render: () => <NocfoBankAccountsWorkspace />,
301
+ parameters: { layout: 'fullscreen' },
302
+ };
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { expect, userEvent, within } from 'storybook/test';
2
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
3
3
 
4
4
  import { Button } from '../../../../packages/ui/src/button';
5
5
  import {
@@ -53,7 +53,7 @@ export const OpenAndClose: Story = {
53
53
  play: async ({ canvas }) => {
54
54
  await userEvent.click(canvas.getByRole('button', { name: /review change/i }));
55
55
  const dialog = within(document.body).getByRole('dialog');
56
- await expect(dialog).toBeVisible();
56
+ await waitFor(() => expect(dialog).toBeVisible());
57
57
  await userEvent.click(within(dialog).getByRole('button', { name: /cancel/i }));
58
58
  },
59
59
  };
@@ -6,6 +6,7 @@ import {
6
6
  iconsaxFreeSource,
7
7
  NoeMark,
8
8
  } from '../../../../packages/icons/src/index';
9
+ import { NocfoLogo } from '../../../../packages/icons/src/nocfo-logo';
9
10
 
10
11
  function IconGallery() {
11
12
  return (
@@ -24,6 +25,16 @@ function IconGallery() {
24
25
  <span>Backward</span>
25
26
  </div>
26
27
  </div>
28
+ <div
29
+ data-theme="dark"
30
+ style={{
31
+ background: 'var(--noe-color-surface-sunken)',
32
+ borderRadius: 'var(--noe-radius-md)',
33
+ padding: 'var(--noe-space-4)',
34
+ }}
35
+ >
36
+ <NocfoLogo label="NOCFO" />
37
+ </div>
27
38
  <div>
28
39
  <strong>Mirror in RTL:</strong> {iconDirectionPolicy.mirror.join(', ')}
29
40
  </div>
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { expect, userEvent, within } from 'storybook/test';
2
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
3
3
 
4
4
  import { Button } from '../../../../packages/ui/src/button';
5
5
  import {
@@ -40,7 +40,7 @@ export const KeyboardOpen: Story = {
40
40
  trigger.focus();
41
41
  await userEvent.keyboard('{Enter}');
42
42
  const menu = within(document.body).getByRole('menu');
43
- await expect(menu).toBeVisible();
44
- await expect(within(menu).getByText('Rerun')).toBeVisible();
43
+ await waitFor(() => expect(menu).toBeVisible());
44
+ await waitFor(() => expect(within(menu).getByText('Rerun')).toBeVisible());
45
45
  },
46
46
  };
@@ -18,7 +18,6 @@ input,
18
18
  textarea,
19
19
  select {
20
20
  font: inherit;
21
- color: inherit;
22
21
  }
23
22
  button,
24
23
  a,
@@ -30,3 +30,11 @@ Do not create a separate app palette.
30
30
  - Admin, expert and profile shells: the same paper/graphite canvas, thin utility
31
31
  dividers, and one mint primary action per region.
32
32
  - Destructive paths continue to use coral and must retain explicit labels.
33
+
34
+ ## Product marks
35
+
36
+ `NocfoLogo` from `@noe/icons/nocfo-logo` owns the official wide NOCFO wordmark and its intrinsic
37
+ aspect ratio. Render it on a dark product-chrome surface, give it a localized accessible
38
+ label, and never mirror, crop, redraw or recolor the artwork. Product shells compose the
39
+ mark through the `HeaderNavigation.brand` slot instead of referencing a product asset
40
+ from page code.
@@ -60,6 +60,12 @@ surface tokens and spacing; their outer stroke and every shadow are absent. Use
60
60
  the standard `Button variant="secondary"` for transparent, currentColor outline
61
61
  actions. It is white on dark/colored surfaces and graphite on paper.
62
62
 
63
+ The `Template — NOCFO bank accounts workspace` story on `app-frame` is the canonical
64
+ dense finance composition: official `NocfoLogo`, compact header actions, logical RTL
65
+ sidebar, bilingual `PageHeader`, multi-control `FilterBar`, and compact `DataTable`.
66
+ Product repositories keep domain columns and queries local while reusing this
67
+ composition instead of maintaining separate Cashflow and Loan shell/table wrappers.
68
+
63
69
  Mapping to the Untitled UI reference taxonomy (honest as of 2026-08-27 — foundation stabilization):
64
70
 
65
71
  | Untitled UI category | NOE coverage |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noecosystem-design",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Agent-readable NOE design-system source, registry metadata, and verification contracts.",
6
6
  "keywords": [
@@ -1,3 +1,8 @@
1
1
  @import "tailwindcss";
2
2
  @import "./theme.css";
3
3
  @import "./base.css";
4
+
5
+ /* Tailwind v4 resolves explicit sources relative to this stylesheet. Storybook and
6
+ product apps consume workspace source outside their own package roots. */
7
+ @source "../../../packages";
8
+ @source "../../../apps";
@@ -7,9 +7,14 @@
7
7
  "react": "^19.2.0"
8
8
  },
9
9
  "exports": {
10
- ".": "./src/index.tsx"
10
+ ".": "./src/index.tsx",
11
+ "./nocfo-logo": "./src/nocfo-logo.tsx"
11
12
  },
12
13
  "dependencies": {
13
14
  "iconsax-react": "0.0.8"
15
+ },
16
+ "devDependencies": {
17
+ "vitest": "4.1.11",
18
+ "vitest-browser-react": "2.2.0"
14
19
  }
15
20
  }
@@ -0,0 +1,8 @@
1
+ <svg width="698" height="115" viewBox="0 0 698 115" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <title>NOCFO</title>
3
+ <path d="M636.475 42.5066C670.199 41.9183 699.188 36.762 697.24 78.3513C696.422 95.8057 694.599 107.431 674.435 113.115C655.219 113.967 618.693 118.485 614.59 94.2956C611.226 74.4705 609.538 47.9355 636.475 42.5066ZM688.198 76.031C690.086 45.5042 661.671 51.0544 637.899 51.5583C632.981 52.6653 629.35 53.7832 626.398 58.1775C620.114 67.5366 618.965 95.6618 630.797 101.452C639.35 105.639 662.895 104.297 672.995 103.76C688.139 98.6936 687.394 89.0325 688.198 76.031Z" fill="white"/>
4
+ <path d="M467.517 43.3473C482.372 43.1323 513.647 39.1155 520.943 54.2547C522.502 57.4924 524.8 62.6848 523.304 66.1513C520.707 67.2164 520.514 67.0872 517.584 66.6572C510.654 62.2262 516.468 52.7675 499.371 52.2646C464.447 51.3928 449.226 48.4785 452.936 86.8645C454.824 106.392 466.853 104.769 485.656 104.5C526.417 104.971 504.703 92.1634 520.242 89.2117C523.238 90.3793 522.264 89.5509 523.759 92.4987C523.548 113.208 504.309 114.086 486.432 113.861C470.279 113.659 447.74 114.956 444.598 94.261C441.582 74.4048 439.949 48.4154 467.517 43.3473Z" fill="white"/>
5
+ <path d="M554.993 43.2972C565.284 43.1654 601.126 41.0629 606.755 45.6961C607.862 48.7493 607.864 47.4537 606.659 50.5743C597.297 57.7393 541.771 40.7556 544.87 67.1734C546.493 81.0099 594.502 67.9992 602.929 74.1125C602.981 77.0923 603.463 79.5864 601.237 81.6661C587.929 82.9637 559.376 82.0993 544.925 82.017C544.823 90.3091 546.719 105.331 543.654 112.144C540.944 114.363 542.339 113.847 538.229 113.803L536.945 112.23C535.403 102.746 534.598 62.4579 539.522 52.2873C541.947 47.2793 549.2 44.6295 554.993 43.2972Z" fill="white"/>
6
+ <path d="M56.7057 0.220818C59.4812 -0.0783868 66.655 0.00764426 69.5347 0.0238175C78.3794 0.0954738 87.2231 0.25451 96.0646 0.501376C100.5 0.589655 105.045 0.395802 109.484 0.256308C132.52 -0.467892 156.409 -0.133871 173.733 17.4305C179.379 23.0998 183.544 30.0721 185.86 37.7326C187.257 42.4387 187.682 48.1555 188.036 53.0969C189.467 73.0742 188.685 93.4045 188.559 113.42C181.836 113.656 173.963 113.499 167.156 113.523L167.227 75.7448C167.255 68.6649 167.379 61.7158 167.124 54.636C166.395 34.3692 151.889 21.6885 132.236 19.9335C117.122 18.9447 100.969 19.8066 85.8082 19.9052C66.7354 20.0292 45.5038 16.4282 30.7644 31.6442C24.6706 38.0679 22.4129 45.2014 21.9355 53.7631C20.8234 73.7101 21.3197 93.5171 21.552 113.465C15.0667 113.737 6.74555 113.517 0.174689 113.502C0.0536924 100.976 -0.00444898 88.4497 0.000265171 75.9236C0.023387 65.4894 -0.0850384 49.2196 2.53468 39.4846C4.44796 32.5183 7.8839 26.064 12.5947 20.589C24.2656 7.00099 39.1747 1.54859 56.7057 0.220818Z" fill="white"/>
7
+ <path d="M319.568 0.193154C332.548 0.161706 345.543 -0.281296 358.514 0.29081C365.335 0.582372 372.427 0.7861 379.045 2.5828C396.211 7.24404 410.499 20.151 416.147 37.1297C418.006 42.7191 418.727 48.4357 418.961 54.3387C420.359 89.7317 395.124 112.994 360.341 113.954C346.371 114.201 332.168 113.892 318.17 113.936L293.926 114.03C272.33 113.981 254.719 113.713 238.029 98.2703C216.174 78.0453 215.513 41.0945 235.138 19.2088C247.145 5.81961 262.554 0.908067 279.985 0.192178L281.043 0.157021C293.831 -0.229988 306.771 0.224377 319.568 0.193154ZM329.095 19.2166C313.953 19.1948 298.232 18.934 283.14 19.2752C283.028 19.2772 282.918 19.2805 282.808 19.2859C271.122 19.6424 260.264 22.6589 252.265 31.5525C245.275 39.3273 242.217 48.2684 242.538 58.5779C242.751 68.5089 246.945 77.937 254.18 84.74C267.898 97.6367 287.634 94.6477 304.868 94.7137C322.102 94.7799 339.509 95.1774 356.733 94.7576C386.396 94.2072 404.261 71.1977 395.264 42.6101C391.991 32.2128 380.566 23.3761 370.269 20.8816C362.924 19.1026 354.048 19.1626 346.523 19.1844L329.095 19.2166Z" fill="white"/>
8
+ </svg>
@@ -0,0 +1,14 @@
1
+ import { expect, test } from 'vitest';
2
+ import { render } from 'vitest-browser-react';
3
+
4
+ import { NocfoLogo } from './nocfo-logo';
5
+
6
+ test('NocfoLogo exposes the official wordmark with an accessible product name', async () => {
7
+ const screen = await render(<NocfoLogo label="سامانه NOCFO" />);
8
+ const logo = screen.getByRole('img', { name: 'سامانه NOCFO' });
9
+
10
+ await expect.element(logo).toHaveAttribute('data-slot', 'nocfo-logo');
11
+ await expect.element(logo).toHaveAttribute('width', '698');
12
+ await expect.element(logo).toHaveAttribute('height', '115');
13
+ expect(logo.element().getAttribute('src')).toMatch(/^data:image\/svg\+xml/);
14
+ });
@@ -0,0 +1,23 @@
1
+ import type * as React from 'react';
2
+
3
+ const nocfoLogoUrl = new URL('./brands/nocfo.svg', import.meta.url).href;
4
+
5
+ export interface NocfoLogoProps extends Omit<React.ComponentProps<'img'>, 'alt' | 'src'> {
6
+ /** Localized accessible name. Pass an empty string only when adjacent text names the brand. */
7
+ label?: string;
8
+ }
9
+
10
+ /** Official NOCFO wordmark. Brand artwork is never directionally mirrored. */
11
+ export function NocfoLogo({ label = 'NOCFO', style, ...props }: NocfoLogoProps) {
12
+ return (
13
+ <img
14
+ alt={label}
15
+ data-slot="nocfo-logo"
16
+ height={115}
17
+ src={nocfoLogoUrl}
18
+ style={{ blockSize: 'auto', inlineSize: '8.75rem', ...style }}
19
+ width={698}
20
+ {...props}
21
+ />
22
+ );
23
+ }
@@ -43,10 +43,17 @@
43
43
  "color.surfaceRaised",
44
44
  "color.surfaceSunken"
45
45
  ],
46
- "capabilityAliases": ["app frame", "app-frame", "finance application shell", "admin shell"],
46
+ "capabilityAliases": [
47
+ "app frame",
48
+ "app-frame",
49
+ "finance application shell",
50
+ "admin shell",
51
+ "NOCFO workspace"
52
+ ],
47
53
  "useWhen": [
48
54
  "using app frame in Patterns / Pages",
49
- "building a flat finance or admin application shell"
55
+ "building a flat finance or admin application shell",
56
+ "composing the NOCFO bank-account workspace template shown in Storybook"
50
57
  ],
51
58
  "doNotUseWhen": ["when app frame semantics do not match"]
52
59
  }
@@ -3,7 +3,7 @@
3
3
  "name": "icon-system",
4
4
  "type": "registry:ui",
5
5
  "title": "Icon System",
6
- "description": "Icon System Foundations / Icons component. icon-system provides foundations / icons behavior with RTL, i18n and accessibility built-in.",
6
+ "description": "One icon and product-mark policy for NOE interfaces. Includes direction-aware Iconsax controls, the NOE mark and the official NOCFO wordmark; brand artwork never mirrors in RTL.",
7
7
  "files": [
8
8
  {
9
9
  "path": "packages/icons/src/index.tsx",
@@ -14,6 +14,16 @@
14
14
  "path": "packages/icons/src/iconsax.tsx",
15
15
  "type": "registry:component",
16
16
  "target": "packages/icons/src/iconsax.tsx"
17
+ },
18
+ {
19
+ "path": "packages/icons/src/nocfo-logo.tsx",
20
+ "type": "registry:component",
21
+ "target": "packages/icons/src/nocfo-logo.tsx"
22
+ },
23
+ {
24
+ "path": "packages/icons/src/brands/nocfo.svg",
25
+ "type": "registry:file",
26
+ "target": "packages/icons/src/brands/nocfo.svg"
17
27
  }
18
28
  ],
19
29
  "registryDependencies": [],
@@ -30,7 +40,7 @@
30
40
  "rtl": "required",
31
41
  "i18n": {
32
42
  "messageKeys": [],
33
- "bidiNotes": ["logical properties", "no global mirror"]
43
+ "bidiNotes": ["directional controls mirror by policy", "brand marks never mirror"]
34
44
  },
35
45
  "accessibility": {
36
46
  "keyboard": ["Tab to focus", "Enter/Space activate"],
@@ -38,7 +48,10 @@
38
48
  "focusBehavior": ["visible ring 2px"]
39
49
  },
40
50
  "tokenContracts": ["color.foreground", "color.border"],
41
- "capabilityAliases": ["icon system", "icon-system"],
42
- "useWhen": ["using icon system in Foundations / Icons"],
43
- "doNotUseWhen": ["when icon system semantics do not match"]
51
+ "capabilityAliases": ["icon system", "icon-system", "NOCFO logo", "product wordmark"],
52
+ "useWhen": [
53
+ "rendering interface icons through the canonical Iconsax policy",
54
+ "rendering the official NOCFO wordmark in product chrome"
55
+ ],
56
+ "doNotUseWhen": ["rendering a third-party service logo through the NOCFO product mark"]
44
57
  }
@@ -3,7 +3,7 @@
3
3
  "name": "sidebar-navigation",
4
4
  "type": "registry:block",
5
5
  "title": "Sidebar Navigation",
6
- "description": "Sidebar Navigation — Patterns / Application component. Uses logical icon, label and end-adornment order without row reversal; RTL, i18n and accessibility are built in.",
6
+ "description": "Sidebar Navigation — Patterns / Application component. Uses logical icon, label and end-adornment order without row reversal, with opt-in wrapping for long bilingual finance labels; RTL, i18n and accessibility are built in.",
7
7
  "files": [
8
8
  {
9
9
  "path": "packages/ui/src/lib/cn.ts",
@@ -44,6 +44,9 @@
44
44
  "color.mutedForeground"
45
45
  ],
46
46
  "capabilityAliases": ["sidebar navigation", "sidebar-navigation", "finance sidebar"],
47
- "useWhen": ["using sidebar navigation in Patterns / Application"],
47
+ "useWhen": [
48
+ "using sidebar navigation in Patterns / Application",
49
+ "rendering long bilingual finance labels with wrapLabels"
50
+ ],
48
51
  "doNotUseWhen": ["when sidebar navigation semantics do not match"]
49
52
  }
@@ -27,6 +27,7 @@ test('finance app frame keeps a flat, logical RTL sidebar structure', async () =
27
27
  ],
28
28
  },
29
29
  ]}
30
+ wrapLabels
30
31
  />
31
32
  }
32
33
  variant="finance"
@@ -43,5 +44,10 @@ test('finance app frame keeps a flat, logical RTL sidebar structure', async () =
43
44
  .element(screen.getByRole('link', { name: 'داشبورد' }))
44
45
  .toHaveAttribute('aria-current', 'page');
45
46
  await expect.element(screen.getByTestId('dashboard-icon')).toBeVisible();
47
+ await expect.element(screen.getByText('داشبورد')).toHaveClass('whitespace-normal');
46
48
  await expect.element(screen.getByRole('main')).toHaveAttribute('id', 'finance-main');
49
+ const layoutGrid = frame.element().querySelector(':scope > div');
50
+ expect(layoutGrid).not.toBeNull();
51
+ expect(layoutGrid?.getAttribute('data-has-inspector')).toBeNull();
52
+ expect(layoutGrid?.className).not.toContain('_18rem');
47
53
  });
@@ -42,9 +42,16 @@ export function AppFrame({
42
42
  className={cn(
43
43
  'grid',
44
44
  isFinanceShell
45
- ? 'lg:grid-cols-[17.5rem_minmax(0,1fr)] xl:grid-cols-[17.5rem_minmax(0,1fr)_18rem]'
46
- : 'lg:grid-cols-[15rem_minmax(0,1fr)] xl:grid-cols-[15rem_minmax(0,1fr)_18rem]',
45
+ ? cn(
46
+ 'lg:grid-cols-[17.5rem_minmax(0,1fr)]',
47
+ inspector && 'xl:grid-cols-[17.5rem_minmax(0,1fr)_18rem]',
48
+ )
49
+ : cn(
50
+ 'lg:grid-cols-[15rem_minmax(0,1fr)]',
51
+ inspector && 'xl:grid-cols-[15rem_minmax(0,1fr)_18rem]',
52
+ ),
47
53
  )}
54
+ data-has-inspector={inspector ? true : undefined}
48
55
  >
49
56
  {mobileNavigation ? (
50
57
  <nav
@@ -8,14 +8,14 @@ export const badgeVariants = cva(
8
8
  {
9
9
  variants: {
10
10
  variant: {
11
- default: 'border-accent/30 bg-accent/10 text-accent',
11
+ default: 'border-accent/30 bg-accent/10 text-foreground',
12
12
  outline: 'border-border bg-transparent text-foreground',
13
13
  secondary: 'border-transparent bg-muted text-foreground',
14
- destructive: 'border-destructive/30 bg-destructive/10 text-destructive',
14
+ destructive: 'border-destructive/30 bg-destructive/10 text-foreground',
15
15
  info: 'border-info/30 bg-info/10 text-info',
16
16
  success: 'border-success/30 bg-success/10 text-success',
17
- warning: 'border-warning/40 bg-warning/15 text-warning-foreground',
18
- error: 'border-destructive/30 bg-destructive/10 text-destructive',
17
+ warning: 'border-warning/40 bg-warning/15 text-foreground',
18
+ error: 'border-destructive/30 bg-destructive/10 text-foreground',
19
19
  },
20
20
  },
21
21
  defaultVariants: {
@@ -317,7 +317,7 @@ export function Calendar({
317
317
  isSelected
318
318
  ? 'bg-accent text-accent-foreground'
319
319
  : isToday
320
- ? 'border border-accent text-accent'
320
+ ? 'border border-accent text-foreground'
321
321
  : 'text-foreground hover:bg-muted',
322
322
  )}
323
323
  >
@@ -105,7 +105,9 @@ export function Terminal({
105
105
  <span
106
106
  className={cn(
107
107
  'block',
108
- line.stream === 'stderr' ? 'text-destructive' : 'text-background/90',
108
+ line.stream === 'stderr'
109
+ ? 'text-[var(--noe-primitive-color-coral-400)]'
110
+ : 'text-background/90',
109
111
  )}
110
112
  data-stream={line.stream ?? 'stdout'}
111
113
  key={line.key}
@@ -97,7 +97,7 @@ export function FieldError({
97
97
  }: React.ComponentProps<typeof FieldPrimitive.Error>) {
98
98
  return (
99
99
  <FieldPrimitive.Error
100
- className={cn('text-xs leading-normal text-destructive', className)}
100
+ className={cn('text-xs font-medium leading-normal text-foreground', className)}
101
101
  data-slot="field-error"
102
102
  {...props}
103
103
  />
@@ -11,7 +11,7 @@ export function FileUploader({ className, ...props }: React.ComponentProps<'div'
11
11
  {...props}
12
12
  >
13
13
  Drag files here or{' '}
14
- <button type="button" className="text-accent">
14
+ <button type="button" className="font-medium text-foreground underline">
15
15
  browse
16
16
  </button>
17
17
  </div>
@@ -54,7 +54,7 @@ export function ProgressSteps({
54
54
  isCompleted
55
55
  ? 'border-success/20 bg-success/10 text-success'
56
56
  : isCurrent
57
- ? 'border-accent bg-accent/10 text-accent'
57
+ ? 'border-accent bg-accent/10 text-foreground'
58
58
  : 'border-border bg-surface text-muted-foreground',
59
59
  )}
60
60
  >
@@ -22,11 +22,14 @@ export interface SidebarNavigationSection {
22
22
  export interface SidebarNavigationProps extends Omit<React.ComponentProps<'nav'>, 'children'> {
23
23
  label: string;
24
24
  sections: readonly SidebarNavigationSection[];
25
+ /** Allow long bilingual labels to wrap in dense finance navigation. */
26
+ wrapLabels?: boolean;
25
27
  }
26
28
 
27
29
  export function SidebarNavigation({
28
30
  label,
29
31
  sections,
32
+ wrapLabels = false,
30
33
  className,
31
34
  ...props
32
35
  }: SidebarNavigationProps) {
@@ -70,7 +73,14 @@ export function SidebarNavigation({
70
73
  {item.icon}
71
74
  </span>
72
75
  ) : null}
73
- <span className="min-w-0 flex-1 truncate">{item.label}</span>
76
+ <span
77
+ className={cn(
78
+ 'min-w-0 flex-1',
79
+ wrapLabels ? 'whitespace-normal break-words' : 'truncate',
80
+ )}
81
+ >
82
+ {item.label}
83
+ </span>
74
84
  {item.endAdornment ? (
75
85
  <span className="ms-auto shrink-0 text-current">{item.endAdornment}</span>
76
86
  ) : null}