@bacon-pos/ui 0.9.0 → 0.10.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/lib/commonjs/components/EmptyState/EmptyState.js +88 -0
- package/lib/commonjs/components/EmptyState/EmptyState.js.map +1 -0
- package/lib/commonjs/components/EmptyState/EmptyState.stories.js +46 -0
- package/lib/commonjs/components/EmptyState/EmptyState.stories.js.map +1 -0
- package/lib/commonjs/components/EmptyState/EmptyState.test.js +40 -0
- package/lib/commonjs/components/EmptyState/EmptyState.test.js.map +1 -0
- package/lib/commonjs/components/EmptyState/index.js +13 -0
- package/lib/commonjs/components/EmptyState/index.js.map +1 -0
- package/lib/commonjs/components/Sidebar/Sidebar.js +264 -0
- package/lib/commonjs/components/Sidebar/Sidebar.js.map +1 -0
- package/lib/commonjs/components/Sidebar/Sidebar.stories.js +229 -0
- package/lib/commonjs/components/Sidebar/Sidebar.stories.js.map +1 -0
- package/lib/commonjs/components/Sidebar/Sidebar.test.js +131 -0
- package/lib/commonjs/components/Sidebar/Sidebar.test.js.map +1 -0
- package/lib/commonjs/components/Sidebar/index.js +13 -0
- package/lib/commonjs/components/Sidebar/index.js.map +1 -0
- package/lib/commonjs/components/Table/DataTable.js +149 -0
- package/lib/commonjs/components/Table/DataTable.js.map +1 -0
- package/lib/commonjs/components/Table/DataTable.stories.js +214 -0
- package/lib/commonjs/components/Table/DataTable.stories.js.map +1 -0
- package/lib/commonjs/components/Table/DataTable.test.js +143 -0
- package/lib/commonjs/components/Table/DataTable.test.js.map +1 -0
- package/lib/commonjs/components/Table/Table.js +520 -0
- package/lib/commonjs/components/Table/Table.js.map +1 -0
- package/lib/commonjs/components/Table/Table.stories.js +385 -0
- package/lib/commonjs/components/Table/Table.stories.js.map +1 -0
- package/lib/commonjs/components/Table/Table.test.js +278 -0
- package/lib/commonjs/components/Table/Table.test.js.map +1 -0
- package/lib/commonjs/components/Table/index.js +20 -0
- package/lib/commonjs/components/Table/index.js.map +1 -0
- package/lib/commonjs/components/brand/BaconLogo.js +29 -0
- package/lib/commonjs/components/brand/BaconLogo.js.map +1 -0
- package/lib/commonjs/components/brand/index.js +7 -0
- package/lib/commonjs/components/brand/index.js.map +1 -1
- package/lib/commonjs/components/index.js +50 -17
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/module/components/EmptyState/EmptyState.js +84 -0
- package/lib/module/components/EmptyState/EmptyState.js.map +1 -0
- package/lib/module/components/EmptyState/EmptyState.stories.js +42 -0
- package/lib/module/components/EmptyState/EmptyState.stories.js.map +1 -0
- package/lib/module/components/EmptyState/EmptyState.test.js +40 -0
- package/lib/module/components/EmptyState/EmptyState.test.js.map +1 -0
- package/lib/module/components/EmptyState/index.js +4 -0
- package/lib/module/components/EmptyState/index.js.map +1 -0
- package/lib/module/components/Sidebar/Sidebar.js +260 -0
- package/lib/module/components/Sidebar/Sidebar.js.map +1 -0
- package/lib/module/components/Sidebar/Sidebar.stories.js +225 -0
- package/lib/module/components/Sidebar/Sidebar.stories.js.map +1 -0
- package/lib/module/components/Sidebar/Sidebar.test.js +131 -0
- package/lib/module/components/Sidebar/Sidebar.test.js.map +1 -0
- package/lib/module/components/Sidebar/index.js +4 -0
- package/lib/module/components/Sidebar/index.js.map +1 -0
- package/lib/module/components/Table/DataTable.js +145 -0
- package/lib/module/components/Table/DataTable.js.map +1 -0
- package/lib/module/components/Table/DataTable.stories.js +210 -0
- package/lib/module/components/Table/DataTable.stories.js.map +1 -0
- package/lib/module/components/Table/DataTable.test.js +143 -0
- package/lib/module/components/Table/DataTable.test.js.map +1 -0
- package/lib/module/components/Table/Table.js +516 -0
- package/lib/module/components/Table/Table.js.map +1 -0
- package/lib/module/components/Table/Table.stories.js +381 -0
- package/lib/module/components/Table/Table.stories.js.map +1 -0
- package/lib/module/components/Table/Table.test.js +278 -0
- package/lib/module/components/Table/Table.test.js.map +1 -0
- package/lib/module/components/Table/index.js +5 -0
- package/lib/module/components/Table/index.js.map +1 -0
- package/lib/module/components/brand/BaconLogo.js +25 -0
- package/lib/module/components/brand/BaconLogo.js.map +1 -0
- package/lib/module/components/brand/index.js +1 -0
- package/lib/module/components/brand/index.js.map +1 -1
- package/lib/module/components/index.js +3 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/typescript/commonjs/components/EmptyState/EmptyState.d.ts +30 -0
- package/lib/typescript/commonjs/components/EmptyState/EmptyState.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/EmptyState/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/EmptyState/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Sidebar/Sidebar.d.ts +57 -0
- package/lib/typescript/commonjs/components/Sidebar/Sidebar.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Sidebar/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/Sidebar/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Table/DataTable.d.ts +53 -0
- package/lib/typescript/commonjs/components/Table/DataTable.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Table/Table.d.ts +85 -0
- package/lib/typescript/commonjs/components/Table/Table.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Table/index.d.ts +5 -0
- package/lib/typescript/commonjs/components/Table/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/brand/BaconLogo.d.ts +13 -0
- package/lib/typescript/commonjs/components/brand/BaconLogo.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/brand/index.d.ts +1 -0
- package/lib/typescript/commonjs/components/brand/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
- package/lib/typescript/module/components/EmptyState/EmptyState.d.ts +30 -0
- package/lib/typescript/module/components/EmptyState/EmptyState.d.ts.map +1 -0
- package/lib/typescript/module/components/EmptyState/index.d.ts +3 -0
- package/lib/typescript/module/components/EmptyState/index.d.ts.map +1 -0
- package/lib/typescript/module/components/Sidebar/Sidebar.d.ts +57 -0
- package/lib/typescript/module/components/Sidebar/Sidebar.d.ts.map +1 -0
- package/lib/typescript/module/components/Sidebar/index.d.ts +3 -0
- package/lib/typescript/module/components/Sidebar/index.d.ts.map +1 -0
- package/lib/typescript/module/components/Table/DataTable.d.ts +53 -0
- package/lib/typescript/module/components/Table/DataTable.d.ts.map +1 -0
- package/lib/typescript/module/components/Table/Table.d.ts +85 -0
- package/lib/typescript/module/components/Table/Table.d.ts.map +1 -0
- package/lib/typescript/module/components/Table/index.d.ts +5 -0
- package/lib/typescript/module/components/Table/index.d.ts.map +1 -0
- package/lib/typescript/module/components/brand/BaconLogo.d.ts +13 -0
- package/lib/typescript/module/components/brand/BaconLogo.d.ts.map +1 -0
- package/lib/typescript/module/components/brand/index.d.ts +1 -0
- package/lib/typescript/module/components/brand/index.d.ts.map +1 -1
- package/lib/typescript/module/components/index.d.ts +3 -0
- package/lib/typescript/module/components/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/EmptyState/EmptyState.tsx +91 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.tsx +320 -0
- package/src/components/Sidebar/index.ts +2 -0
- package/src/components/Table/DataTable.tsx +173 -0
- package/src/components/Table/Table.tsx +556 -0
- package/src/components/Table/index.ts +10 -0
- package/src/components/brand/BaconLogo.tsx +25 -0
- package/src/components/brand/index.ts +1 -0
- package/src/components/index.ts +3 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
export type ColumnAlign = 'left' | 'center' | 'right';
|
|
4
|
+
export type SortDirection = 'asc' | 'desc';
|
|
5
|
+
export interface TableSort {
|
|
6
|
+
key: string;
|
|
7
|
+
direction: SortDirection;
|
|
8
|
+
}
|
|
9
|
+
export interface TableColumn<T> {
|
|
10
|
+
/** Unique column id. */
|
|
11
|
+
key: string;
|
|
12
|
+
/** Header content (string or node). */
|
|
13
|
+
header: ReactNode;
|
|
14
|
+
/** Cell renderer. Defaults to `String(row[key])`. Return a string/number or any node (e.g. a `Badge`). */
|
|
15
|
+
render?: (row: T, index: number) => ReactNode;
|
|
16
|
+
/** Flex grow. Defaults to 1. Ignored when `width` is set. */
|
|
17
|
+
flex?: number;
|
|
18
|
+
/** Fixed column width in px. */
|
|
19
|
+
width?: number;
|
|
20
|
+
/** Text/content alignment. Defaults to `left`. */
|
|
21
|
+
align?: ColumnAlign;
|
|
22
|
+
/** Enable sorting on this column (needs `sortAccessor`). */
|
|
23
|
+
sortable?: boolean;
|
|
24
|
+
/** Value used to sort this column when sorting internally. */
|
|
25
|
+
sortAccessor?: (row: T) => string | number;
|
|
26
|
+
}
|
|
27
|
+
export interface TableProps<T> {
|
|
28
|
+
columns: TableColumn<T>[];
|
|
29
|
+
data: T[];
|
|
30
|
+
/** Stable key per row. */
|
|
31
|
+
keyExtractor: (row: T, index: number) => string;
|
|
32
|
+
/** Row press (non-expandable tables). Expandable rows toggle on press instead. */
|
|
33
|
+
onRowPress?: (row: T, index: number) => void;
|
|
34
|
+
/** Expandable (accordion) rows — mutually exclusive with `renderActions`. */
|
|
35
|
+
expandable?: boolean;
|
|
36
|
+
/** Content shown when a row is expanded. */
|
|
37
|
+
renderExpanded?: (row: T, index: number) => ReactNode;
|
|
38
|
+
/** One open row at a time (`single`) or many (`multiple`). Defaults to `single`. */
|
|
39
|
+
expansionMode?: 'single' | 'multiple';
|
|
40
|
+
/** Controlled expanded row keys. */
|
|
41
|
+
expandedKeys?: string[];
|
|
42
|
+
defaultExpandedKeys?: string[];
|
|
43
|
+
onExpandedChange?: (keys: string[]) => void;
|
|
44
|
+
/** Trailing per-row actions (e.g. edit/delete) — mutually exclusive with `expandable`. */
|
|
45
|
+
renderActions?: (row: T, index: number) => ReactNode;
|
|
46
|
+
/** Width reserved for the actions column. Defaults to 96. */
|
|
47
|
+
actionsWidth?: number;
|
|
48
|
+
/** Row selection via checkboxes. */
|
|
49
|
+
selectable?: boolean;
|
|
50
|
+
selectedKeys?: string[];
|
|
51
|
+
defaultSelectedKeys?: string[];
|
|
52
|
+
onSelectionChange?: (keys: string[]) => void;
|
|
53
|
+
/** Controlled sort. Omit for internal sorting via each column's `sortAccessor`. */
|
|
54
|
+
sort?: TableSort;
|
|
55
|
+
defaultSort?: TableSort;
|
|
56
|
+
onSortChange?: (sort: TableSort) => void;
|
|
57
|
+
/** Rounded, clipped container. Defaults to true. */
|
|
58
|
+
rounded?: boolean;
|
|
59
|
+
/** Cap the body height; rows scroll with the elegant indicator, header stays. */
|
|
60
|
+
maxHeight?: number;
|
|
61
|
+
scrollbarColor?: string;
|
|
62
|
+
/** Show a loading placeholder instead of rows. */
|
|
63
|
+
loading?: boolean;
|
|
64
|
+
/** Loading style: a centered `spinner` (default) or shimmering `skeleton` rows. */
|
|
65
|
+
loadingVariant?: 'spinner' | 'skeleton';
|
|
66
|
+
/** Text under the spinner. Defaults to "Loading data"; pass `null` to hide it. */
|
|
67
|
+
loadingText?: string | null;
|
|
68
|
+
/** Number of skeleton rows. Defaults to 5. */
|
|
69
|
+
skeletonRows?: number;
|
|
70
|
+
/** Shown when there is no data (and not loading). */
|
|
71
|
+
empty?: ReactNode;
|
|
72
|
+
/** Below this width the table renders each row as a card. Defaults to 640. */
|
|
73
|
+
mobileBreakpoint?: number;
|
|
74
|
+
style?: StyleProp<ViewStyle>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Table — a flexible, token-driven data table (Figma "Table"). Columns define
|
|
78
|
+
* width/flex, alignment and cell renderers (text, `Badge`, anything). Supports
|
|
79
|
+
* sortable headers, checkbox selection, and accordion rows (expand on row press)
|
|
80
|
+
* OR a trailing actions column (the two are mutually exclusive). A sticky header
|
|
81
|
+
* with an elegant scrolling body (`maxHeight`), loading and empty states, and a
|
|
82
|
+
* card fallback below `mobileBreakpoint`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function Table<T>({ columns, data, keyExtractor, onRowPress, expandable, renderExpanded, expansionMode, expandedKeys, defaultExpandedKeys, onExpandedChange, renderActions, actionsWidth, selectable, selectedKeys, defaultSelectedKeys, onSelectionChange, sort, defaultSort, onSortChange, rounded, maxHeight, scrollbarColor, loading, loadingVariant, loadingText, skeletonRows, empty, mobileBreakpoint, style, }: TableProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
85
|
+
//# sourceMappingURL=Table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Table.d.ts","sourceRoot":"","sources":["../../../../../src/components/Table/Table.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Table } from './Table';
|
|
2
|
+
export type { TableProps, TableColumn, TableSort, ColumnAlign, SortDirection, } from './Table';
|
|
3
|
+
export { DataTable } from './DataTable';
|
|
4
|
+
export type { DataTableProps, DataTableTab, DataTablePagination } from './DataTable';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/Table/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EACV,UAAU,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface BaconLogoProps {
|
|
2
|
+
/** Rendered height in px; width follows the mark's aspect ratio. Defaults to 40. */
|
|
3
|
+
height?: number;
|
|
4
|
+
/** Mark color. Defaults to the Bacon red. */
|
|
5
|
+
color?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The Bacon POS brand mark (the red "B"). Rendered at its natural aspect ratio
|
|
9
|
+
* and not tinted by default — the brand color is preserved. Used as the default
|
|
10
|
+
* logo in `Sidebar`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function BaconLogo({ height, color }: BaconLogoProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=BaconLogo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaconLogo.d.ts","sourceRoot":"","sources":["../../../../../src/components/brand/BaconLogo.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAW,EAAE,KAAiB,EAAE,EAAE,cAAc,2CAG3E"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Logo, type LogoProps, type LogoName } from './Logo';
|
|
2
|
+
export { BaconLogo, type BaconLogoProps } from './BaconLogo';
|
|
2
3
|
export { Flag, type FlagProps, type FlagCountry } from './Flag';
|
|
3
4
|
export { graphics, type GraphicName, type Graphic } from './registry';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/brand/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/brand/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -9,6 +9,7 @@ export * from './CodeField';
|
|
|
9
9
|
export * from './CodeInput';
|
|
10
10
|
export * from './Dialog';
|
|
11
11
|
export * from './Dropdown';
|
|
12
|
+
export * from './EmptyState';
|
|
12
13
|
export * from './Icon';
|
|
13
14
|
export * from './IconButton';
|
|
14
15
|
export * from './Input';
|
|
@@ -20,9 +21,11 @@ export * from './ProgressBar';
|
|
|
20
21
|
export * from './QuantityStepper';
|
|
21
22
|
export * from './Radio';
|
|
22
23
|
export * from './Select';
|
|
24
|
+
export * from './Sidebar';
|
|
23
25
|
export * from './Spinner';
|
|
24
26
|
export * from './Surface';
|
|
25
27
|
export * from './Switch';
|
|
28
|
+
export * from './Table';
|
|
26
29
|
export * from './Text';
|
|
27
30
|
export * from './Toast';
|
|
28
31
|
export * from './UserChip';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAE3B,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAE3B,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { View, type StyleProp, type ViewStyle } from 'react-native';
|
|
2
|
+
import { useTheme } from '../../theme';
|
|
3
|
+
import { Button, type ButtonVariant } from '../Button/Button';
|
|
4
|
+
import { Icon } from '../Icon/Icon';
|
|
5
|
+
import type { IconName } from '../Icon/icons.generated';
|
|
6
|
+
import { Text } from '../Text/Text';
|
|
7
|
+
|
|
8
|
+
export interface EmptyStateAction {
|
|
9
|
+
label: string;
|
|
10
|
+
onPress: () => void;
|
|
11
|
+
icon?: IconName;
|
|
12
|
+
variant?: ButtonVariant;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface EmptyStateProps {
|
|
16
|
+
/** Icon shown in the badge above the title. */
|
|
17
|
+
icon?: IconName;
|
|
18
|
+
/** Title line. */
|
|
19
|
+
title: string;
|
|
20
|
+
/** Supporting description below the title. */
|
|
21
|
+
description?: string;
|
|
22
|
+
/** Optional call-to-action button. */
|
|
23
|
+
action?: EmptyStateAction;
|
|
24
|
+
/** Vertical padding around the block. Defaults to 32. */
|
|
25
|
+
paddingVertical?: number;
|
|
26
|
+
/** Override the container style. */
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Empty state — a centered icon, title, description and optional action button.
|
|
32
|
+
* Used by `Table`/`DataTable` when there's no data, and reusable anywhere a
|
|
33
|
+
* "nothing here yet" message is needed.
|
|
34
|
+
*/
|
|
35
|
+
export function EmptyState({
|
|
36
|
+
icon = 'no-photo',
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
39
|
+
action,
|
|
40
|
+
paddingVertical = 32,
|
|
41
|
+
style,
|
|
42
|
+
}: EmptyStateProps) {
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
const variant = action?.variant ?? 'inverse';
|
|
45
|
+
const actionIconColor =
|
|
46
|
+
variant === 'default'
|
|
47
|
+
? theme.colors.action.primaryForeground
|
|
48
|
+
: variant === 'secondary'
|
|
49
|
+
? theme.colors.action.secondaryForeground
|
|
50
|
+
: theme.colors.action.inverseForeground;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View style={[{ alignItems: 'center', gap: 12, paddingVertical, paddingHorizontal: 24 }, style]}>
|
|
54
|
+
<View
|
|
55
|
+
style={{
|
|
56
|
+
width: 48,
|
|
57
|
+
height: 48,
|
|
58
|
+
borderRadius: 24,
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
justifyContent: 'center',
|
|
61
|
+
backgroundColor: theme.colors.background.subtle,
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
<Icon name={icon} size={24} color="tertiary" />
|
|
65
|
+
</View>
|
|
66
|
+
|
|
67
|
+
<View style={{ alignItems: 'center', gap: 4 }}>
|
|
68
|
+
<Text variant="bodyLarge" weight="medium" color="primary" align="center">
|
|
69
|
+
{title}
|
|
70
|
+
</Text>
|
|
71
|
+
{description ? (
|
|
72
|
+
<Text variant="bodyMedium" color="tertiary" align="center" style={{ maxWidth: 420 }}>
|
|
73
|
+
{description}
|
|
74
|
+
</Text>
|
|
75
|
+
) : null}
|
|
76
|
+
</View>
|
|
77
|
+
|
|
78
|
+
{action ? (
|
|
79
|
+
<View style={{ marginTop: 4 }}>
|
|
80
|
+
<Button
|
|
81
|
+
label={action.label}
|
|
82
|
+
variant={action.variant ?? 'inverse'}
|
|
83
|
+
size="sm"
|
|
84
|
+
onPress={action.onPress}
|
|
85
|
+
leftIcon={action.icon ? <Icon name={action.icon} size={20} color={actionIconColor} /> : undefined}
|
|
86
|
+
/>
|
|
87
|
+
</View>
|
|
88
|
+
) : null}
|
|
89
|
+
</View>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Easing,
|
|
5
|
+
Pressable,
|
|
6
|
+
View,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import { useTheme } from '../../theme';
|
|
11
|
+
import { BaconLogo } from '../brand/BaconLogo';
|
|
12
|
+
import { Icon } from '../Icon/Icon';
|
|
13
|
+
import type { IconName } from '../Icon/icons.generated';
|
|
14
|
+
import { IconButton } from '../IconButton/IconButton';
|
|
15
|
+
import { Text } from '../Text/Text';
|
|
16
|
+
import { ScrollArea } from '../_internal/ScrollArea';
|
|
17
|
+
|
|
18
|
+
export interface SidebarItem {
|
|
19
|
+
/** Stable key; also the active identifier. */
|
|
20
|
+
key: string;
|
|
21
|
+
/** Button text (hidden when collapsed). */
|
|
22
|
+
label: string;
|
|
23
|
+
/** Leading icon. When omitted, expanded reserves the icon space and collapsed shows the label's initial. */
|
|
24
|
+
icon?: IconName;
|
|
25
|
+
/** Optional icon shown when the item is active (e.g. a filled variant). */
|
|
26
|
+
activeIcon?: IconName;
|
|
27
|
+
/** Press handler. */
|
|
28
|
+
onPress?: () => void;
|
|
29
|
+
/** Dim and block the item. */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SidebarProps {
|
|
34
|
+
/** Navigation items — rendered top-to-bottom. Defaults to none. */
|
|
35
|
+
items?: SidebarItem[];
|
|
36
|
+
/** Active item key (controlled). */
|
|
37
|
+
activeKey?: string;
|
|
38
|
+
/** Active item key for the uncontrolled case. */
|
|
39
|
+
defaultActiveKey?: string;
|
|
40
|
+
/** Called with the pressed item's key. */
|
|
41
|
+
onChange?: (key: string) => void;
|
|
42
|
+
|
|
43
|
+
/** Collapsed (icons only) state — controlled. */
|
|
44
|
+
collapsed?: boolean;
|
|
45
|
+
/** Initial collapsed state (uncontrolled). Defaults to false. */
|
|
46
|
+
defaultCollapsed?: boolean;
|
|
47
|
+
/** Called when the collapse state changes. */
|
|
48
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
49
|
+
/** Show the built-in expand/collapse toggle. Defaults to true. */
|
|
50
|
+
showToggle?: boolean;
|
|
51
|
+
|
|
52
|
+
/** Logo shown at the top. Defaults to the Bacon mark. */
|
|
53
|
+
logo?: ReactNode;
|
|
54
|
+
/** Bottom supporting text (e.g. a version). Defaults to none. */
|
|
55
|
+
footer?: string;
|
|
56
|
+
|
|
57
|
+
/** Expanded width. Defaults to 284 (Figma). */
|
|
58
|
+
expandedWidth?: number;
|
|
59
|
+
/** Collapsed width. Defaults to 122 (Figma). */
|
|
60
|
+
collapsedWidth?: number;
|
|
61
|
+
/** Scroll indicator color. Defaults to a subtle neutral. */
|
|
62
|
+
scrollbarColor?: string;
|
|
63
|
+
/** Override the container style. */
|
|
64
|
+
style?: StyleProp<ViewStyle>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ITEM_HEIGHT = 74;
|
|
68
|
+
const ITEM_GAP = 16;
|
|
69
|
+
const PADDING = 24;
|
|
70
|
+
const TOGGLE_SIZE = 40; // IconButton size "sm"
|
|
71
|
+
|
|
72
|
+
function SidebarButton({
|
|
73
|
+
item,
|
|
74
|
+
active,
|
|
75
|
+
collapsed,
|
|
76
|
+
onPress,
|
|
77
|
+
}: {
|
|
78
|
+
item: SidebarItem;
|
|
79
|
+
active: boolean;
|
|
80
|
+
collapsed: boolean;
|
|
81
|
+
onPress: () => void;
|
|
82
|
+
}) {
|
|
83
|
+
const theme = useTheme();
|
|
84
|
+
const [hovered, setHovered] = useState(false);
|
|
85
|
+
const disabled = item.disabled;
|
|
86
|
+
|
|
87
|
+
const backgroundColor = active
|
|
88
|
+
? theme.colors.action.secondary
|
|
89
|
+
: hovered && !disabled
|
|
90
|
+
? theme.colors.background.muted
|
|
91
|
+
: 'transparent';
|
|
92
|
+
const contentColor = active
|
|
93
|
+
? theme.colors.action.secondaryForeground
|
|
94
|
+
: theme.colors.text.secondary;
|
|
95
|
+
const iconColor = active ? theme.colors.action.secondaryForeground : 'secondary';
|
|
96
|
+
const iconName = active && item.activeIcon ? item.activeIcon : item.icon;
|
|
97
|
+
const initial = item.label.trim().charAt(0).toUpperCase();
|
|
98
|
+
|
|
99
|
+
// Leading slot is always a fixed 24×24 box (flexShrink: 0) so the icon never
|
|
100
|
+
// shrinks next to a long label and labels stay aligned with or without an icon.
|
|
101
|
+
// No icon → reserve the space when expanded; show the initial when collapsed.
|
|
102
|
+
const leadingBox: ViewStyle = {
|
|
103
|
+
width: 24,
|
|
104
|
+
height: 24,
|
|
105
|
+
flexShrink: 0,
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
justifyContent: 'center',
|
|
108
|
+
};
|
|
109
|
+
const leading = (
|
|
110
|
+
<View style={leadingBox}>
|
|
111
|
+
{iconName ? (
|
|
112
|
+
<Icon name={iconName} size={24} color={iconColor} />
|
|
113
|
+
) : collapsed ? (
|
|
114
|
+
<Text variant="bodyLarge" weight="medium" color={contentColor}>
|
|
115
|
+
{initial}
|
|
116
|
+
</Text>
|
|
117
|
+
) : null}
|
|
118
|
+
</View>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Pressable
|
|
123
|
+
onPress={disabled ? undefined : onPress}
|
|
124
|
+
onHoverIn={() => setHovered(true)}
|
|
125
|
+
onHoverOut={() => setHovered(false)}
|
|
126
|
+
disabled={disabled}
|
|
127
|
+
accessibilityRole="tab"
|
|
128
|
+
accessibilityState={{ selected: active, disabled }}
|
|
129
|
+
accessibilityLabel={item.label}
|
|
130
|
+
style={{
|
|
131
|
+
flexDirection: 'row',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
justifyContent: collapsed ? 'center' : 'flex-start',
|
|
134
|
+
gap: 10,
|
|
135
|
+
height: ITEM_HEIGHT,
|
|
136
|
+
paddingHorizontal: PADDING,
|
|
137
|
+
borderRadius: theme.radii.pill,
|
|
138
|
+
borderWidth: active ? 1 : 0,
|
|
139
|
+
borderColor: active ? theme.colors.border.default : 'transparent',
|
|
140
|
+
backgroundColor,
|
|
141
|
+
opacity: disabled ? 0.5 : 1,
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{leading}
|
|
145
|
+
{!collapsed ? (
|
|
146
|
+
<Text
|
|
147
|
+
variant="bodyLarge"
|
|
148
|
+
weight="medium"
|
|
149
|
+
color={contentColor}
|
|
150
|
+
numberOfLines={1}
|
|
151
|
+
ellipsizeMode="tail"
|
|
152
|
+
style={{ flexShrink: 1, minWidth: 0 }}
|
|
153
|
+
>
|
|
154
|
+
{item.label}
|
|
155
|
+
</Text>
|
|
156
|
+
) : null}
|
|
157
|
+
</Pressable>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sidebar — the app's main navigation drawer (Figma "Sidebar"). Expands/collapses
|
|
163
|
+
* between a labelled and an icon-only rail (animated width). The logo defaults to
|
|
164
|
+
* the Bacon mark (override via `logo`); an optional `footer` shows supporting text
|
|
165
|
+
* at the bottom. Items are dynamic buttons (icon + label via props); the list
|
|
166
|
+
* scrolls with an elegant auto-hiding indicator when it overflows, leaving the
|
|
167
|
+
* footer pinned. Buttons have hover and active states to mark the current section.
|
|
168
|
+
*/
|
|
169
|
+
export function Sidebar({
|
|
170
|
+
items = [],
|
|
171
|
+
activeKey,
|
|
172
|
+
defaultActiveKey,
|
|
173
|
+
onChange,
|
|
174
|
+
collapsed: collapsedProp,
|
|
175
|
+
defaultCollapsed = false,
|
|
176
|
+
onCollapsedChange,
|
|
177
|
+
showToggle = true,
|
|
178
|
+
logo,
|
|
179
|
+
footer,
|
|
180
|
+
expandedWidth = 284,
|
|
181
|
+
collapsedWidth = 122,
|
|
182
|
+
scrollbarColor,
|
|
183
|
+
style,
|
|
184
|
+
}: SidebarProps) {
|
|
185
|
+
const theme = useTheme();
|
|
186
|
+
|
|
187
|
+
const activeControlled = activeKey !== undefined;
|
|
188
|
+
const [activeInternal, setActiveInternal] = useState(defaultActiveKey);
|
|
189
|
+
const active = activeControlled ? activeKey : activeInternal;
|
|
190
|
+
|
|
191
|
+
const collapseControlled = collapsedProp !== undefined;
|
|
192
|
+
const [collapsedInternal, setCollapsedInternal] = useState(defaultCollapsed);
|
|
193
|
+
const collapsed = collapseControlled ? collapsedProp : collapsedInternal;
|
|
194
|
+
|
|
195
|
+
const width = useRef(new Animated.Value(collapsed ? collapsedWidth : expandedWidth)).current;
|
|
196
|
+
// `visualCollapsed` drives the layout (labels, alignment, footer). It lags on
|
|
197
|
+
// collapse — labels stay mounted (clipped by overflow) until the width finishes
|
|
198
|
+
// shrinking — and leads on expand, so both directions animate without a jump.
|
|
199
|
+
const [visualCollapsed, setVisualCollapsed] = useState(collapsed);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (!collapsed) setVisualCollapsed(false);
|
|
202
|
+
const anim = Animated.timing(width, {
|
|
203
|
+
toValue: collapsed ? collapsedWidth : expandedWidth,
|
|
204
|
+
duration: 220,
|
|
205
|
+
easing: Easing.out(Easing.ease),
|
|
206
|
+
useNativeDriver: false, // width
|
|
207
|
+
});
|
|
208
|
+
anim.start(({ finished }) => {
|
|
209
|
+
if (finished && collapsed) setVisualCollapsed(true);
|
|
210
|
+
});
|
|
211
|
+
return () => anim.stop();
|
|
212
|
+
}, [collapsed, collapsedWidth, expandedWidth, width]);
|
|
213
|
+
|
|
214
|
+
// The toggle arrow spins in place (0° → 180°) instead of moving, so collapsing
|
|
215
|
+
// never nudges it. Driven by intent (`collapsed`), in sync with the width.
|
|
216
|
+
const arrowRot = useRef(new Animated.Value(collapsed ? 1 : 0)).current;
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
const anim = Animated.timing(arrowRot, {
|
|
219
|
+
toValue: collapsed ? 1 : 0,
|
|
220
|
+
duration: 220,
|
|
221
|
+
easing: Easing.out(Easing.ease),
|
|
222
|
+
useNativeDriver: true,
|
|
223
|
+
});
|
|
224
|
+
anim.start();
|
|
225
|
+
return () => anim.stop();
|
|
226
|
+
}, [collapsed, arrowRot]);
|
|
227
|
+
const arrowRotate = arrowRot.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '180deg'] });
|
|
228
|
+
|
|
229
|
+
// Glide the toggle from flush-left (expanded) to centered on the icon axis
|
|
230
|
+
// (collapsed), tracking the width so it never jumps.
|
|
231
|
+
const toggleTranslateX = width.interpolate({
|
|
232
|
+
inputRange: [collapsedWidth, expandedWidth],
|
|
233
|
+
outputRange: [Math.max(0, (collapsedWidth - 2 * PADDING - TOGGLE_SIZE) / 2), 0],
|
|
234
|
+
extrapolate: 'clamp',
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const selectItem = (item: SidebarItem) => {
|
|
238
|
+
if (!activeControlled) setActiveInternal(item.key);
|
|
239
|
+
item.onPress?.();
|
|
240
|
+
onChange?.(item.key);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const toggleCollapsed = () => {
|
|
244
|
+
const next = !collapsed;
|
|
245
|
+
if (!collapseControlled) setCollapsedInternal(next);
|
|
246
|
+
onCollapsedChange?.(next);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const logoNode = logo ?? <BaconLogo height={visualCollapsed ? 36 : 40} />;
|
|
250
|
+
const toggle = showToggle ? (
|
|
251
|
+
<Animated.View style={{ transform: [{ translateX: toggleTranslateX }] }}>
|
|
252
|
+
<Animated.View style={{ transform: [{ rotate: arrowRotate }] }}>
|
|
253
|
+
<IconButton
|
|
254
|
+
icon="arrow-left"
|
|
255
|
+
accessibilityLabel={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
256
|
+
variant="ghost"
|
|
257
|
+
size="sm"
|
|
258
|
+
onPress={toggleCollapsed}
|
|
259
|
+
/>
|
|
260
|
+
</Animated.View>
|
|
261
|
+
</Animated.View>
|
|
262
|
+
) : null;
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<Animated.View
|
|
266
|
+
style={[
|
|
267
|
+
{
|
|
268
|
+
width,
|
|
269
|
+
height: '100%',
|
|
270
|
+
backgroundColor: theme.colors.background.default,
|
|
271
|
+
paddingTop: 48,
|
|
272
|
+
paddingBottom: PADDING,
|
|
273
|
+
paddingHorizontal: PADDING,
|
|
274
|
+
overflow: 'hidden',
|
|
275
|
+
},
|
|
276
|
+
style,
|
|
277
|
+
]}
|
|
278
|
+
>
|
|
279
|
+
{/* Logo */}
|
|
280
|
+
<View
|
|
281
|
+
style={{
|
|
282
|
+
minHeight: 56,
|
|
283
|
+
alignItems: visualCollapsed ? 'center' : 'flex-start',
|
|
284
|
+
justifyContent: 'center',
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
{logoNode}
|
|
288
|
+
</View>
|
|
289
|
+
|
|
290
|
+
{/* Tabs (scrolls when overflowing; footer stays pinned below) */}
|
|
291
|
+
<ScrollArea
|
|
292
|
+
style={{ flex: 1, marginTop: 32 }}
|
|
293
|
+
indicatorColor={scrollbarColor ?? theme.colors.border.strong}
|
|
294
|
+
contentContainerStyle={{ gap: ITEM_GAP, paddingBottom: 8 }}
|
|
295
|
+
>
|
|
296
|
+
{items.map((item) => (
|
|
297
|
+
<SidebarButton
|
|
298
|
+
key={item.key}
|
|
299
|
+
item={item}
|
|
300
|
+
active={item.key === active}
|
|
301
|
+
collapsed={visualCollapsed}
|
|
302
|
+
onPress={() => selectItem(item)}
|
|
303
|
+
/>
|
|
304
|
+
))}
|
|
305
|
+
</ScrollArea>
|
|
306
|
+
|
|
307
|
+
{/* Bottom: supporting footer + fixed collapse/expand toggle */}
|
|
308
|
+
<View style={{ marginTop: 28, gap: 16 }}>
|
|
309
|
+
{footer && !visualCollapsed ? (
|
|
310
|
+
<Text variant="bodySmall" color="tertiary" numberOfLines={1} style={{ marginLeft: 8 }}>
|
|
311
|
+
{footer}
|
|
312
|
+
</Text>
|
|
313
|
+
) : null}
|
|
314
|
+
{toggle ? (
|
|
315
|
+
<View style={{ alignItems: 'flex-start' }}>{toggle}</View>
|
|
316
|
+
) : null}
|
|
317
|
+
</View>
|
|
318
|
+
</Animated.View>
|
|
319
|
+
);
|
|
320
|
+
}
|