pixelize-design-library 2.4.2-beta.36 → 2.4.2-beta.37

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.
@@ -0,0 +1,135 @@
1
+ # Component Gotchas & Decision Guide
2
+
3
+ For anyone — human or AI — consuming `pixelize-design-library` in an app. Read this **before**
4
+ choosing a component or a prop value, not after something looks wrong. Every entry below compiles
5
+ cleanly, accepts what you pass, and produces the wrong result with no warning — TypeScript cannot
6
+ catch any of these, and a unit suite that mocks the library won't either.
7
+
8
+ This file ships inside the published package (`node_modules/pixelize-design-library/COMPONENT-GOTCHAS.md`)
9
+ so it's available without checking out the library's source repo.
10
+
11
+ ---
12
+
13
+ ## Choosing between components
14
+
15
+ ### Segmented control look — `SegmentedControl` / `Toggle/TableToggle`
16
+
17
+ Both take `variant?: "default" | "brandSlide" | "underlineSlide"` (default `"default"` — always
18
+ opt-in, never assume a non-default look).
19
+
20
+ | Context | `variant` | Look |
21
+ | --- | --- | --- |
22
+ | Most call sites — settings rows, filters, table view switches | `"default"` | Neutral chip |
23
+ | The primary, hard-to-miss view switcher on a page (not a dense toolbar) | `"brandSlide"` | Solid brand-filled pill slides behind the active segment |
24
+ | A dense toolbar, table header, or anywhere a boxed control feels heavy | `"underlineSlide"` | Thin brand-colored bar glides under the active label, no fill/chip |
25
+
26
+ Pick by density and emphasis, not by taste. When unsure, or migrating an existing call site, leave
27
+ it on `"default"` — it's the only variant guaranteed to match previously-shipped screenshots.
28
+
29
+ ### Which "select" component — seven components overlap in name, not purpose
30
+
31
+ **Not picking a value from an options list at all:**
32
+
33
+ | Doing | Component | Why not the others |
34
+ | --- | --- | --- |
35
+ | Triggering a labelled *action* (export, row menu, "...") | `Dropdown` | A menu-button, not a form field — nothing is "selected" and persisted |
36
+ | Typing free-text tags (e.g. email addresses), no options list | `MultiSelect` | Despite the name, there is nothing to pick from — see gotcha below |
37
+ | Choosing which *fields/columns* to include, in a modal, from a checkbox grid | `FieldSelectModal` | Multi-checkbox selection UI, not an inline field control |
38
+
39
+ **Picking one or more values from an options list, inline in a form:**
40
+
41
+ | List length / need | Component | Why |
42
+ | --- | --- | --- |
43
+ | Short, always-visible list, no search needed | `Select` | Native `<select>` wrapper — lightest option, but the popup is OS-painted and can't be themed |
44
+ | New build, want themed/custom-rendered rows (icons, descriptions), no huge list | `SelectV2` | Fully React-rendered popup — **prefer this for new work** over `Select` |
45
+ | Long list needing search + chips + select-all + infinite scroll | `SearchSelect` | The full-featured picker; virtualizes |
46
+ | Search-as-you-type with avatars/colour swatches/a pinned "add new" row | `SelectSearch` | Lighter than `SearchSelect`; reach for it for that specific row-content need |
47
+
48
+ Default when unsure: `SelectV2` for a themed field, `Select` if the native popup is genuinely fine.
49
+
50
+ ---
51
+
52
+ ## Known gotchas (silent failures)
53
+
54
+ ### `Drawer` / `Modal` / `ProfileCard` drop any child that isn't a named slot
55
+
56
+ All three match children by exact type (`child.type === XHeader/XBody/XFooter`) and render only
57
+ those matches — nothing else in `children` is ever read. Two consequences: a sibling among the
58
+ slots (a nested `Modal`, `AlertDialog`, a confirm dialog, a portal after the footer) silently never
59
+ mounts; and a slot wrapped in a `<>...</>` fragment is dropped too, because the matching doesn't
60
+ unwrap fragments — a fragment's `.type` is `Fragment`, never the slot component.
61
+
62
+ **Apply:** put a confirm dialog **outside** the `Drawer`/`Modal`/`ProfileCard`, as a sibling in a
63
+ wrapping fragment — never among the header/body/footer slots. When a ternary picks between bodies,
64
+ branch into a variable rendered inside one body slot, rather than returning fragment-wrapped slots
65
+ per branch.
66
+
67
+ ### `Select`'s `onChange` never fires for the built-in placeholder
68
+
69
+ `Select` resolves the changed value against its `options` list and calls `onChange` only on a hit.
70
+ The built-in `placeholder` renders as its own empty-value option, which isn't in `options` — so
71
+ picking it resolves to no match and nothing fires. An "Any / All / None" reset built on the
72
+ placeholder alone is a dead control; the value snaps back and no filter clears.
73
+
74
+ **Apply:** fine for a plain pick-one-of-N. For a clearable filter, add an explicit
75
+ `{ id: "", label: "Any" }` entry to `options` instead of relying on the placeholder.
76
+
77
+ ### `MultiSelect` is an email-chip input, not a picker
78
+
79
+ Despite the name, `MultiSelect` takes **no `options` prop at all**. Typed text is validated as an
80
+ email address and rejected if it doesn't match — there is no way to choose from a product/entity
81
+ list.
82
+
83
+ **Apply:** use `SearchSelect` with `isMultiple` to pick multiple values from an options list.
84
+
85
+ ### `RadioButton` has a closed prop list — no `...rest`, no `name`
86
+
87
+ It forwards only a fixed set of props (`label`, `colorScheme`, `isChecked`, `onChange`,
88
+ `isDisabled`, `size`, `value`, `defaultChecked`, plus label/tooltip props) to the underlying radio
89
+ — a `data-*` hook, `aria-describedby`, or a `ref` cannot reach it.
90
+
91
+ **Apply:** it still joins an external `RadioGroup` correctly (grouping comes from context, no
92
+ `name` prop needed) — don't avoid it just for grouping. Reach for the library's Chakra escape
93
+ hatch only when you need something outside that closed list.
94
+
95
+ ### `Table`'s `onRowClick` is dead if every column has a `node` renderer
96
+
97
+ Row-click is gated **per column**, not per row: a column supplying a custom `node` renderer
98
+ swallows the click on that cell. Give every column a `node` and `onRowClick` becomes dead code —
99
+ accepted, typed, never called.
100
+
101
+ **Apply:** leave at least one column without a `node`, or put an explicit control inside a `node`
102
+ renderer instead of relying on row click.
103
+
104
+ ### `InputTextArea`'s `width` defaults to the number `500`, and no `id` means no label
105
+
106
+ `width` defaults to `500`, forwarded straight through — `500` isn't a sizing-scale key, so it
107
+ renders a literal `500px`, not a max and not responsive. It only shows up on a narrow screen.
108
+ Separately, `id` feeds both the visible label and the field itself with no fallback — omit it and
109
+ the label is associated with nothing; the field has no accessible name.
110
+
111
+ **Apply:** pass `width="100%"` **and** `id` at every call site.
112
+
113
+ ### `error` without `errorMessage` renders the literal word "Error" — and erases `helperText`
114
+
115
+ Every field that shows validation errors (`TextInput`, `InputTextArea`, `Select`, `MultiSelect`,
116
+ `SelectV2`, `SearchSelect`, `SelectSearch`, `Search`, `Checkbox`, the `DatePicker` variants,
117
+ `PhoneNumberInput`) falls back to the literal word **"Error"** when `error` is `true` but
118
+ `errorMessage` is empty. Worse, `helperText` only renders when `!error` — so the guidance
119
+ disappears exactly when it's needed.
120
+
121
+ **Apply:** never pass `error` without `errorMessage`. Gate `error` on a `touched` flag so a field
122
+ whose defaults arrive asynchronously doesn't open already red.
123
+
124
+ ### A wrapper presetting a prop must destructure with a default, not rely on JSX prop order
125
+
126
+ Wrapping a library component to preset a prop (e.g. `Table`'s `variant`) as
127
+ `<Table variant="studio" {...props} />` silently reverts to the library's own default the moment a
128
+ caller passes `variant={undefined}` — a later spread always wins in JSX, even with `undefined`,
129
+ and the library component resolves that `undefined` via its own default parameter.
130
+
131
+ **Apply:**
132
+ ```tsx
133
+ const AppTable = ({ variant = 'studio', ...props }: TableProps) => <Table variant={variant} {...props} />;
134
+ ```
135
+ Applies to any library prop with a default value, not just `Table`'s `variant`.
package/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  A comprehensive React component library built with TypeScript, providing a rich set of UI components for modern web applications.
4
4
 
5
+ > **Building with this library (including via an AI assistant)?** Read
6
+ > [COMPONENT-GOTCHAS.md](./COMPONENT-GOTCHAS.md) first — it documents known silent-failure traps
7
+ > and which near-duplicate component to pick for what. It ships with this package, so it's
8
+ > available at `node_modules/pixelize-design-library/COMPONENT-GOTCHAS.md` too.
9
+
5
10
  ## Features
6
11
 
7
12
  - 🎨 **Modern Design System** - Consistent and beautiful components
@@ -4,8 +4,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
4
4
  const react_1 = require("@chakra-ui/react");
5
5
  const lucide_react_1 = require("lucide-react");
6
6
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
7
- // `id` lets a field point `aria-describedby` here so the message is part of the control's
8
- // description on every focus. `role="alert"` alone announces once, when it first renders.
7
+ // `id` lets a field point `aria-describedby` here; `role="alert"` announces once, on render.
8
+ // Always pass `errorMessage` when `error` is true — omitted, this renders the literal word "Error", and every caller hides `helperText` while `error` is true.
9
9
  const ErrorMessage = ({ errorMessage, id }) => {
10
10
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
11
11
  return ((0, jsx_runtime_1.jsxs)(react_1.Flex, { id: id, align: "center", color: colors.semanticText.error, fontSize: "0.875rem", role: "alert", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Info, { width: "0.875rem" }), (0, jsx_runtime_1.jsx)(react_1.Text, { ml: "0.188rem", children: errorMessage !== null && errorMessage !== void 0 ? errorMessage : "Error" })] }));
