@7shifts/sous-chef 4.11.0 → 4.12.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.
@@ -1,20 +1,95 @@
1
+ <!-- AUTO-GENERATED by scripts/build-llms-guidelines.js. Do not edit manually. Run `yarn build-llms-guidelines` to regenerate. -->
2
+
1
3
  ## When to Use
2
4
 
3
- - When the user needs to select from a list of options that is loaded dynamically from an API or other async source — employees, roles, locations, etc.
4
- - When the full list of options is too large to load upfront and should be fetched on demand or filtered server-side
5
- - When the available options depend on another field's value (e.g. employees filtered by location)
5
+ - When options come from an API and the full list is too large to load at once (employees, locations, roles, etc.).
6
+ - When results should be narrowed by what the user types search-as-you-type against a backend.
7
+ - When the option list is dynamic — it changes based on server state or another field's value.
6
8
 
7
9
  ## When Not to Use
8
10
 
9
- - When the list of options is fixed and known at render time — use `SelectField` instead
10
- - When the user must be able to select more than one option — use `MultiSelectField` instead
11
- - When the options are few and benefit from being fully visible — use `RadioGroupField` or `PillSelectField` instead
12
- - When the user needs to enter free-form text — use `TextField` instead
11
+ - When the option list is small and static — use `SelectField` instead; it takes a plain `options` array and has no loading overhead.
12
+ - When options are already available in memory pass them directly to `SelectField` or filter them client-side.
13
+ - When the user needs to pick from a fixed set of weekdays — use `WeekSelectField`.
13
14
 
14
15
  ## Usage
15
16
 
16
- ### Copy
17
+ ### The `loadOptions` Callback
18
+
19
+ `loadOptions` is the only required prop unique to this component. It receives two arguments and must return a Promise:
20
+
21
+ ```ts
22
+ loadOptions: (
23
+ inputValue: string, // current search text (empty string on initial load)
24
+ cursor?: string | null // pagination cursor, null on the first call
25
+ ) =>
26
+ Promise<{
27
+ options: SelectOption<T>[]; // the page of options to display
28
+ hasMore: boolean; // whether more pages exist
29
+ nextCursor?: string | null; // opaque cursor for the next page (omit or null if none)
30
+ }>;
31
+ ```
32
+
33
+ The component calls `loadOptions('', null)` automatically on mount to populate the initial list. There is no need to trigger a manual fetch.
34
+
35
+ ### Pagination and Scroll-to-Load-More
36
+
37
+ When a `nextCursor` is returned, a spinner appears at the bottom of the menu as the user scrolls down. When the user reaches the bottom, the next page is fetched using that cursor and its options are **appended** to the current list — the previous options are not replaced.
38
+
39
+ ```ts
40
+ // Page 1 response — signals more pages exist
41
+ { options: firstPageOptions, hasMore: true, nextCursor: '20' }
42
+
43
+ // Page 2 response — called with cursor='20', no further pages
44
+ { options: secondPageOptions, hasMore: false }
45
+ ```
46
+
47
+ If `nextCursor` is omitted or `null`, or if `hasMore` is `false`, no further fetches are triggered on scroll. Omitting `nextCursor` entirely is the same as returning `null` — the component treats both as "no next page".
48
+
49
+ ### Search Behavior
50
+
51
+ When the user types, `loadOptions` is called with the current input text and `null` as the cursor — pagination always resets to the first page on a new search. Accumulated options from previous scroll-loads are discarded and replaced with the fresh results.
52
+
53
+ **Client-side search optimisation:** If the very first `loadOptions` call returns `hasMore: false`, the component caches the full option list and all subsequent searches are filtered client-side — no further network calls are made regardless of what the user types. This makes single-page datasets feel instant without any extra code on the caller's side.
54
+
55
+ ### Dependent Fields
56
+
57
+ Use the `key` prop to force a full reload whenever an external value changes — for example when a Location field controls which employees are shown:
58
+
59
+ ```tsx
60
+ <AsyncSelectField
61
+ loadOptions={(inputValue, cursor) =>
62
+ fetchEmployees(inputValue, cursor, location.value)
63
+ }
64
+ name="employee"
65
+ label="Employee"
66
+ key={location.value} // remounts (and re-fetches) when location changes
67
+ />
68
+ ```
69
+
70
+ When `key` changes, React unmounts and remounts the component, which resets all accumulated options, the cursor, and the search input.
71
+
72
+ ### Label, Caption, and Error
73
+
74
+ Always provide a `label` — it identifies the field and is linked to the input for screen readers. Use `caption` for supplementary guidance such as what the user should search for. Use `error` to surface validation messages; the field is marked as invalid for screen readers when an error is present.
75
+
76
+ ### No Options and Loading States
77
+
78
+ The component handles loading and empty states automatically. A "Loading..." indicator appears while options are being fetched on initial load or after typing. When `loadOptions` resolves with an empty array and `hasMore: false`, react-select's default "No options" message is shown — this can be customised via the `noOptionsMessage` prop.
79
+
80
+ ## Tips & Tricks
81
+
82
+ - If your API doesn't paginate, simply return `hasMore: false` and omit `nextCursor` — the scroll-load-more behaviour is disabled and the component behaves like a plain async select.
83
+ - The `cursor` parameter passed to `loadOptions` is whatever string you returned as `nextCursor` on the previous call. It is opaque to the component — you can use an offset, a page number, a keyset value, or any server-issued token. The component never inspects or modifies it.
84
+ - Debouncing is built in (500 ms, leading edge). There is no need to debounce `loadOptions` at the call site.
85
+ - Avoid returning `hasMore: true` without a `nextCursor`. The component uses the cursor to guard against duplicate fetches — if `hasMore` is `true` but `nextCursor` is `null`, scroll-to-load-more is silently disabled for that page.
86
+ - The `key` pattern is more reliable than updating `loadOptions` in a `useEffect`. React's remount guarantee means state is always fully reset, whereas updating the callback while keeping the component mounted may not clear accumulated options.
87
+
88
+ ## Additional Rules
17
89
 
