@isi-ui7/bos7-shared 0.2.4 → 0.2.7

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.
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { ElementType, ReactNode } from 'react';
2
2
 
3
3
  export interface AppShellAuthState {
4
4
  isAuthenticated: boolean;
@@ -12,7 +12,22 @@ export interface AppShellAuthState {
12
12
  login: () => void;
13
13
  logout: () => Promise<void> | void;
14
14
  }
15
- export type ShellNavItemLike = any;
15
+ /**
16
+ * Structural shape of a side-nav item, mirroring ui-shell's `ShellNavItem`.
17
+ * `label`/`href` are required to stay mutually assignable with `ShellNavItem`:
18
+ * this type is used both covariantly (`navItems`) and contravariantly
19
+ * (`markActive`'s parameter), so consumers passing `ShellNavItem`-typed values
20
+ * would otherwise fail under `strict`. The index signature keeps it permissive
21
+ * for extra metadata (e.g. `requiredPermission`).
22
+ */
23
+ export interface ShellNavItemLike {
24
+ label: string;
25
+ href: string;
26
+ isActive?: boolean;
27
+ icon?: string;
28
+ children?: ShellNavItemLike[];
29
+ [key: string]: unknown;
30
+ }
16
31
  export interface ShellNotificationLike {
17
32
  id?: string;
18
33
  read?: boolean;
@@ -43,9 +58,9 @@ export interface AppShellLayoutDeps {
43
58
  theme: ThemeLike;
44
59
  };
45
60
  useNotificationBell: (opts: NotificationBellOptionsLike) => NotificationBellLike;
46
- GlobalTheme: any;
47
- ModalProvider: any;
48
- UiShell: any;
61
+ GlobalTheme: ElementType;
62
+ ModalProvider: ElementType;
63
+ UiShell: ElementType;
49
64
  useAuth?: () => AppShellAuthState;
50
65
  }
51
66
  export interface AppShellLayoutProps {
@@ -63,7 +78,7 @@ export interface AppShellLayoutProps {
63
78
  notificationsViewAllTarget?: string;
64
79
  hideMenuButton?: boolean;
65
80
  disableAppsMenu?: boolean;
66
- sideNavMode?: "drilldown" | "overlay";
81
+ sideNavMode?: "drilldown" | "overlay" | "expand";
67
82
  sideNavItems?: ShellNavItemLike[];
68
83
  }
69
84
  export declare function AppShellLayout({ children, deps, navItems, markActive, appNameFallback, auth7UiUrlFallback, portalUrlFallback, roleFallback, appSwitchMode, resolveNotificationHref, notificationsViewAllHref, notificationsViewAllTarget, hideMenuButton, disableAppsMenu, sideNavMode, sideNavItems, }: AppShellLayoutProps): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,15 @@
1
1
  export type Ui7FormDensity = "compact" | "comfortable";
2
2
  export type Ui7ThemeDensity = Ui7FormDensity | "normal";
3
+ /**
4
+ * Desktop form-panel width. Mobile + tablet (<1056px) always release the
5
+ * cap and stretch to 100% — the modifier only matters at ≥1056px.
6
+ *
7
+ * - "half" → 50% of page body
8
+ * - "two-thirds" → 66.6667% (default — readable column width on wide
9
+ * monitors, what the contract shipped with originally)
10
+ * - "full" → 100% (forms with many columns or wide tables inside)
11
+ */
12
+ export type Ui7FormWidth = "half" | "two-thirds" | "full";
3
13
  export interface Ui7FormVisualTokens {
4
14
  labelSpacing: "6px";
5
15
  labelFontSize: "12px";
@@ -38,6 +48,9 @@ export declare const UI7_FORM_VISUAL_CLASSNAMES: {
38
48
  readonly scope: "ui7-form-contract";
39
49
  readonly densityCompact: "ui7-form-contract--density-compact";
40
50
  readonly densityComfortable: "ui7-form-contract--density-comfortable";
51
+ readonly widthHalf: "ui7-form-contract--width-half";
52
+ readonly widthTwoThirds: "ui7-form-contract--width-two-thirds";
53
+ readonly widthFull: "ui7-form-contract--width-full";
41
54
  readonly readonly: "ui7-form-contract--readonly";
42
55
  readonly row: "ui7-form-row";
43
56
  readonly section: "ui7-form-section";
@@ -52,21 +65,23 @@ export declare const UI7_FORM_VISUAL_CLASSNAMES: {
52
65
  export declare const UI7_FORM_DENSITY_TOKENS: {
53
66
  readonly compact: {
54
67
  readonly controlHeight: "2rem";
55
- readonly rowGap: "0.75rem";
56
- readonly sectionGap: "1rem";
57
- readonly columnGap: "0.75rem";
68
+ readonly rowGap: "1rem";
69
+ readonly sectionGap: "1.25rem";
70
+ readonly columnGap: "1rem";
58
71
  };
59
72
  readonly comfortable: {
60
73
  readonly controlHeight: "2.5rem";
61
- readonly rowGap: "1rem";
74
+ readonly rowGap: "1.25rem";
62
75
  readonly sectionGap: "1.5rem";
63
- readonly columnGap: "1rem";
76
+ readonly columnGap: "1.25rem";
64
77
  };
65
78
  };
66
79
  export declare function normalizeUi7FormDensity(density?: Ui7ThemeDensity | null, fallback?: Ui7FormDensity): Ui7FormDensity;
67
80
  export declare function getUi7FormDensityClassName(density?: Ui7ThemeDensity | null, fallback?: Ui7FormDensity): string;
81
+ export declare function getUi7FormWidthClassName(width?: Ui7FormWidth | null): string;
68
82
  export declare function getUi7FormContractClassName(options?: {
69
83
  density?: Ui7ThemeDensity | null;
84
+ width?: Ui7FormWidth | null;
70
85
  readonly?: boolean;
71
86
  className?: string | null;
72
87
  }): string;
@@ -13,9 +13,22 @@ export type StartWorkflowInput = {
13
13
  flowId: string;
14
14
  payload: Record<string, unknown>;
15
15
  externalRef?: string;
16
+ /**
17
+ * Optional override for initiator identity. By default the helper decodes
18
+ * the full tenant identity (user_id, username, org_id, branch_id, branch_code)
19
+ * from the access token and uses that. Any field explicitly set here wins.
20
+ *
21
+ * Set ONLY when the caller has a verified reason to override (rare). For
22
+ * normal flow-start paths leave this undefined — letting the helper read
23
+ * the JWT keeps initiator_context aligned with the auth7 signature.
24
+ */
16
25
  initiatorContext?: {
17
26
  user_id?: string;
27
+ username?: string;
28
+ org_id?: string;
18
29
  branch_id?: string;
30
+ branch_code?: string;
31
+ ip?: string;
19
32
  };
20
33
  /** User access token dari auth7 — diforward ke workflow7 sebagai Bearer. */
21
34
  userAccessToken: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isi-ui7/bos7-shared",
3
- "version": "0.2.4",
3
+ "version": "0.2.7",
4
4
  "description": "Shared auth7 and layout primitives for bos7 applications.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -72,7 +72,8 @@
72
72
  "@carbon/react": "^1.97.0",
73
73
  "@carbon/icons-react": "^11.71.0",
74
74
  "@isi-ui7/data-table": ">=0.2.0",
75
- "@isi-ui7/lookup-input": ">=0.2.0"
75
+ "@isi-ui7/lookup-input": ">=0.2.0",
76
+ "@isi-ui7/editable-table": ">=0.2.0"
76
77
  },
77
78
  "devDependencies": {
78
79
  "@eslint/eslintrc": "^3.3.1",
@@ -98,13 +99,14 @@
98
99
  "next": "16.1.1",
99
100
  "@carbon/react": "^1.97.0",
100
101
  "@carbon/icons-react": "^11.71.0",
101
- "@isi-ui7/ui-shell": "0.2.3",
102
102
  "@isi-ui7/i18n": "0.2.0",
103
+ "@isi-ui7/corporate-themes": "0.2.4",
104
+ "@isi-ui7/ui-shell": "0.2.4",
103
105
  "@isi-ui7/modal-manager": "0.2.1",
104
- "@isi-ui7/lookup-input": "0.2.2",
105
106
  "@isi-ui7/realtime": "0.2.2",
106
- "@isi-ui7/corporate-themes": "0.2.3",
107
- "@isi-ui7/data-table": "0.2.2"
107
+ "@isi-ui7/lookup-input": "0.2.2",
108
+ "@isi-ui7/data-table": "0.2.2",
109
+ "@isi-ui7/editable-table": "0.2.2"
108
110
  },
109
111
  "dependencies": {
110
112
  "@carbon/icons-react": "^11.71.0",
@@ -117,8 +119,9 @@
117
119
  "@opentelemetry/sdk-node": "^0.57.0",
118
120
  "@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
119
121
  "@opentelemetry/auto-instrumentations-node": "^0.57.0",
122
+ "@isi-ui7/lookup-input": "0.2.2",
120
123
  "@isi-ui7/data-table": "0.2.2",
121
- "@isi-ui7/lookup-input": "0.2.2"
124
+ "@isi-ui7/editable-table": "0.2.2"
122
125
  },
123
126
  "scripts": {
124
127
  "build": "vite build",
@@ -15,6 +15,7 @@ import {
15
15
  ToastNotification,
16
16
  } from "@carbon/react";
17
17
  import { ServerDataTable } from "@isi-ui7/data-table";
18
+ import type { I_DataTblColumn } from "@isi-ui7/data-table";
18
19
  import type { CrudCustomActionSchema, CrudDeleteSchema, CrudListSchema } from "./crud-types";
19
20
  import type { CrudForm, FormMode } from "./form-types";
20
21
  import type { Ui7FormDensity } from "./style-contract";
@@ -25,6 +26,24 @@ import { useCrudForm } from "./workflow/use-crud-form";
25
26
  import { useWorkflowTracker } from "./workflow/use-workflow-tracker";
26
27
  import { useBosSharedI18n } from "./i18n";
27
28
 
29
+ // Re-export the value_schema → form adapter so category pages (bos7-enterprise)
30
+ // can compose: [scopeSection(t), ...buildValueSections(schema, t).sections].
31
+ export {
32
+ buildValueSections,
33
+ buildXRulesValidator,
34
+ } from "./form-value-schema";
35
+ export type {
36
+ JSONSchema,
37
+ XUi,
38
+ XUiRoot,
39
+ XUiNumeric,
40
+ XUiOption,
41
+ XUiWidget,
42
+ XRule,
43
+ XRuleOp,
44
+ ValueValidatorFn,
45
+ } from "./form-value-schema";
46
+
28
47
  // ── List page (bare) ──────────────────────────────────────────────────────────
29
48
 
30
49
  export function SharedCrudListPage({
@@ -46,10 +65,10 @@ export function SharedCrudListPage({
46
65
  <ServerDataTable
47
66
  key={tableKey}
48
67
  apiPath={schema.apiPath}
49
- columns={schema.columns as any}
68
+ columns={schema.columns as Record<string, I_DataTblColumn>}
50
69
  showSearch={schema.showSearch ?? true}
51
70
  showSort={schema.showSort ?? true}
52
- popupMenu={{ src: "local", items: schema.popupMenuItems as any, onClick: onPopupClick }}
71
+ popupMenu={{ src: "local", items: schema.popupMenuItems, onClick: onPopupClick }}
53
72
  toolbarActions={
54
73
  <Button renderIcon={Add} size="sm" onClick={onAdd}>
55
74
  {schema.addButtonLabel || labels.next}
@@ -307,6 +326,9 @@ export function CrudSchemaPage<TData extends Record<string, unknown>>({
307
326
  }) {
308
327
  const labels = useBosSharedI18n();
309
328
  const density = densityProp ?? schema.density ?? "compact";
329
+ // Width default lives in the renderer — passing undefined here keeps
330
+ // the contract function's "two-thirds" fallback as the single source.
331
+ const width = schema.width;
310
332
  const {
311
333
  form,
312
334
  setForm,
@@ -379,6 +401,7 @@ export function CrudSchemaPage<TData extends Record<string, unknown>>({
379
401
  disabled={saving}
380
402
  onChange={setForm}
381
403
  density={density}
404
+ width={width}
382
405
  />
383
406
  </PageBody>
384
407
  );
@@ -437,6 +460,7 @@ export function CrudSchemaPage<TData extends Record<string, unknown>>({
437
460
  disabled={saving}
438
461
  onChange={setForm}
439
462
  density={density}
463
+ width={width}
440
464
  />
441
465
  </div>
442
466
  ))}
@@ -463,6 +487,7 @@ export function CrudSchemaPage<TData extends Record<string, unknown>>({
463
487
  disabled={saving}
464
488
  onChange={setForm}
465
489
  density={density}
490
+ width={width}
466
491
  />
467
492
  ) : null}
468
493
  </PageBody>
package/src/crud-hooks.ts CHANGED
@@ -9,9 +9,12 @@ export async function runCrudHook<TData extends Record<string, unknown>, TResult
9
9
  return hook(data, context);
10
10
  }
11
11
 
12
- export function applyCrudFieldOverrides<TField extends { key: string; readonly?: boolean; required?: boolean; helperText?: unknown }>(
12
+ export function applyCrudFieldOverrides<
13
+ TField extends { key: string; readonly?: boolean; required?: boolean; helperText?: unknown },
14
+ TData extends Record<string, unknown> = Record<string, unknown>,
15
+ >(
13
16
  fields: TField[],
14
- hooks?: CrudActionOverrideHooks<Record<string, unknown>> | CrudActionOverrideHooks<any>,
17
+ hooks?: CrudActionOverrideHooks<TData>,
15
18
  ): TField[] {
16
19
  if (!hooks?.fieldOverrides) return fields;
17
20
 
@@ -1,6 +1,29 @@
1
1
  /* Form contract — Carbon overrides scoped to .ui7-form-contract
2
2
  Token source of truth: UI7_FORM_VISUAL_TOKENS in style-contract.ts */
3
3
 
4
+ /* Panel width — desktop-first (matches the rest of this file).
5
+ The width modifier (one of --width-half / --width-two-thirds / --width-full)
6
+ is always present on the root — `getUi7FormContractClassName` emits one,
7
+ defaulting to two-thirds when the consumer omits the option. Base scope
8
+ sets only the hard ceiling + alignment; modifier sets the width. The
9
+ breakpoint override at the bottom of the file releases the cap on tablet
10
+ + mobile (<1056px) so fields can use full width on narrow screens. */
11
+ .ui7-form-contract {
12
+ max-width: 1200px;
13
+ }
14
+
15
+ .ui7-form-contract--width-half {
16
+ width: 50%;
17
+ }
18
+
19
+ .ui7-form-contract--width-two-thirds {
20
+ width: 66.6667%;
21
+ }
22
+
23
+ .ui7-form-contract--width-full {
24
+ width: 100%;
25
+ }
26
+
4
27
  /* Label: 12px / 500 / 16px */
5
28
  .ui7-form-contract .cds--label {
6
29
  margin-block-end: 0;
@@ -21,15 +44,54 @@
21
44
  line-height: 1.25rem;
22
45
  }
23
46
 
47
+ /* Control height — keep Select aligned with TextInput / DatePicker per density.
48
+ Carbon uses logical `block-size` (not `height`) on every input element and
49
+ resolves it through --cds-layout-size-height-* tokens that the React `size`
50
+ prop sets on the wrapper. A `height` rule alone loses the cascade because
51
+ `block-size` is a separate physical→logical property in horizontal writing
52
+ mode. We do TWO things to win cleanly:
53
+ 1. Redirect Carbon's height tokens at the density-modifier class
54
+ (see @isi-ui7/corporate-themes globals.scss).
55
+ 2. Set both `block-size` AND `height` here as a safety net, anchored to
56
+ the same var so density swaps stay in sync across all input flavours.
57
+ --ui7-form-control-height resolves to 1.75rem (compact) / 2.5rem (comfortable). */
58
+ .ui7-form-contract .cds--text-input,
59
+ .ui7-form-contract .cds--select-input,
60
+ .ui7-form-contract .cds--date-picker__input,
61
+ .ui7-form-contract .cds--number input,
62
+ .ui7-form-contract .cds--combo-box__input,
63
+ .ui7-form-contract .cds--dropdown {
64
+ block-size: var(--ui7-form-control-height);
65
+ min-block-size: var(--ui7-form-control-height);
66
+ max-block-size: var(--ui7-form-control-height);
67
+ height: var(--ui7-form-control-height);
68
+ min-height: var(--ui7-form-control-height);
69
+ }
70
+
24
71
  /* Full-width date picker */
25
72
  .ui7-form-contract .cds--date-picker,
26
73
  .ui7-form-contract .cds--date-picker-container {
27
74
  width: 100%;
28
75
  }
29
76
 
77
+ /* ── Tablet + mobile: release the desktop width cap ─────────────────────── */
78
+ /* Below Carbon's lg breakpoint (1056px), narrow viewports need every
79
+ available pixel — any desktop width modifier (half / two-thirds / full)
80
+ would leave fields cramped. Release back to full width here, beating
81
+ the modifier selectors via specificity (compound) + later declaration. */
82
+ @media (max-width: 1055px) {
83
+ .ui7-form-contract,
84
+ .ui7-form-contract.ui7-form-contract--width-half,
85
+ .ui7-form-contract.ui7-form-contract--width-two-thirds,
86
+ .ui7-form-contract.ui7-form-contract--width-full {
87
+ width: 100%;
88
+ max-width: 100%;
89
+ }
90
+ }
91
+
30
92
  /* ── Mobile: collapse all form panels to single column ───────────────────── */
31
- /* Desktop-first: multi-column span values set via inline style are overridden
32
- below Carbon's md breakpoint (671px). !important required to beat inline style. */
93
+ /* Multi-column span values set via inline style are overridden below Carbon's
94
+ md breakpoint (671px). !important required to beat inline style. */
33
95
  @media (max-width: 671px) {
34
96
  .ui7-form-contract .ui7-form-row > .ui7-form-column {
35
97
  grid-column: 1 / -1 !important;
@@ -171,3 +171,19 @@ export const INDONESIA_CURRENCY_CONFIG: CurrencyPrecisionConfig = {
171
171
  precisionByCurrency: { IDR: 2, USD: 4 },
172
172
  defaultPrecision: 2,
173
173
  };
174
+
175
+ /**
176
+ * Whole-rupiah currency config — used for ledger-style fields like plafond,
177
+ * subsidi, agent limit, where the convention in Indonesian retail banking is
178
+ * to round to whole IDR (no sen). USD/USD-denominated fields still keep 2
179
+ * decimals (standard cents). Default precision is 0 so any other currency
180
+ * defaults to whole units in these contexts.
181
+ *
182
+ * Use this for "agent plafond / saldo / subsidi" style fields. For amounts
183
+ * that genuinely need sub-rupiah precision (e.g. percentage-derived fees,
184
+ * accrued interest), prefer `INDONESIA_CURRENCY_CONFIG` instead.
185
+ */
186
+ export const INDONESIA_RUPIAH_WHOLE_CONFIG: CurrencyPrecisionConfig = {
187
+ precisionByCurrency: { IDR: 0, USD: 2 },
188
+ defaultPrecision: 0,
189
+ };