@tallyui/components 3.5.3 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ import { VariantProps } from 'class-variance-authority';
8
8
  import { SvgProps } from 'react-native-svg';
9
9
  import { Separator as Separator$1, Label as Label$1, Tabs as Tabs$1, Dialog as Dialog$1, AlertDialog as AlertDialog$1, Select as Select$1, Combobox as Combobox$1, DropdownMenu as DropdownMenu$1, ContextMenu as ContextMenu$1, Popover as Popover$1, Tooltip as Tooltip$1, HoverCard as HoverCard$1, Accordion as Accordion$1, Collapsible as Collapsible$1, Checkbox as Checkbox$1, Switch as Switch$1, RadioGroup as RadioGroup$1, Toggle as Toggle$1, ToggleGroup as ToggleGroup$1, Progress as Progress$1, Slider as Slider$1, Image } from '@tallyui/primitives';
10
10
  export { TextClassContext } from '@tallyui/primitives';
11
- import { CatalogueView, ProductSortValue, ProductSort, useRegisterSession, MovementType, useSale, AppliedDiscount, Discount, CatalogueEntry, CatalogueViewState, SentOrder, PosOrder, OutboxState, ParkedOrderSummary } from '@tallyui/pos';
11
+ import { CatalogueView, ProductSortValue, ProductSort, useRegisterSession, RegisterSession, MovementType, useSale, AppliedDiscount, Discount, CatalogueEntry, CatalogueViewState, SentOrder, PosOrder, OutboxState, ParkedOrderSummary } from '@tallyui/pos';
12
12
  import { ProductTraits, TraitContext, Money, CustomerTraits, Customer, CustomerInput, ProductCategory, SyncNotice, StoreSettingsChoices, StoreSettingsChoice } from '@tallyui/core';
13
13
 