@@ -3,5 +3,6 @@ export type DrawerProps = Pick<ChakraDrawerProps, "isOpen" | "onClose" | "placem
3
3
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "full";
4
4
  placement?: "top" | "right" | "bottom" | "left";
5
5
  vaiant?: "solid" | "outline" | "ghost" | "link";
6
+ /** Only DrawerHeader/DrawerBody/DrawerFooter render; other elements and fragment-wrapped slots are silently dropped. */
6
7
  children?: React.ReactNode;
7
8
  };
@@ -1,4 +1,5 @@
1
1
  import { TextareaProps } from "@chakra-ui/react";
2
+ /** width defaults to 500 → literal "500px", not responsive (pass "100%"); id is required or the label has no accessible association. */
2
3
  export type InputTextAreaProps = Pick<TextareaProps, "placeholder" | "value" | "onChange" | "onBlur" | "name" | "id" | "size" | "resize" | "isDisabled" | "isReadOnly" | "isRequired" | "variant" | "autoComplete"> & {
3
4
  label?: string;
4
5
  error?: boolean;
@@ -5,6 +5,7 @@ export type ChakraModelProps = Pick<ModalProps, "isOpen" | "onClose" | "finalFoc
5
5
  overlaybackdropInvert?: string;
6
6
  overlaybackdropBlur?: string;
7
7
  size: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "full";
8
+ /** Only ModalHeader/ModalBody/ModalFooter render; other elements and fragment-wrapped slots are silently dropped. */
8
9
  children?: React.ReactNode;
9
10
  isLoading?: boolean;
10
11
  };