18
- - Use sentence case for labels, captions, and placeholder text
19
- - Write placeholder text as a concrete search prompt ("Search for an employee") rather than a generic instruction ("Select something")
20
- - Keep labels shortone to three words is ideal
90
+ - `loadOptions` and `name` are required. All other props are inherited from `SelectField` (`label`, `onChange`, `value`, `caption`, `error`, `prefix`, etc.).
91
+ - `loadOptions` is called with `('', null)` on mount the initial call always receives an empty string and a null cursor.
92
+ - The `cursor` argument is `string | null` always `null` on the first call per search query, and the opaque `nextCursor` string on subsequent scroll-load calls.
93
+ - `nextCursor` in the response is optional and typed as `string | null`. Returning `undefined`, `null`, or omitting the field are all treated identically — no next page.
94
+ - `hasMore: false` stops all further scroll-triggered fetches for the current search, even if a `nextCursor` was returned (it is discarded).
95
+ - `filterOption` is set to `null` internally — react-select's built-in client-side filtering is disabled because the component manages the option list directly. Do not pass a `filterOption` prop.
@@ -98,7 +98,7 @@ Form components are used to collect user input and data.
98
98
 
99
99
  ### AsyncSelectField
100
100
 
101
- A variation of select field that loads a dynamic list of options from a source rather than a fixed set of options. Use when you need to select from a dynamic list that can change such as roles, employees, locations, etc. Instead of passing a `options` props, this component requires a `loadOptions` prop.
101
+ A select field that fetches its options from an async source an API, a search endpoint, or any function that returns a Promise. Use it when the option list is too large to load upfront or when options change based on server-side data. Supports scroll-to-load-more pagination: when the user scrolls to the bottom of the open menu and more results exist, the next page is fetched and appended automatically.
102
102
 
103
103
  ### CheckboxField
104
104
 
@@ -413,6 +413,7 @@ Most of the time you won't need to use these tokens because it is baked into Sou
413
413
  | ------------------- | ----- |
414
414
  | --border-radius-300 | 4px |
415
415
  | --border-radius-400 | 8px |
416
+ | --border-radius-500 | 12px |
416
417
  | --border-radius-600 | 20px |
417
418
 
418
419
  ## Z-index
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7shifts/sous-chef",
3
- "version": "4.11.0",
3
+ "version": "4.12.0",
4
4
  "description": "7shifts component library",
5
5
  "author": "7shifts",
6
6
  "license": "MIT",