@scalepad/ui 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -119,10 +119,11 @@ export default defineConfig({
119
119
  });
120
120
  ```
121
121
 
122
- The preset wires up two things consumers commonly miss:
122
+ The preset wires up three things consumers commonly miss:
123
123
 
124
124
  1. The vanilla-extract Vite plugin (`@vanilla-extract/vite-plugin`), so every `.css.ts` file inside `node_modules/@scalepad/ui` is compiled at build time.
125
125
  2. `optimizeDeps.exclude: ['@scalepad/ui']` + `optimizeDeps.include: ['@mantine/dates', '@mantine/schedule', '@tiptap/react', 'dayjs', 'fast-deep-equal', 'prop-types']`, which together let Vite's esbuild pre-bundling step succeed (it can't ingest `.css.ts` files on its own) and ensure the transitive CommonJS deps reached through the excluded package still get pre-bundled.
126
+ 3. A narrowly-scoped Rollup `onLog` filter that drops the noisy `"default" is not exported by …/rrule/dist/esm/index.js` warning (also seen as `[IMPORT_IS_UNDEFINED] Import 'default' will always be undefined …` on older Rollup versions) emitted from `@mantine/schedule`'s `rrule` import. The warning is a static-analysis false positive — Mantine's source explicitly handles both module shapes (`"default" in rruleAll ? rruleAll.default.RRule : rruleAll.RRule`), so the dead branch is tree-shaken away at runtime. The filter is scoped to (importer is `@mantine/schedule` + message mentions `rrule`); other missing-export warnings still surface.
126
127
 
127
128
  You can extend the include list or pass options through to the vanilla-extract plugin:
128
129
 
@@ -149,7 +150,27 @@ import react from '@vitejs/plugin-react';
149
150
  import { defineConfig } from 'vite';
150
151
 
151
152
  export default defineConfig({
152
- plugins: [react(), vanillaExtractPlugin()],
153
+ plugins: [
154
+ react(),
155
+ vanillaExtractPlugin(),
156
+ {
157
+ // See README troubleshooting → "IMPORT_IS_UNDEFINED warning from rrule".
158
+ // Mantine's import is defensive; the flagged branch is dead code under
159
+ // rrule's ESM build, so the warning is safe to drop. Match on importer
160
+ // + message (not on `log.code`) because Rollup emits this under both
161
+ // `MISSING_EXPORT` and `IMPORT_IS_UNDEFINED` in different versions.
162
+ name: 'suppress-mantine-schedule-rrule-warning',
163
+ onLog(level, log) {
164
+ if (
165
+ level === 'warn' &&
166
+ (log.id ?? '').includes('@mantine/schedule') &&
167
+ /\brrule\b/.test(log.message ?? '')
168
+ ) {
169
+ return false;
170
+ }
171
+ },
172
+ },
173
+ ],
153
174
  optimizeDeps: {
154
175
  exclude: ['@scalepad/ui'],
155
176
  include: [
@@ -272,6 +293,23 @@ The vanilla-extract Vite plugin isn't wired up, or the package is being pre-bund
272
293
  - Use the preset: `import { scalepadUi } from '@scalepad/ui/vite'`, OR
273
294
  - If wiring manually, make sure your `vite.config.ts` has BOTH `vanillaExtractPlugin()` in `plugins` AND `optimizeDeps.exclude: ['@scalepad/ui']`. Missing either one will fail.
274
295
 
296
+ ### `[IMPORT_IS_UNDEFINED] Import 'default' will always be undefined because there is no matching export in 'node_modules/rrule/dist/esm/index.js'`
297
+
298
+ `@mantine/schedule` reads `rruleAll.default.RRule`, but `rrule@2.x` only has a `default` export on its CJS build (`dist/es5/rrule.js`), not its ESM build (`dist/esm/index.js`). Vite resolves `rrule` via its `module` field, so the static-analysis warning fires on every build — even when the scheduler is never imported.
299
+
300
+ The warning is a false positive. Mantine's source is explicitly defensive about both module shapes:
301
+
302
+ ```js
303
+ const RRule = "default" in rruleAll ? rruleAll.default.RRule : rruleAll.RRule;
304
+ ```
305
+
306
+ Under the ESM build, `"default" in rruleAll` is `false`, the `.default.RRule` branch is dead code, Rollup tree-shakes it away, and only `rruleAll.RRule` runs at runtime — which resolves correctly. Nothing breaks; the message is just noise.
307
+
308
+ Either:
309
+
310
+ - Use the preset (it adds a narrowly-scoped Rollup `onLog` filter for exactly this warning), OR
311
+ - If wiring manually, add the same filter — see the small `suppress-mantine-schedule-rrule-warning` plugin in the manual-setup snippet above.
312
+
275
313
  ### `The requested module '…@mantine/dates' does not provide an export named '…'` (or similar from `@tiptap/react`, `dayjs`, `prop-types`)
276
314
 
277
315
  These are CommonJS-or-mixed-module deps that Vite's dep scanner can't walk through once `@scalepad/ui` is excluded from pre-bundling. Either:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalepad/ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "ScalePad LM Design System — React + Mantine 9 + vanilla-extract component library",
@@ -52,6 +52,22 @@ function run(cmd, args, opts = {}) {
52
52
  return result;
53
53
  }
54
54
 
55
+ function runCapture(cmd, args, opts = {}) {
56
+ const display = `${cmd} ${args.join(' ')}`;
57
+ log(`$ ${display} (cwd=${opts.cwd ?? process.cwd()})`);
58
+ const result = spawnSync(cmd, args, {
59
+ stdio: ['inherit', 'pipe', 'pipe'],
60
+ encoding: 'utf8',
61
+ ...opts,
62
+ });
63
+ process.stdout.write(result.stdout ?? '');
64
+ process.stderr.write(result.stderr ?? '');
65
+ if (result.status !== 0) {
66
+ die(`${display} exited with status ${result.status}`);
67
+ }
68
+ return result;
69
+ }
70
+
55
71
  function findTarball(dir, prefix) {
56
72
  const matches = readdirSync(dir).filter(
57
73
  (f) => f.startsWith(prefix) && f.endsWith('.tgz'),
@@ -234,9 +250,26 @@ run('npm', ['install', '--no-audit', '--no-fund', '--legacy-peer-deps=false'], {
234
250
  cwd: projectDir,
235
251
  });
236
252
 
237
- // 5. Build.
253
+ // 5. Build. Capture output so we can assert no known-problematic Vite warnings
254
+ // snuck back in (they don't fail the build by default, so a plain run won't
255
+ // catch a regression).
238
256
  log('building...');
239
- run('npx', ['vite', 'build'], { cwd: projectDir });
257
+ const buildResult = runCapture('npx', ['vite', 'build'], { cwd: projectDir });
258
+ const buildOutput = `${buildResult.stdout ?? ''}\n${buildResult.stderr ?? ''}`;
259
+
260
+ // Regression guard for the rrule / @mantine/schedule warning. The
261
+ // `scalepadUi()` preset aliases `rrule` to its CJS UMD entry so this warning
262
+ // shouldn't appear. See README → Troubleshooting → IMPORT_IS_UNDEFINED.
263
+ if (
264
+ buildOutput.includes('IMPORT_IS_UNDEFINED') ||
265
+ /node_modules\/rrule\/dist\/esm\/index\.js/.test(buildOutput)
266
+ ) {
267
+ die(
268
+ 'detected rrule IMPORT_IS_UNDEFINED warning in build output — the ' +
269
+ 'scalepadUi() preset should be aliasing rrule to rrule/dist/es5/rrule.js. ' +
270
+ 'See packages/ui/src/vite.js.',
271
+ );
272
+ }
240
273
 
241
- log('OK — built successfully.');
274
+ log('OK — built successfully (no known-problematic warnings).');
242
275
  log(`(temp project left at ${projectDir} for inspection; delete when done)`);
@@ -53,7 +53,7 @@ export const root = style({
53
53
  * drift.
54
54
  */
55
55
  export const variant = styleVariants(textStyleVariants, styles => ({
56
- fontFamily: styles.fontFamily,
56
+ ...(styles.fontFamily != null && { fontFamily: styles.fontFamily }),
57
57
  fontWeight: styles.fontWeight,
58
58
  fontSize: styles.fontSize,
59
59
  lineHeight: styles.lineHeight,
@@ -0,0 +1,32 @@
1
+ import figma from '@figma/code-connect';
2
+
3
+ import { ColorInput } from './ColorInput';
4
+
5
+ /**
6
+ * Code Connect mapping for the LM Design System `ColorInput` (node 4265:237).
7
+ *
8
+ * The Figma component_set has `Size` (Small / Regular / Large) and `State`
9
+ * (Empty / Value / Focus / Error / Error Focus / Disabled) axes. We
10
+ * intentionally don't map the size axis: in code the component follows the
11
+ * `TextInput` chrome (single default size, overridable via `size`), and
12
+ * Figma will be re-aligned to match later. Only the boolean-ish state axes
13
+ * map cleanly today (`disabled`, `error`).
14
+ */
15
+ figma.connect(
16
+ ColorInput,
17
+ 'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=4265%3A237',
18
+ {
19
+ props: {
20
+ disabled: figma.enum('State', {
21
+ Disabled: true,
22
+ }),
23
+ error: figma.enum('State', {
24
+ Error: 'Invalid color',
25
+ 'Error Focus': 'Invalid color',
26
+ }),
27
+ },
28
+ example: props => (
29
+ <ColorInput disabled={props.disabled} error={props.error} />
30
+ ),
31
+ },
32
+ );
@@ -0,0 +1,94 @@
1
+ /**
2
+ * ColorInput component
3
+ *
4
+ * Thin wrapper around Mantine's `ColorInput` that mirrors the conventions of
5
+ * our `TextInput` wrapper (`size='sm'`, `radius='lg'`, `format='hex'`) and
6
+ * pre-loads the swatch grid with `AVATAR_PALETTE` — the 50-color list
7
+ * `@scalepad/ui` already uses for identity chips. Reusing the same palette
8
+ * here keeps every "pick a color" surface in the app consistent without
9
+ * making consumers wire `swatches` themselves.
10
+ *
11
+ * Visual chrome (height, padding, radius, focus ring, error state, disabled
12
+ * state) intentionally follows `TextInput` rather than the standalone Figma
13
+ * ColorInput frame. Figma will be aligned later.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * <ColorInput
18
+ * label="Brand color"
19
+ * value={color}
20
+ * onChange={setColor}
21
+ * />
22
+ * ```
23
+ *
24
+ * @example Custom swatches
25
+ * ```tsx
26
+ * <ColorInput
27
+ * swatches={['#ff0000', '#00ff00', '#0000ff']}
28
+ * swatchesPerRow={3}
29
+ * />
30
+ * ```
31
+ */
32
+
33
+ import { forwardRef, useMemo } from 'react';
34
+
35
+ import {
36
+ ColorInput as MantineColorInput,
37
+ type ColorInputProps as MantineColorInputProps,
38
+ } from '@mantine/core';
39
+
40
+ import { AVATAR_PALETTE } from '../../utils/avatar';
41
+
42
+ /**
43
+ * Default swatch grid for `ColorInput`. Mirrors `AVATAR_PALETTE` and is
44
+ * exported so consumers can compose it with extra brand-specific swatches
45
+ * (`swatches={[...DEFAULT_COLOR_INPUT_SWATCHES, '#abc123']}`).
46
+ */
47
+ export const DEFAULT_COLOR_INPUT_SWATCHES: readonly string[] = AVATAR_PALETTE;
48
+
49
+ /**
50
+ * Default number of swatches per row. 10 columns × 5 rows fits the 50-entry
51
+ * `AVATAR_PALETTE` cleanly without forcing the popover wider than the input.
52
+ */
53
+ export const DEFAULT_COLOR_INPUT_SWATCHES_PER_ROW = 10;
54
+
55
+ export interface ColorInputProps extends MantineColorInputProps {}
56
+
57
+ export const ColorInput = forwardRef<HTMLInputElement, ColorInputProps>(
58
+ (
59
+ {
60
+ className,
61
+ size = 'sm',
62
+ radius = 'lg',
63
+ format = 'hex',
64
+ swatches,
65
+ swatchesPerRow = DEFAULT_COLOR_INPUT_SWATCHES_PER_ROW,
66
+ ...props
67
+ },
68
+ ref,
69
+ ) => {
70
+ // `swatches` is mutated by Mantine internals in some flows, so feed it a
71
+ // fresh array (not the frozen palette constant) when defaulting.
72
+ const resolvedSwatches = useMemo(
73
+ () => swatches ?? [...DEFAULT_COLOR_INPUT_SWATCHES],
74
+ [swatches],
75
+ );
76
+
77
+ return (
78
+ <MantineColorInput
79
+ ref={ref}
80
+ size={size}
81
+ radius={radius}
82
+ format={format}
83
+ swatches={resolvedSwatches}
84
+ swatchesPerRow={swatchesPerRow}
85
+ classNames={{
86
+ input: className,
87
+ }}
88
+ {...props}
89
+ />
90
+ );
91
+ },
92
+ );
93
+
94
+ ColorInput.displayName = 'ColorInput';
@@ -0,0 +1,6 @@
1
+ export {
2
+ ColorInput,
3
+ DEFAULT_COLOR_INPUT_SWATCHES,
4
+ DEFAULT_COLOR_INPUT_SWATCHES_PER_ROW,
5
+ } from './ColorInput';
6
+ export type { ColorInputProps } from './ColorInput';
@@ -36,13 +36,18 @@ export const selectButton = style({
36
36
  overflow: 'hidden',
37
37
  transition: 'border-color 0.15s ease',
38
38
  selectors: {
39
- '&:hover': {
39
+ '&:hover:not(:disabled)': {
40
40
  borderColor: tokens.color.stroke.subduedStrong,
41
41
  },
42
- '&:focus': focusRing,
43
- '&:active': {
42
+ '&:focus:not(:disabled)': focusRing,
43
+ '&:active:not(:disabled)': {
44
44
  borderColor: tokens.color.stroke.strong,
45
45
  },
46
+ '&:disabled': {
47
+ cursor: 'not-allowed',
48
+ opacity: 0.6,
49
+ backgroundColor: tokens.color.background.disabledDefault,
50
+ },
46
51
  [`${mantineVars.darkSelector} &`]: {
47
52
  borderColor: tokens.color.stroke.default,
48
53
  backgroundColor: tokens.color.background.default,
@@ -35,6 +35,11 @@ export interface SelectProps {
35
35
  * Validation error message displayed below the select
36
36
  */
37
37
  error?: string;
38
+ /**
39
+ * Disable the select trigger. When disabled, the dropdown cannot be opened
40
+ * and the button is excluded from the focus order.
41
+ */
42
+ disabled?: boolean;
38
43
  }
39
44
 
40
45
  /**
@@ -55,7 +60,10 @@ export interface SelectProps {
55
60
  * ```
56
61
  */
57
62
  export const Select = forwardRef<HTMLButtonElement, SelectProps>(
58
- ({ labelPrefix, data, value, onChange, placeholder, w, error }, ref) => {
63
+ (
64
+ { labelPrefix, data, value, onChange, placeholder, w, error, disabled },
65
+ ref,
66
+ ) => {
59
67
  const id = useId();
60
68
  const errorId = error ? `${id}-error` : undefined;
61
69
  const combobox = useCombobox({
@@ -88,11 +96,13 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
88
96
  <button
89
97
  ref={ref}
90
98
  type="button"
99
+ disabled={disabled}
91
100
  className={styles.selectButton}
92
101
  onClick={() => combobox.toggleDropdown()}
93
102
  aria-haspopup="listbox"
94
103
  aria-expanded={combobox.dropdownOpened}
95
104
  aria-invalid={!!error}
105
+ aria-disabled={disabled || undefined}
96
106
  aria-describedby={errorId}
97
107
  style={
98
108
  w ? { width: typeof w === 'number' ? `${w}px` : w } : undefined
package/src/index.ts CHANGED
@@ -287,6 +287,12 @@ export type {
287
287
  export type { JSONContent } from '@tiptap/react';
288
288
 
289
289
  // Input components with Figma design specifications
290
+ export {
291
+ ColorInput,
292
+ DEFAULT_COLOR_INPUT_SWATCHES,
293
+ DEFAULT_COLOR_INPUT_SWATCHES_PER_ROW,
294
+ } from './components/ColorInput';
295
+ export type { ColorInputProps } from './components/ColorInput';
290
296
  export { CommentComposer } from './components/CommentComposer';
291
297
  export type { CommentComposerProps } from './components/CommentComposer';
292
298
  export { MultiSelect } from './components/MultiSelect';
@@ -1,6 +1,10 @@
1
1
  /**
2
- * AUTO-GENERATED - DO NOT EDIT
3
- * Figma Design Tokens
2
+ * Design tokens that mirror the LM Design System Figma file
3
+ * (https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System).
4
+ *
5
+ * Source of truth lives in Figma; edit these files by hand to mirror Figma
6
+ * changes and use the Figma MCP (`get_design_context`, `search_design_system`)
7
+ * to fetch authoritative values.
4
8
  */
5
9
 
6
10
  export * from './colors';
@@ -1,6 +1,11 @@
1
1
  /**
2
- * AUTO-GENERATED - DO NOT EDIT
3
- * Generated from: packages/figma-tokens/source/text-styles.json + typography.json
2
+ * Text-style variants synced from the LM Design System Figma file
3
+ * (https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System).
4
+ *
5
+ * Source of truth lives in Figma; edit this file by hand to mirror Figma
6
+ * changes. The Figma MCP is the canonical way to fetch the latest values —
7
+ * use `get_design_context` on the relevant text-style node before changing
8
+ * sizes / weights / line-heights / letter-spacing.
4
9
  */
5
10
 
6
11
  import { rem } from '@mantine/core';
@@ -73,6 +78,27 @@ export const textStyleVariants = {
73
78
  lineHeight: rem('21px'),
74
79
  letterSpacing: 0,
75
80
  },
81
+ body2: {
82
+ fontFamily: undefined,
83
+ fontWeight: 400,
84
+ fontSize: rem('16px'),
85
+ lineHeight: rem('23px'),
86
+ letterSpacing: 0,
87
+ },
88
+ 'body2.strong': {
89
+ fontFamily: undefined,
90
+ fontWeight: 500,
91
+ fontSize: rem('16px'),
92
+ lineHeight: rem('23px'),
93
+ letterSpacing: 0,
94
+ },
95
+ 'body2.stronger': {
96
+ fontFamily: undefined,
97
+ fontWeight: 600,
98
+ fontSize: rem('16px'),
99
+ lineHeight: rem('23px'),
100
+ letterSpacing: 0,
101
+ },
76
102
  caption1: {
77
103
  fontFamily: undefined,
78
104
  fontWeight: 400,
@@ -166,6 +192,9 @@ export type BodyVariant =
166
192
  | 'body1'
167
193
  | 'body1.strong'
168
194
  | 'body1.stronger'
195
+ | 'body2'
196
+ | 'body2.strong'
197
+ | 'body2.stronger'
169
198
  | 'caption1'
170
199
  | 'caption1.strong'
171
200
  | 'caption1.stronger'
package/src/vite.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Vite preset for `@scalepad/ui`.
3
3
  *
4
- * @scalepad/ui ships as TypeScript + vanilla-extract source. Two things must
4
+ * @scalepad/ui ships as TypeScript + vanilla-extract source. Three things must
5
5
  * be wired into a consumer's Vite config for it to load cleanly:
6
6
  *
7
7
  * 1. The vanilla-extract Vite plugin, so every `.css.ts` file the package
@@ -14,8 +14,24 @@
14
14
  * - `include: [...]` for the CommonJS transitive deps Vite's scanner
15
15
  * stops walking through once the package is excluded from
16
16
  * pre-bundling.
17
+ * 3. A narrowly-scoped Rollup `onLog` filter that suppresses the
18
+ * `MISSING_EXPORT` / `IMPORT_IS_UNDEFINED` warning emitted from
19
+ * `@mantine/schedule`'s `rrule` import (Rollup uses the former in
20
+ * current versions and the latter in older ones for the same root
21
+ * cause). The warning is a static-analysis false positive: the Mantine
22
+ * source does
17
23
  *
18
- * This preset wraps both into a single plugin so consumers can do:
24
+ * const RRule = "default" in rruleAll ? rruleAll.default.RRule : rruleAll.RRule;
25
+ *
26
+ * which is explicitly defensive about both module shapes. Under rrule's
27
+ * ESM build the `default` branch is dead code (Rollup tree-shakes it
28
+ * away); only `rruleAll.RRule` runs and it resolves correctly. Rollup
29
+ * still flags the dead branch at scan time, producing a noisy warning on
30
+ * every build. The filter is scoped to (importer is `@mantine/schedule`
31
+ * + message mentions `rrule`) so unrelated missing-export / undefined-
32
+ * import warnings still surface.
33
+ *
34
+ * This preset wraps all three into a single plugin so consumers can do:
19
35
  *
20
36
  * import { scalepadUi } from '@scalepad/ui/vite';
21
37
  *
@@ -53,14 +69,30 @@ export const SCALEPAD_UI_OPTIMIZE_DEPS_INCLUDE = Object.freeze([
53
69
  'prop-types',
54
70
  ]);
55
71
 
72
+ /**
73
+ * True iff `log` is the known-noisy rrule / @mantine/schedule warning
74
+ * described in the file header. Rollup emits this under more than one code
75
+ * depending on which static-analysis pass catches it first — `MISSING_EXPORT`
76
+ * ("default" is not exported by …) and `IMPORT_IS_UNDEFINED` (Import
77
+ * `default` will always be undefined …) are both seen in the wild for the
78
+ * exact same import. We match by (importer is @mantine/schedule + message
79
+ * mentions rrule), which is tight enough that unrelated missing-export
80
+ * warnings still surface but loose enough to survive Rollup version drift.
81
+ */
82
+ function isRruleScheduleDeadBranchWarning(log) {
83
+ if (!log) return false;
84
+ const importer = log.id ?? log.loc?.file ?? '';
85
+ const message = log.message ?? '';
86
+ return importer.includes('@mantine/schedule') && /\brrule\b/.test(message);
87
+ }
88
+
56
89
  export function scalepadUi(options = {}) {
57
- const includeFromOptions =
58
- options.optimizeDepsIncludeOverride ?? [
59
- ...SCALEPAD_UI_OPTIMIZE_DEPS_INCLUDE,
60
- ...(options.optimizeDepsInclude ?? []),
61
- ];
90
+ const includeFromOptions = options.optimizeDepsIncludeOverride ?? [
91
+ ...SCALEPAD_UI_OPTIMIZE_DEPS_INCLUDE,
92
+ ...(options.optimizeDepsInclude ?? []),
93
+ ];
62
94
 
63
- const optimizeDepsPlugin = {
95
+ const presetPlugin = {
64
96
  name: '@scalepad/ui/vite-preset',
65
97
  config: () => ({
66
98
  optimizeDeps: {
@@ -68,12 +100,21 @@ export function scalepadUi(options = {}) {
68
100
  include: includeFromOptions,
69
101
  },
70
102
  }),
103
+ // Rollup plugin hook (Rollup 4+, used by Vite 5+). Returning `false`
104
+ // suppresses the log without clobbering the consumer's own `onwarn` or
105
+ // any other plugin's `onLog` — Rollup chains every plugin's handler.
106
+ onLog(level, log) {
107
+ if (level === 'warn' && isRruleScheduleDeadBranchWarning(log)) {
108
+ return false;
109
+ }
110
+ return undefined;
111
+ },
71
112
  };
72
113
 
73
114
  const veResult = vanillaExtractPlugin(options.vanillaExtract);
74
115
  const vePlugins = Array.isArray(veResult) ? veResult : [veResult];
75
116
 
76
- return [...vePlugins, optimizeDepsPlugin];
117
+ return [...vePlugins, presetPlugin];
77
118
  }
78
119
 
79
120
  export default scalepadUi;