@@ -1,3 +1,4 @@
1
+ /** No `options` prop — typed text is validated as an email, not picked from a list; use `SearchSelect isMultiple`. */
1
2
  export type MultiSelectProps = {
2
3
  value: MultiSelctOPtions[];
3
4
  onValueChange: (options: MultiSelctOPtions[]) => void;
@@ -3,6 +3,7 @@ export type ProfileCardVariant = "elevated" | "outline" | "filled" | "unstyled";
3
3
  /** @deprecated Misspelling kept for existing callers — use "outline". */
4
4
  export type ProfileCardVariantLegacy = "outlein";
5
5
  export type ProfileCardProps = Pick<CardProps, "direction" | "maxW" | "align" | "justify" | "overflow"> & {
6
+ /** Only direct ProfileCardHeader/Body/Footer children render — no wrapping fragment, no other siblings. */
6
7
  children: React.ReactNode;
7
8
  variant?: ProfileCardVariant | ProfileCardVariantLegacy;
8
9
  size?: "sm" | "md" | "lg";
@@ -1,4 +1,5 @@
1
1
  import { RadioProps, RadioGroupProps } from '@chakra-ui/react';
2
+ /** Closed prop list, no ...rest — for aria-* or a ref use Chakra's Radio (Primitives barrel) instead. Still joins an external RadioGroup correctly via Chakra's own context. */
2
3
  export type ChakraRadioProps = Pick<RadioProps, "size" | "colorScheme" | "isChecked" | "onChange" | "isDisabled" | "value" | "defaultChecked"> & {
3
4
  label: string;
4
5
  labelFontSize?: string;
@@ -10,6 +10,7 @@ export type chakraSelectProps = Pick<SelectProps, "placeholder" | "size" | "vari
10
10
  options: OptionProp[];
11
11
  width?: string | number;
12
12
  height?: string | number;
13
+ /** Selecting the built-in placeholder (value="") never fires this unless `options` has an explicit `id: ""` entry. */
13
14
  onChange: (selectedOption: OptionProp | undefined) => void;
14
15
  formControlStyle?: React.CSSProperties;
15
16
  isInformation?: boolean;
@@ -36,6 +36,7 @@ export type TableProps = {
36
36
  onSelection?: (selected: (string | number)[]) => void;
37
37
  isPagination?: boolean;
38
38
  selections?: (string | number)[];
39
+ /** Fires per cell, gated on the column: a column with `node` swallows its clicks. */
39
40
  onRowClick?: (row: DataObject, header: Record<string | number, string | number>) => void;
40
41
  isActionFreeze?: boolean;
41
42
  paginationMode?: "client" | "server";
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Flex, Text, } from "@chakra-ui/react";
3
3
  import { Info } from "lucide-react";
4
4
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
- // `id` lets a field point `aria-describedby` here so the message is part of the control's
6
- // description on every focus. `role="alert"` alone announces once, when it first renders.
5
+ // `id` lets a field point `aria-describedby` here; `role="alert"` announces once, on render.
6
+ // Always pass `errorMessage` when `error` is true — omitted, this renders the literal word "Error", and every caller hides `helperText` while `error` is true.
7
7
  const ErrorMessage = ({ errorMessage, id }) => {
8
8
  const { colors } = useCustomTheme();
9
9
  return (_jsxs(Flex, { id: id, align: "center", color: colors.semanticText.error, fontSize: "0.875rem", role: "alert", children: [_jsx(Info, { width: "0.875rem" }), _jsx(Text, { ml: "0.188rem", children: errorMessage !== null && errorMessage !== void 0 ? errorMessage : "Error" })] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixelize-design-library",
3
- "version": "2.4.2-beta.36",
3
+ "version": "2.4.2-beta.37",
4
4
  "description": "React component library for Pixelize apps: themeable Chakra-based components, design tokens and light/dark brand theming.",
5
5
  "keywords": [
6
6
  "react",
@@ -28,7 +28,8 @@
28
28
  "types": "dist/index.d.ts",
29
29
  "files": [
30
30
  "dist",
31
- "README.md"
31
+ "README.md",
32
+ "COMPONENT-GOTCHAS.md"
32
33
  ],
33
34
  "dependencies": {
34
35
  "@fontsource-variable/inter": "^5.2.8",
@@ -151,7 +152,6 @@
151
152
  "jest": "^29.7.0",
152
153
  "jest-axe": "^11.0.0",
153
154
  "jest-environment-jsdom": "^29.7.0",
154
- "path": "^0.12.7",
155
155
  "postcss": "^8.5.4",
156
156
  "prettier": "^3.3.2",
157
157
  "prop-types": "^15.8.1",