lkd-web-kit 0.10.2 → 0.10.3

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.
Files changed (33) hide show
  1. package/.agents/skills/mantine-combobox/SKILL.md +73 -0
  2. package/.agents/skills/mantine-combobox/references/api.md +199 -0
  3. package/.agents/skills/mantine-combobox/references/patterns.md +279 -0
  4. package/.agents/skills/mantine-custom-components/SKILL.md +112 -0
  5. package/.agents/skills/mantine-custom-components/references/api.md +407 -0
  6. package/.agents/skills/mantine-custom-components/references/patterns.md +431 -0
  7. package/.agents/skills/publish-lkd-web-kit/SKILL.md +172 -0
  8. package/.agents/skills/publish-lkd-web-kit/agents/openai.yaml +4 -0
  9. package/.agents/skills/publish-lkd-web-kit/references/npm-trusted-publishing.md +82 -0
  10. package/.agents/skills/update-lkd-dependencies/SKILL.md +88 -0
  11. package/.agents/skills/update-lkd-dependencies/agents/openai.yaml +4 -0
  12. package/.agents/skills/update-lkd-dependencies/scripts/collect-npm-metadata.mjs +73 -0
  13. package/dist/index.cjs +2 -0
  14. package/dist/index.js +2 -1
  15. package/dist/mantine/breakpoints-with-px.cjs +3 -2
  16. package/dist/mantine/breakpoints-with-px.d.ts +2 -1
  17. package/dist/mantine/breakpoints-with-px.d.ts.map +1 -1
  18. package/dist/mantine/breakpoints-with-px.js +3 -2
  19. package/dist/mantine/create-lkd-theme.cjs +7 -0
  20. package/dist/mantine/create-lkd-theme.d.ts +3 -0
  21. package/dist/mantine/create-lkd-theme.d.ts.map +1 -0
  22. package/dist/mantine/create-lkd-theme.js +6 -0
  23. package/dist/mantine/index.d.ts +1 -0
  24. package/dist/mantine/index.d.ts.map +1 -1
  25. package/package.json +3 -3
  26. package/scripts/{install-codex-skills.mjs → install-agent-skills.mjs} +1 -1
  27. package/codex/skills/create-modal-component/SKILL.md +0 -168
  28. package/codex/skills/create-modal-component/agents/openai.yaml +0 -4
  29. package/codex/skills/create-svg-icon/SKILL.md +0 -41
  30. package/codex/skills/create-svg-icon/agents/openai.yaml +0 -3
  31. package/codex/skills/create-table-component/SKILL.md +0 -189
  32. package/codex/skills/create-table-component/agents/openai.yaml +0 -3
  33. package/codex/skills/rhf-lkd-forms/SKILL.md +0 -186
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: mantine-combobox
3
+ description: >
4
+ Build custom dropdown/select/autocomplete/multiselect components using Mantine's Combobox
5
+ primitives. Use this skill when: (1) creating a new custom select-like component with
6
+ Combobox primitives, (2) building a searchable dropdown, (3) implementing a multi-select
7
+ or tags input variant, (4) customizing option rendering, (5) adding custom filtering logic,
8
+ or (6) any task involving useCombobox, Combobox.Target, Combobox.Option, or Combobox.Dropdown.
9
+ ---
10
+
11
+ # Mantine Combobox Skill
12
+
13
+ ## Overview
14
+
15
+ `Combobox` provides low-level primitives for building any select-like UI. The built-in
16
+ `Select`, `Autocomplete`, and `TagsInput` components are all built on top of it.
17
+
18
+ ## Core Workflow
19
+
20
+ ### 1. Create the store
21
+
22
+ ```tsx
23
+ const combobox = useCombobox({
24
+ onDropdownClose: () => combobox.resetSelectedOption(),
25
+ onDropdownOpen: () => combobox.selectFirstOption(),
26
+ });
27
+ ```
28
+
29
+ ### 2. Render structure
30
+
31
+ ```tsx
32
+ <Combobox store={combobox} onOptionSubmit={handleSubmit}>
33
+ <Combobox.Target>
34
+ <InputBase
35
+ component="button"
36
+ pointer
37
+ rightSection={<Combobox.Chevron />}
38
+ onClick={() => combobox.toggleDropdown()}
39
+ >
40
+ {value || <Input.Placeholder>Pick value</Input.Placeholder>}
41
+ </InputBase>
42
+ </Combobox.Target>
43
+ <Combobox.Dropdown>
44
+ <Combobox.Options>
45
+ {options.map((item) => (
46
+ <Combobox.Option value={item} key={item}>{item}</Combobox.Option>
47
+ ))}
48
+ </Combobox.Options>
49
+ </Combobox.Dropdown>
50
+ </Combobox>
51
+ ```
52
+
53
+ ### 3. Handle submit
54
+
55
+ ```tsx
56
+ const handleSubmit = (val: string) => {
57
+ setValue(val);
58
+ combobox.closeDropdown();
59
+ };
60
+ ```
61
+
62
+ ## Target Types
63
+
64
+ | Scenario | Use |
65
+ |---|---|
66
+ | Button trigger (no text input) | `<Combobox.Target targetType="button">` |
67
+ | Input trigger | `<Combobox.Target>` (default) |
68
+ | Pills + separate input (multi-select) | `<Combobox.DropdownTarget>` + `<Combobox.EventsTarget>` |
69
+
70
+ ## References
71
+
72
+ - **[`references/api.md`](references/api.md)** — Full API: `useCombobox` options and store, all sub-component props, CSS variables, Styles API selectors
73
+ - **[`references/patterns.md`](references/patterns.md)** — Code examples: searchable select, multi-select with pills, groups, custom rendering, clear button, form integration
@@ -0,0 +1,199 @@
1
+ # Combobox API Reference
2
+
3
+ ## Table of Contents
4
+ - [useCombobox hook](#usecombobox-hook)
5
+ - [Combobox (root)](#combobox-root)
6
+ - [Sub-components](#sub-components)
7
+ - [CSS variables & Styles API](#css-variables--styles-api)
8
+
9
+ ---
10
+
11
+ ## useCombobox hook
12
+
13
+ ```tsx
14
+ const combobox = useCombobox(options?: UseComboboxOptions);
15
+ ```
16
+
17
+ **Options:**
18
+ ```ts
19
+ interface UseComboboxOptions {
20
+ defaultOpened?: boolean;
21
+ opened?: boolean;
22
+ onOpenedChange?: (opened: boolean) => void;
23
+ onDropdownClose?: (eventSource: 'keyboard' | 'mouse' | 'unknown') => void;
24
+ onDropdownOpen?: (eventSource: 'keyboard' | 'mouse' | 'unknown') => void;
25
+ loop?: boolean; // Default: true — keyboard nav wraps at boundaries
26
+ scrollBehavior?: ScrollBehavior; // Default: 'instant'
27
+ }
28
+ ```
29
+
30
+ **Returned store:**
31
+ ```ts
32
+ interface ComboboxStore {
33
+ // Dropdown state
34
+ dropdownOpened: boolean;
35
+ openDropdown(eventSource?: 'keyboard' | 'mouse' | 'unknown'): void;
36
+ closeDropdown(eventSource?: 'keyboard' | 'mouse' | 'unknown'): void;
37
+ toggleDropdown(eventSource?: 'keyboard' | 'mouse' | 'unknown'): void;
38
+
39
+ // Option keyboard navigation
40
+ selectActiveOption(): string | null;
41
+ selectFirstOption(): string | null;
42
+ selectNextOption(): string | null;
43
+ selectPreviousOption(): string | null;
44
+ resetSelectedOption(): void;
45
+ clickSelectedOption(): void;
46
+ updateSelectedOptionIndex(
47
+ target?: 'active' | 'selected' | number,
48
+ options?: { scrollIntoView?: boolean }
49
+ ): void;
50
+
51
+ // Programmatic focus
52
+ focusSearchInput(): void;
53
+ focusTarget(): void;
54
+ }
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Combobox (root)
60
+
61
+ ```tsx
62
+ <Combobox
63
+ store={combobox} // required — ComboboxStore from useCombobox()
64
+ onOptionSubmit={fn} // (value: string, optionProps) => void
65
+ size="sm" // MantineSize | string, default: 'sm'
66
+ dropdownPadding={4} // number, default: 4
67
+ resetSelectionOnOptionHover // boolean
68
+ readOnly // boolean — disables all interactions
69
+ // + all Popover props (position, offset, width, withinPortal, etc.)
70
+ />
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Sub-components
76
+
77
+ ### Combobox.Target
78
+ ```tsx
79
+ <Combobox.Target
80
+ targetType="input" // 'button' | 'input', default: 'input'
81
+ withKeyboardNavigation // boolean, default: true
82
+ withAriaAttributes // boolean, default: true
83
+ withExpandedAttribute // boolean, default: false
84
+ autoComplete="off" // string
85
+ >
86
+ {/* single child — the trigger element */}
87
+ </Combobox.Target>
88
+ ```
89
+
90
+ Use `targetType="button"` when the trigger is a button (Space key toggles dropdown).
91
+
92
+ ### Combobox.DropdownTarget
93
+ Marks the element used for dropdown positioning when separate from the keyboard events target. Used together with `Combobox.EventsTarget` in multi-select/pills patterns.
94
+
95
+ ### Combobox.EventsTarget
96
+ Receives keyboard events for dropdown navigation. Used alongside `Combobox.DropdownTarget` when the typing input is nested inside the trigger (e.g. inside `PillsInput`).
97
+
98
+ ### Combobox.Dropdown
99
+ ```tsx
100
+ <Combobox.Dropdown hidden={false}>
101
+ {/* dropdown content */}
102
+ </Combobox.Dropdown>
103
+ ```
104
+
105
+ Use `hidden` to hide without unmounting (preserves scroll position).
106
+
107
+ ### Combobox.Options
108
+ ```tsx
109
+ <Combobox.Options labelledBy="some-label-id">
110
+ {/* Combobox.Option or Combobox.Group elements */}
111
+ </Combobox.Options>
112
+ ```
113
+
114
+ ### Combobox.Option
115
+ ```tsx
116
+ <Combobox.Option
117
+ value="react" // string | number, required
118
+ active={false} // visually marks as selected; used by selectActiveOption()
119
+ disabled={false}
120
+ >
121
+ React
122
+ </Combobox.Option>
123
+ ```
124
+
125
+ ### Combobox.Search
126
+ Built-in search input, wired to keyboard navigation. Renders sticky at top of dropdown.
127
+
128
+ ```tsx
129
+ <Combobox.Search
130
+ value={search}
131
+ onChange={(e) => setSearch(e.currentTarget.value)}
132
+ placeholder="Search..."
133
+ withAriaAttributes // boolean, default: true
134
+ withKeyboardNavigation // boolean, default: true
135
+ // + all Input props
136
+ />
137
+ ```
138
+
139
+ ### Combobox.Empty
140
+ ```tsx
141
+ <Combobox.Empty>Nothing found</Combobox.Empty>
142
+ ```
143
+
144
+ ### Combobox.Group
145
+ ```tsx
146
+ <Combobox.Group label="Frontend">
147
+ <Combobox.Option value="react">React</Combobox.Option>
148
+ </Combobox.Group>
149
+ ```
150
+
151
+ ### Combobox.Header / Combobox.Footer
152
+ ```tsx
153
+ <Combobox.Header>Custom header</Combobox.Header>
154
+ <Combobox.Footer>Custom footer</Combobox.Footer>
155
+ ```
156
+
157
+ ### Combobox.Chevron
158
+ ```tsx
159
+ <Combobox.Chevron size="sm" error={error} color="blue" />
160
+ ```
161
+
162
+ ### Combobox.ClearButton
163
+ ```tsx
164
+ <Combobox.ClearButton onClear={() => setValue(null)} />
165
+ ```
166
+
167
+ ### Combobox.HiddenInput
168
+ ```tsx
169
+ <Combobox.HiddenInput
170
+ value={value} // string | number | (string | number)[] | null
171
+ valuesDivider="," // string, default: ','
172
+ name="myField"
173
+ form="myForm"
174
+ />
175
+ ```
176
+
177
+ ---
178
+
179
+ ## CSS variables & Styles API
180
+
181
+ ### CSS variables (set on root element)
182
+ | Variable | Description |
183
+ |---|---|
184
+ | `--combobox-option-fz` | Option font size (driven by `size` prop) |
185
+ | `--combobox-option-padding` | Option padding (driven by `size` prop) |
186
+ | `--combobox-padding` | Dropdown padding (driven by `dropdownPadding`, default 4px) |
187
+
188
+ ### Styles API selectors
189
+ | Selector | Element |
190
+ |---|---|
191
+ | `dropdown` | Dropdown container |
192
+ | `options` | Options listbox |
193
+ | `option` | Individual option |
194
+ | `search` | Search input |
195
+ | `empty` | Nothing found message |
196
+ | `header` | Dropdown header |
197
+ | `footer` | Dropdown footer |
198
+ | `group` | Group container |
199
+ | `groupLabel` | Group label text |
@@ -0,0 +1,279 @@
1
+ # Combobox Implementation Patterns
2
+
3
+ ## Table of Contents
4
+ - [Basic select (button trigger)](#basic-select-button-trigger)
5
+ - [Searchable select (input trigger)](#searchable-select-input-trigger)
6
+ - [Multi-select with pills](#multi-select-with-pills)
7
+ - [Options with groups](#options-with-groups)
8
+ - [Custom option rendering](#custom-option-rendering)
9
+ - [Clear button](#clear-button)
10
+ - [Form integration (hidden input)](#form-integration-hidden-input)
11
+ - [Nothing found message](#nothing-found-message)
12
+
13
+ ---
14
+
15
+ ## Basic select (button trigger)
16
+
17
+ ```tsx
18
+ function CustomSelect({ data }: { data: string[] }) {
19
+ const [value, setValue] = useState<string | null>(null);
20
+
21
+ const combobox = useCombobox({
22
+ onDropdownClose: () => combobox.resetSelectedOption(),
23
+ });
24
+
25
+ const options = data.map((item) => (
26
+ <Combobox.Option value={item} key={item} active={item === value}>
27
+ {item}
28
+ </Combobox.Option>
29
+ ));
30
+
31
+ return (
32
+ <Combobox
33
+ store={combobox}
34
+ onOptionSubmit={(val) => {
35
+ setValue(val);
36
+ combobox.closeDropdown();
37
+ }}
38
+ >
39
+ <Combobox.Target targetType="button">
40
+ <InputBase
41
+ component="button"
42
+ type="button"
43
+ pointer
44
+ rightSection={<Combobox.Chevron />}
45
+ rightSectionPointerEvents="none"
46
+ onClick={() => combobox.toggleDropdown()}
47
+ >
48
+ {value || <Input.Placeholder>Pick value</Input.Placeholder>}
49
+ </InputBase>
50
+ </Combobox.Target>
51
+
52
+ <Combobox.Dropdown>
53
+ <Combobox.Options>{options}</Combobox.Options>
54
+ </Combobox.Dropdown>
55
+ </Combobox>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Searchable select (input trigger)
63
+
64
+ Input acts as both trigger and search field. Filter options based on the typed value.
65
+
66
+ ```tsx
67
+ function SearchableSelect({ data }: { data: string[] }) {
68
+ const [value, setValue] = useState<string | null>(null);
69
+ const [search, setSearch] = useState('');
70
+
71
+ const combobox = useCombobox({
72
+ onDropdownClose: () => combobox.resetSelectedOption(),
73
+ onDropdownOpen: () => combobox.selectFirstOption(),
74
+ });
75
+
76
+ const shouldFilterOptions = data.every((item) => item !== search);
77
+ const filtered = shouldFilterOptions
78
+ ? data.filter((item) => item.toLowerCase().includes(search.toLowerCase().trim()))
79
+ : data;
80
+
81
+ const options = filtered.length > 0
82
+ ? filtered.map((item) => (
83
+ <Combobox.Option value={item} key={item}>{item}</Combobox.Option>
84
+ ))
85
+ : <Combobox.Empty>Nothing found</Combobox.Empty>;
86
+
87
+ return (
88
+ <Combobox
89
+ store={combobox}
90
+ onOptionSubmit={(val) => {
91
+ setValue(val);
92
+ setSearch(val);
93
+ combobox.closeDropdown();
94
+ }}
95
+ >
96
+ <Combobox.Target>
97
+ <InputBase
98
+ rightSection={<Combobox.Chevron />}
99
+ value={search}
100
+ onChange={(e) => {
101
+ combobox.openDropdown();
102
+ combobox.updateSelectedOptionIndex();
103
+ setSearch(e.currentTarget.value);
104
+ }}
105
+ onClick={() => combobox.openDropdown()}
106
+ onFocus={() => combobox.openDropdown()}
107
+ onBlur={() => {
108
+ combobox.closeDropdown();
109
+ setSearch(value || '');
110
+ }}
111
+ placeholder="Search value"
112
+ rightSectionPointerEvents="none"
113
+ />
114
+ </Combobox.Target>
115
+
116
+ <Combobox.Dropdown>
117
+ <Combobox.Options>{options}</Combobox.Options>
118
+ </Combobox.Dropdown>
119
+ </Combobox>
120
+ );
121
+ }
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Multi-select with pills
127
+
128
+ Use `Combobox.DropdownTarget` for the outer container and `Combobox.EventsTarget` around the text input inside it.
129
+
130
+ ```tsx
131
+ function MultiSelect({ data }: { data: string[] }) {
132
+ const combobox = useCombobox({ onDropdownClose: () => combobox.resetSelectedOption() });
133
+ const [search, setSearch] = useState('');
134
+ const [value, setValue] = useState<string[]>([]);
135
+
136
+ const handleValueSelect = (val: string) =>
137
+ setValue((current) =>
138
+ current.includes(val) ? current.filter((v) => v !== val) : [...current, val]
139
+ );
140
+
141
+ const handleValueRemove = (val: string) =>
142
+ setValue((current) => current.filter((v) => v !== val));
143
+
144
+ const options = data
145
+ .filter((item) => item.toLowerCase().includes(search.trim().toLowerCase()))
146
+ .map((item) => (
147
+ <Combobox.Option value={item} key={item} active={value.includes(item)}>
148
+ <Group gap="sm">
149
+ {value.includes(item) ? <CheckIcon size={12} /> : null}
150
+ <span>{item}</span>
151
+ </Group>
152
+ </Combobox.Option>
153
+ ));
154
+
155
+ const pills = value.map((item) => (
156
+ <Pill key={item} withRemoveButton onRemove={() => handleValueRemove(item)}>
157
+ {item}
158
+ </Pill>
159
+ ));
160
+
161
+ return (
162
+ <Combobox store={combobox} onOptionSubmit={handleValueSelect}>
163
+ <Combobox.DropdownTarget>
164
+ <PillsInput onClick={() => combobox.openDropdown()}>
165
+ <Pill.Group>
166
+ {pills}
167
+ <Combobox.EventsTarget>
168
+ <PillsInput.Field
169
+ onFocus={() => combobox.openDropdown()}
170
+ onBlur={() => combobox.closeDropdown()}
171
+ value={search}
172
+ placeholder="Search values"
173
+ onChange={(e) => {
174
+ combobox.updateSelectedOptionIndex();
175
+ setSearch(e.currentTarget.value);
176
+ }}
177
+ onKeyDown={(e) => {
178
+ if (e.key === 'Backspace' && search.length === 0) {
179
+ e.preventDefault();
180
+ handleValueRemove(value[value.length - 1]);
181
+ }
182
+ }}
183
+ />
184
+ </Combobox.EventsTarget>
185
+ </Pill.Group>
186
+ </PillsInput>
187
+ </Combobox.DropdownTarget>
188
+
189
+ <Combobox.Dropdown>
190
+ <Combobox.Options>
191
+ {options.length > 0 ? options : <Combobox.Empty>Nothing found</Combobox.Empty>}
192
+ </Combobox.Options>
193
+ </Combobox.Dropdown>
194
+ </Combobox>
195
+ );
196
+ }
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Options with groups
202
+
203
+ ```tsx
204
+ const groups = data.map((group) => (
205
+ <Combobox.Group label={group.group} key={group.group}>
206
+ {group.items.map((item) => (
207
+ <Combobox.Option value={item.value} key={item.value}>
208
+ {item.label}
209
+ </Combobox.Option>
210
+ ))}
211
+ </Combobox.Group>
212
+ ));
213
+
214
+ // Inside Combobox.Dropdown:
215
+ <Combobox.Options>{groups}</Combobox.Options>
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Custom option rendering
221
+
222
+ Render any content inside `Combobox.Option`:
223
+
224
+ ```tsx
225
+ const options = data.map((item) => (
226
+ <Combobox.Option value={item.value} key={item.value}>
227
+ <Group>
228
+ <Avatar src={item.avatar} size="sm" />
229
+ <div>
230
+ <Text size="sm">{item.label}</Text>
231
+ <Text size="xs" c="dimmed">{item.email}</Text>
232
+ </div>
233
+ </Group>
234
+ </Combobox.Option>
235
+ ));
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Clear button
241
+
242
+ ```tsx
243
+ const rightSection = value ? (
244
+ <Combobox.ClearButton onClear={() => { setValue(null); setSearch(''); }} />
245
+ ) : (
246
+ <Combobox.Chevron />
247
+ );
248
+
249
+ <InputBase
250
+ rightSection={rightSection}
251
+ rightSectionPointerEvents={value ? 'all' : 'none'}
252
+ />
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Form integration (hidden input)
258
+
259
+ ```tsx
260
+ // Single value
261
+ <Combobox.HiddenInput value={value} name="framework" />
262
+
263
+ // Multiple values (joined by comma by default)
264
+ <Combobox.HiddenInput value={selectedValues} name="frameworks" valuesDivider="," />
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Nothing found message
270
+
271
+ ```tsx
272
+ <Combobox.Dropdown>
273
+ <Combobox.Options>
274
+ {filteredOptions.length > 0
275
+ ? filteredOptions
276
+ : <Combobox.Empty>Nothing found for "{search}"</Combobox.Empty>}
277
+ </Combobox.Options>
278
+ </Combobox.Dropdown>
279
+ ```
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: mantine-custom-components
3
+ description: >
4
+ Build custom components that integrate with Mantine's theming, Styles API, and core features.
5
+ Use this skill when: (1) creating a new component using factory(), polymorphicFactory(), or
6
+ genericFactory(), (2) adding Styles API support (classNames, styles, vars, unstyled), (3)
7
+ implementing CSS variables via createVarsResolver, (4) building compound components with
8
+ sub-components and shared context, (5) registering a component with MantineProvider via
9
+ Component.extend(), or (6) any task involving Factory, useProps, useStyles, BoxProps,
10
+ StylesApiProps, or ElementProps in @mantine/core.
11
+ ---
12
+
13
+ # Mantine Custom Components Skill
14
+
15
+ ## Component template
16
+
17
+ ```tsx
18
+ import {
19
+ Box, BoxProps, createVarsResolver, ElementProps,
20
+ factory, Factory, getRadius, MantineRadius,
21
+ StylesApiProps, useProps, useStyles,
22
+ } from '@mantine/core';
23
+ import classes from './MyComponent.module.css';
24
+
25
+ export type MyComponentStylesNames = 'root' | 'inner';
26
+ export type MyComponentVariant = 'filled' | 'outline';
27
+ export type MyComponentCssVariables = { root: '--my-radius' };
28
+
29
+ export interface MyComponentProps
30
+ extends BoxProps, StylesApiProps<MyComponentFactory>, ElementProps<'div'> {
31
+ radius?: MantineRadius;
32
+ }
33
+
34
+ export type MyComponentFactory = Factory<{
35
+ props: MyComponentProps;
36
+ ref: HTMLDivElement;
37
+ stylesNames: MyComponentStylesNames;
38
+ vars: MyComponentCssVariables;
39
+ variant: MyComponentVariant;
40
+ }>;
41
+
42
+ const defaultProps = { radius: 'md' } satisfies Partial<MyComponentProps>;
43
+
44
+ const varsResolver = createVarsResolver<MyComponentFactory>((_theme, { radius }) => ({
45
+ root: { '--my-radius': getRadius(radius) },
46
+ }));
47
+
48
+ export const MyComponent = factory<MyComponentFactory>((_props) => {
49
+ const props = useProps('MyComponent', defaultProps, _props);
50
+ const { classNames, className, style, styles, unstyled, vars, attributes, radius, ...others } = props;
51
+
52
+ const getStyles = useStyles<MyComponentFactory>({
53
+ name: 'MyComponent', classes, props,
54
+ className, style, classNames, styles, unstyled, vars, attributes, varsResolver,
55
+ });
56
+
57
+ return <Box {...getStyles('root')} {...others} />;
58
+ });
59
+
60
+ MyComponent.displayName = '@mantine/core/MyComponent';
61
+ MyComponent.classes = classes;
62
+ ```
63
+
64
+ ## Factory variant — which to use
65
+
66
+ | Scenario | Factory function | Type |
67
+ |---|---|---|
68
+ | Standard component | `factory()` | `Factory<{}>` |
69
+ | Supports `component` prop (polymorphic) | `polymorphicFactory()` | `PolymorphicFactory<{}>` — add `defaultComponent` and `defaultRef` |
70
+ | Props change based on a generic (e.g. `multiple`) | `genericFactory()` | `Factory<{ signature: ... }>` |
71
+
72
+ Use `polymorphicFactory` sparingly — it adds TypeScript overhead and slows IDE autocomplete.
73
+
74
+ ## Factory type fields
75
+
76
+ ```ts
77
+ Factory<{
78
+ props: MyComponentProps; // required
79
+ ref: HTMLDivElement; // element type for the forwarded ref
80
+ stylesNames: 'root' | 'inner'; // union of Styles API selectors
81
+ vars: { root: '--my-var' }; // CSS variable map per selector
82
+ variant: 'filled' | 'outline'; // accepted variant strings
83
+ staticComponents: { // sub-components (compound pattern)
84
+ Item: typeof MyComponentItem;
85
+ };
86
+ compound?: boolean; // true = sub-component; disables theme classNames/styles/vars
87
+ ctx?: MyContextType; // passed to styles/vars resolvers as third arg
88
+ signature?: (...) => JSX.Element; // only for genericFactory
89
+ }>
90
+ ```
91
+
92
+ ## Theme integration
93
+
94
+ Users and the theme can override defaults via `Component.extend()`:
95
+
96
+ ```ts
97
+ const theme = createTheme({
98
+ components: {
99
+ MyComponent: MyComponent.extend({
100
+ defaultProps: { radius: 'xl' },
101
+ classNames: { root: 'my-root' },
102
+ styles: { root: { color: 'red' } },
103
+ vars: (_theme, props) => ({ root: { '--my-radius': getRadius(props.radius) } }),
104
+ }),
105
+ },
106
+ });
107
+ ```
108
+
109
+ ## References
110
+
111
+ - **[`references/api.md`](references/api.md)** — All imports: `factory`, `useProps`, `useStyles`, `createVarsResolver`, `createSafeContext`, `StylesApiProps`, `CompoundStylesApiProps`, `BoxProps`, `ElementProps`, theme helpers (`getSize`, `getRadius`, etc.)
112
+ - **[`references/patterns.md`](references/patterns.md)** — Full examples: compound components with context, polymorphic component, generic component, theme integration