14
14
  declare const textVariants: (props?: ({
@@ -1093,6 +1093,7 @@ declare function RegisterBar({ register, registerId, online, registerName, multi
1093
1093
  * register name (`multiRegister` decides whether that's shown at all) and the status pill.
1094
1094
  * Copy is plain English (no i18n keys, ADR-064's neutral-copy rule).
1095
1095
  */
1096
+
1096
1097
  type RegisterBarPill = 'Choose a register' | 'Close not finished' | 'Offline' | 'Approval needed' | 'Counting' | 'Overdue' | 'Register closed';
1097
1098
  /**
1098
1099
  * Priority, matching WCPOS's ordering: choosing a register outranks everything (LEDGER 48);
@@ -1104,7 +1105,8 @@ declare function describeRegisterBarPill({ registerId, online, sessionStatus, ov
1104
1105
  /** The register this till is bound to, or `null` when it isn't bound yet. */
1105
1106
  registerId: string | null;
1106
1107
  online: boolean;
1107
- sessionStatus?: 'open' | 'counting' | 'closed' | null;
1108
+ /** `conflict` and `superseded` (ADR-078 decision 9) get no pill of their own yet; step 3b decides theirs. */
1109
+ sessionStatus?: RegisterSession['status'] | null;
1108
1110
  overdue?: boolean;
1109
1111
  approvalRequired?: boolean;
1110
1112
  /** The store uses register sessions at all (`useRegisterSession`'s `enabled`). */
@@ -1411,7 +1413,7 @@ declare function Catalogue<Doc>({ products, traits, currency, onSelect, statusTe
1411
1413
 
1412
1414
  /** `store`/`taxLabel`/`topInset`/`formatDate` replace the app's Medusa settings, hard-coded VAT (default matches
1413
1415
  * TV6a's `Cart`), outbox strip and date util, so `Receipt` stays platform-neutral (ADR-052). */
1414
- declare function Receipt({ order, posOrder, store, cashier, registerId, newSale, taxLabel, topInset, formatDate }: {
1416
+ declare function Receipt({ order, posOrder, store, cashier, registerId, registerName, newSale, taxLabel, topInset, formatDate, footerSlot }: {
1415
1417
  order: SentOrder;
1416
1418
  store: {
1417
1419
  name: string;
@@ -1420,12 +1422,16 @@ declare function Receipt({ order, posOrder, store, cashier, registerId, newSale,
1420
1422
  cashier: string;
1421
1423
  registerId: string;
1422
1424
  newSale: () => void;
1425
+ /** The register's name, printed as `Register: <name>`; the id is never printed. */
1426
+ registerName?: string;
1423
1427
  /** The finalized order (useSale's receipt stage has it). With it the receipt prints its reference and time, the
1424
1428
  * same as Orders; without it (a preview before finalize) the sale id is marked as a draft. */
1425
1429
  posOrder?: PosOrder;
1426
1430
  taxLabel?: (ratePpm: number) => string;
1427
1431
  topInset?: number;
1428
1432
  formatDate?: (iso: string) => string;
1433
+ /** Prints after the Change row and above the on-screen buttons (e.g. an app's register/software block). */
1434
+ footerSlot?: ReactNode;
1429
1435
  }): react_jsx_runtime.JSX.Element;
1430
1436
 
1431
1437
  /** The order reference a visitor sees on the receipt and in Orders (front desk ruling, 2026-10-06): the finalized
package/dist/index.js CHANGED
@@ -5021,17 +5021,20 @@ function Receipt({
5021
5021
  store,
5022
5022
  cashier,
5023
5023
  registerId,
5024
+ registerName,
5024
5025
  newSale,
5025
5026
  taxLabel = (ratePpm) => `Tax ${ratePpm / 1e4}%`,
5026
5027
  topInset = 0,
5027
- formatDate = defaultFormatDate
5028
+ formatDate = defaultFormatDate,
5029
+ footerSlot
5028
5030
  }) {
5029
5031
  useEffect6(injectPrintStyle, []);
5030
5032
  const receipt = buildReceiptData3(order, {
5031
5033
  storeName: store.name,
5032
5034
  storeAddress: store.address,
5033
5035
  cashier,
5034
- register: registerId
5036
+ register: registerId,
5037
+ registerName
5035
5038
  });
5036
5039
  const money = (amount) => formatMoney16({ amount, currency: receipt.currency }) ?? "";
5037
5040
  const row = (label, amount, bold = false, key) => /* @__PURE__ */ jsxs56(View63, { accessibilityLabel: `${label}: ${amount}`, className: "flex-row justify-between gap-4", children: [
@@ -5052,6 +5055,10 @@ function Receipt({
5052
5055
  "Cashier: ",
5053
5056
  receipt.header.cashier
5054
5057
  ] }),
5058
+ receipt.register.name ? /* @__PURE__ */ jsxs56(Text41, { testID: "receipt-register", className: "text-muted-foreground", children: [
5059
+ "Register: ",
5060
+ receipt.register.name
5061
+ ] }) : null,
5055
5062
  receipt.header.customer && /* @__PURE__ */ jsxs56(Text41, { className: "text-muted-foreground", children: [
5056
5063
  "Customer: ",
5057
5064
  receipt.header.customer
@@ -5078,6 +5085,7 @@ function Receipt({
5078
5085
  index
5079
5086
  )),
5080
5087
  row("Change", money(receipt.changeDueMinor)),
5088
+ footerSlot ? /* @__PURE__ */ jsx98(View63, { testID: "receipt-footer", className: "mt-3 items-center border-t border-dashed border-border pt-3", children: footerSlot }) : null,
5081
5089
  /* @__PURE__ */ jsxs56(View63, { dataSet: { print: "hide" }, className: "flex-row gap-4", children: [
5082
5090
  /* @__PURE__ */ jsx98(Pressable33, { accessibilityRole: "button", onPress: () => {
5083
5091
  if (typeof window !== "undefined" && typeof window.print === "function") window.print();
@@ -5277,7 +5285,7 @@ function OrdersList({ orders, onRetry, formatDate = defaultFormatDate2, footer,
5277
5285
  setRetryingIds(new Set(retrying.current));
5278
5286
  }
5279
5287
  }, className: "rounded-md bg-primary px-4 py-2", children: /* @__PURE__ */ jsx100(Text43, { className: "text-center font-semibold text-primary-foreground", children: "Retry" }) }) : null,
5280
- knownWarnings(order.warnings).map((warning, index) => /* @__PURE__ */ jsx100(View65, { className: "border-l-4 border-warning pl-2", children: /* @__PURE__ */ jsx100(Text43, { className: "text-foreground", children: warning.code === "insufficient_stock" ? `${warning.quantity} sold without stock: ${order.lines.find((line) => line.variantId === warning.variantId)?.name}` : warning.code === "tax_rate_mismatch" ? `Tax at ${(warning.ratePpm / 1e4).toLocaleString(void 0, { maximumFractionDigits: 4 })}%: store ${formatMoney17({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney17({ amount: warning.expectedMinor, currency: order.currency })}` : warning.code === "customer_ignored" ? `The online store didn't recognise the customer on this sale, so it was saved as a guest sale. Customer id: ${warning.customerId}.` : warning.code === "figures_mismatch" ? `The online store worked out different figures for this sale. ${warning.fields.map(({ field, tillMinor, serverMinor }) => `${FIGURE_LABELS.get(field) ?? field}: till ${formatMoney17({ amount: tillMinor, currency: order.currency })}, store ${formatMoney17({ amount: serverMinor, currency: order.currency })}.`).join(" ")}` : warning.bridgeMinor !== void 0 ? `Store calculated ${formatMoney17({ amount: warning.serverMinor, currency: order.currency })}; a rounding line of ${warning.bridgeMinor > 0 ? "+" : ""}${formatMoney17({ amount: warning.bridgeMinor, currency: order.currency })} brought it to ${formatMoney17({ amount: warning.expectedMinor, currency: order.currency })}` : `Store total ${formatMoney17({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney17({ amount: warning.expectedMinor, currency: order.currency })}` }) }, index))
5288
+ knownWarnings(order.warnings).filter((warning) => warning.code !== "register_session_unknown").map((warning, index) => /* @__PURE__ */ jsx100(View65, { className: "border-l-4 border-warning pl-2", children: /* @__PURE__ */ jsx100(Text43, { className: "text-foreground", children: warning.code === "insufficient_stock" ? `${warning.quantity} sold without stock: ${order.lines.find((line) => line.variantId === warning.variantId)?.name}` : warning.code === "tax_rate_mismatch" ? `Tax at ${(warning.ratePpm / 1e4).toLocaleString(void 0, { maximumFractionDigits: 4 })}%: store ${formatMoney17({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney17({ amount: warning.expectedMinor, currency: order.currency })}` : warning.code === "customer_ignored" ? `The online store didn't recognise the customer on this sale, so it was saved as a guest sale. Customer id: ${warning.customerId}.` : warning.code === "figures_mismatch" ? `The online store worked out different figures for this sale. ${warning.fields.map(({ field, tillMinor, serverMinor }) => `${FIGURE_LABELS.get(field) ?? field}: till ${formatMoney17({ amount: tillMinor, currency: order.currency })}, store ${formatMoney17({ amount: serverMinor, currency: order.currency })}.`).join(" ")}` : warning.bridgeMinor !== void 0 ? `Store calculated ${formatMoney17({ amount: warning.serverMinor, currency: order.currency })}; a rounding line of ${warning.bridgeMinor > 0 ? "+" : ""}${formatMoney17({ amount: warning.bridgeMinor, currency: order.currency })} brought it to ${formatMoney17({ amount: warning.expectedMinor, currency: order.currency })}` : `Store total ${formatMoney17({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney17({ amount: warning.expectedMinor, currency: order.currency })}` }) }, index))
5281
5289
  ] }, order.id);
5282
5290
  })
5283
5291
  ] }, section.title)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyui/components",
3
- "version": "3.5.3",
3
+ "version": "3.7.0",
4
4
  "type": "module",
5
5
  "description": "Universal POS UI components for Tally UI",
6
6
  "main": "./dist/index.js",
@@ -27,22 +27,22 @@
27
27
  "directory": "packages/components"
28
28
  },
29
29
  "peerDependencies": {
30
- "@tallyui/core": "^3.5.3",
31
- "@tallyui/primitives": "^3.5.3",
32
- "@tallyui/theme": "^3.5.3",
30
+ "@tallyui/core": "^3.7.0",
31
+ "@tallyui/primitives": "^3.7.0",
32
+ "@tallyui/theme": "^3.7.0",
33
33
  "react": ">=18",
34
34
  "react-native": ">=0.74",
35
35
  "react-native-svg": ">=15"
36
36
  },
37
37
  "dependencies": {
38
38
  "class-variance-authority": "^0.7.1",
39
- "@tallyui/pos": "3.5.3"
39
+ "@tallyui/pos": "3.7.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@tallyui/connector-medusa": "3.5.3",
43
- "@tallyui/connector-vendure": "3.5.3",
44
- "@tallyui/connector-woocommerce": "3.5.3",
45
- "@tallyui/mock-api": "3.5.3",
42
+ "@tallyui/connector-medusa": "3.7.0",
43
+ "@tallyui/connector-vendure": "3.7.0",
44
+ "@tallyui/connector-woocommerce": "3.7.0",
45
+ "@tallyui/mock-api": "3.7.0",
46
46
  "@types/react": ">=18",
47
47
  "react": "^19.2.4",
48
48
  "react-native-svg": "15.12.1",
@@ -11,6 +11,8 @@
11
11
  * Copy is plain English (no i18n keys, ADR-064's neutral-copy rule).
12
12
  */
13
13
 
14
+ import type { RegisterSession } from '@tallyui/pos';
15
+
14
16
  export type RegisterBarPill =
15
17
  | 'Choose a register'
16
18
  | 'Close not finished'
@@ -38,7 +40,8 @@ export function describeRegisterBarPill({
38
40
  /** The register this till is bound to, or `null` when it isn't bound yet. */
39
41
  registerId: string | null;
40
42
  online: boolean;
41
- sessionStatus?: 'open' | 'counting' | 'closed' | null;
43
+ /** `conflict` and `superseded` (ADR-078 decision 9) get no pill of their own yet; step 3b decides theirs. */
44
+ sessionStatus?: RegisterSession['status'] | null;
42
45
  overdue?: boolean;
43
46
  approvalRequired?: boolean;
44
47
  /** The store uses register sessions at all (`useRegisterSession`'s `enabled`). */
@@ -105,7 +105,7 @@ export function OrdersList({ orders, onRetry, formatDate = defaultFormatDate, fo
105
105
  }} className="rounded-md bg-primary px-4 py-2">
106
106
  <Text className="text-center font-semibold text-primary-foreground">Retry</Text>
107
107
  </Pressable> : null}
108
- {knownWarnings(order.warnings).map((warning, index) => <View key={index} className="border-l-4 border-warning pl-2"><Text className="text-foreground">
108
+ {knownWarnings(order.warnings).filter((warning) => warning.code !== 'register_session_unknown').map((warning, index) => <View key={index} className="border-l-4 border-warning pl-2"><Text className="text-foreground">
109
109
  {warning.code === 'insufficient_stock'
110
110
  ? `${warning.quantity} sold without stock: ${order.lines.find((line) => line.variantId === warning.variantId)?.name}`
111
111
  : warning.code === 'tax_rate_mismatch'
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, type ReactNode } from 'react';
2
2
  import { Pressable, Text, View } from 'react-native';
3
3
  import { formatMoney } from '@tallyui/core';
4
4
  import { buildReceiptData, type SentOrder } from '@tallyui/pos';
@@ -11,17 +11,21 @@ const defaultFormatDate = (iso: string) => new Intl.DateTimeFormat(undefined, {
11
11
 
12
12
  /** `store`/`taxLabel`/`topInset`/`formatDate` replace the app's Medusa settings, hard-coded VAT (default matches
13
13
  * TV6a's `Cart`), outbox strip and date util, so `Receipt` stays platform-neutral (ADR-052). */
14
- export function Receipt({ order, posOrder, store, cashier, registerId, newSale, taxLabel = (ratePpm: number) => `Tax ${ratePpm / 10000}%`,
15
- topInset = 0, formatDate = defaultFormatDate }: {
14
+ export function Receipt({ order, posOrder, store, cashier, registerId, registerName, newSale, taxLabel = (ratePpm: number) => `Tax ${ratePpm / 10000}%`,
15
+ topInset = 0, formatDate = defaultFormatDate, footerSlot }: {
16
16
  order: SentOrder; store: { name: string; address?: string }; cashier: string; registerId: string; newSale: () => void;
17
+ /** The register's name, printed as `Register: <name>`; the id is never printed. */
18
+ registerName?: string;
17
19
  /** The finalized order (useSale's receipt stage has it). With it the receipt prints its reference and time, the
18
20
  * same as Orders; without it (a preview before finalize) the sale id is marked as a draft. */
19
21
  posOrder?: PosOrder;
20
22
  taxLabel?: (ratePpm: number) => string; topInset?: number; formatDate?: (iso: string) => string;
23
+ /** Prints after the Change row and above the on-screen buttons (e.g. an app's register/software block). */
24
+ footerSlot?: ReactNode;
21
25
  }) {
22
26
  useEffect(injectPrintStyle, []);
23
27
  const receipt = buildReceiptData(order, {
24
- storeName: store.name, storeAddress: store.address, cashier, register: registerId,
28
+ storeName: store.name, storeAddress: store.address, cashier, register: registerId, registerName,
25
29
  });
26
30
  const money = (amount: number) => formatMoney({ amount, currency: receipt.currency }) ?? '';
27
31
  const row = (label: string, amount: string, bold = false, key?: number) => <View key={key} accessibilityLabel={`${label}: ${amount}`} className="flex-row justify-between gap-4">
@@ -40,6 +44,7 @@ export function Receipt({ order, posOrder, store, cashier, registerId, newSale,
40
44
  <Text testID="receipt-order" className="text-foreground">{posOrder ? `Order ${orderReference(posOrder)}` : `Order ${receipt.header.orderNumber.slice(-8)} (draft)`}</Text>
41
45
  <Text className="text-muted-foreground">{formatDate(posOrder?.createdAt ?? receipt.header.date)}</Text>
42
46
  <Text className="text-muted-foreground">Cashier: {receipt.header.cashier}</Text>
47
+ {receipt.register.name ? <Text testID="receipt-register" className="text-muted-foreground">Register: {receipt.register.name}</Text> : null}
43
48
  {receipt.header.customer && <Text className="text-muted-foreground">Customer: {receipt.header.customer}</Text>}
44
49
  {receipt.lineItems.map((line, index) => <View key={index}>
45
50
  <Text className="text-foreground">{line.name}</Text>
@@ -61,6 +66,7 @@ export function Receipt({ order, posOrder, store, cashier, registerId, newSale,
61
66
  {receipt.payments.map((payment, index) => row(payment.method === 'cash' ? 'Cash tendered' : 'Card terminal',
62
67
  money(payment.amountMinor) + (payment.reference ? ` · ${payment.reference}` : ''), false, index))}
63
68
  {row('Change', money(receipt.changeDueMinor))}
69
+ {footerSlot ? <View testID="receipt-footer" className="mt-3 items-center border-t border-dashed border-border pt-3">{footerSlot}</View> : null}
64
70
  <View dataSet={{ print: 'hide' }} className="flex-row gap-4">
65
71
  <Pressable accessibilityRole="button" onPress={() => { if (typeof window !== 'undefined' && typeof window.print === 'function') window.print(); }} className="rounded-md border border-border bg-card px-4 py-3">
66
72
  <Text className="text-center text-foreground">Print receipt</Text>