@scalepad/ui 0.1.0 → 0.2.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,15 +1,27 @@
1
1
  /**
2
- * Semantic color tokens synced from Figma Design System (VCLfybgU3OaUUPrQdBaVmP).
2
+ * Semantic color tokens.
3
+ *
3
4
  * Values reference primitives in colors.ts; inline rgba for alpha variants.
4
- * Figma source of truth: Tokens collection (Light / Dark modes).
5
+ *
6
+ * NOTE: The dark-mode map and several new tokens (`background.surface.*`,
7
+ * `background.entity.*`, `chart.series.*`) are intentionally diverged from the
8
+ * Figma Design System (VCLfybgU3OaUUPrQdBaVmP) and are hand-tuned in code. The
9
+ * Figma source will be back-synced to match these values; until then, code is
10
+ * the source of truth.
11
+ *
12
+ * Original Figma source of truth: Tokens collection (Light / Dark modes).
5
13
  */
6
14
 
7
15
  import {
8
16
  black,
9
17
  dark,
18
+ darkAlpha,
10
19
  gray,
11
20
  grayAlpha,
12
21
  green,
22
+ greenAlpha,
23
+ grape,
24
+ grapeAlpha,
13
25
  indigo,
14
26
  indigoAlpha,
15
27
  red,
@@ -105,91 +117,183 @@ export const semanticColors = {
105
117
  'background.input': white,
106
118
  'background.backdrop': grayAlpha['9-60'],
107
119
  'icon.medium': gray[7],
120
+
121
+ // Elevated-surface tokens. In light mode both raised and overlay share the
122
+ // default white surface — depth comes from shadow, not color. In dark mode
123
+ // they step up the dark scale for visual elevation.
124
+ 'background.surface.raised': white,
125
+ 'background.surface.overlay': white,
126
+
127
+ // Entity-accent tokens (used by Spotlight Quick Create + similar surfaces).
128
+ // Each pair maps to one of the five semantic role families so they auto-flip
129
+ // with mode. The fifth slot (deliverable) borrows the grape family because
130
+ // there's no sixth core semantic role.
131
+ 'background.entity.task.light': green[0],
132
+ 'background.entity.task.icon': green[7],
133
+ 'background.entity.initiative.light': indigoAlpha['10'],
134
+ 'background.entity.initiative.icon': indigo[7],
135
+ 'background.entity.goal.light': teal[0],
136
+ 'background.entity.goal.icon': teal[7],
137
+ 'background.entity.meeting.light': yellowAlpha['10'],
138
+ 'background.entity.meeting.icon': yellow[7],
139
+ 'background.entity.deliverable.light': grape[0],
140
+ 'background.entity.deliverable.icon': grape[7],
141
+
142
+ // Chart series — deterministic, tuned-for-stacking palette consumed by
143
+ // budget / dashboard / trend charts. Sorted by visual weight; series 1 is
144
+ // the primary brand and the rest stack underneath.
145
+ 'chart.series.1': green[7],
146
+ 'chart.series.2': indigo[6],
147
+ 'chart.series.3': teal[6],
148
+ 'chart.series.4': yellow[7],
149
+ 'chart.series.5': red[7],
150
+ 'chart.series.6': grape[6],
108
151
  } as const;
109
152
 
110
153
  /** Dark mode semantic color values */
111
154
  export const semanticColorsDark = {
112
- 'text.title': dark[0],
113
- 'text.danger.default': red[7],
114
- 'text.link': green[3],
115
- 'background.default': dark[6],
116
- 'background.default-hover': dark[5],
117
- 'text.default': white,
118
- 'stroke.default': dark[4],
119
- 'stroke.light': dark[5],
120
- 'background.body': dark[7],
121
- 'text.disabled.default': dark[3],
155
+ // Foundation surfaces — distinct levels for real depth hierarchy.
156
+ 'background.body': dark[8],
157
+ 'background.default': dark[7],
158
+ 'background.default-hover': dark[6],
122
159
  'background.disabled.default': dark[6],
123
160
  'background.transparent': transparent,
124
- 'background.primary.filled': green[5],
125
- 'background.primary.filled-hover': green[4],
126
- 'background.warning.filled': yellow[8],
127
- 'background.success.filled': teal[8],
128
- 'background.subdued.light': dark[5],
129
- 'background.subdued.ultralight': dark[6],
130
- 'background.danger.filled': red[8],
131
- 'background.subdued.filled': gray[7],
132
- 'background.warning.filled-hover': yellow[9],
133
- 'background.success.filled-hover': teal[9],
134
- 'background.danger.filled-hover': red[9],
135
- 'background.subdued.filled-hover': gray[8],
136
- 'background.danger.light': red[4],
137
- 'background.primary.light': green[9],
138
- 'background.warning.light': yellowAlpha['15'],
139
- 'background.success.light': tealAlpha['15'],
140
- 'text.subdued.default': gray[3],
141
- 'text.primary.default': green[3],
142
- 'text.primary.light': green[4],
143
- 'text.warning.default': yellow[3],
144
- 'text.success.default': teal[3],
145
- 'background.primary.light-hover': green[8],
161
+ 'background.input': dark[7],
162
+ 'background.backdrop': darkAlpha['70'],
163
+
164
+ 'background.surface.raised': dark[6],
165
+ 'background.surface.overlay': dark[5],
166
+
167
+ // Subdued (neutral) surfaces — tied to the dark scale so they keep their
168
+ // relationship with `background.default`. `ultralight` is intentionally a
169
+ // *deeper* recess in dark mode (`dark[9]`, two steps below the card) rather
170
+ // than the single off-white step of light mode — near-black perceptually
171
+ // compresses, so the wrapper needs more delta to read as a distinct frame
172
+ // behind cards (kanban columns, assessment blocks, drawer wells).
173
+ 'background.subdued.light': dark[6],
174
+ 'background.subdued.light-hover': dark[5],
175
+ 'background.subdued.ultralight': dark[9],
176
+ 'background.subdued.filled': dark[5],
177
+ 'background.subdued.filled-hover': dark[4],
178
+
179
+ // Brand / role filled surfaces — keep the strong saturated hue so primary
180
+ // CTAs still read as primary in dark.
181
+ 'background.primary.filled': green[7],
182
+ 'background.primary.filled-hover': green[6],
183
+ 'background.warning.filled': yellow[6],
184
+ 'background.warning.filled-hover': yellow[7],
185
+ 'background.success.filled': teal[6],
186
+ 'background.success.filled-hover': teal[7],
187
+ 'background.danger.filled': red[7],
188
+ 'background.danger.filled-hover': red[8],
189
+ 'background.information.filled': indigo[6],
190
+ 'background.information.filled-hover': indigo[7],
191
+
192
+ // Soft / tinted role surfaces — translucent washes over the dark base, so
193
+ // they read as brand-tinted instead of near-black.
194
+ 'background.primary.light': greenAlpha['12'],
195
+ 'background.primary.light-hover': greenAlpha['20'],
196
+ 'background.warning.light': yellowAlpha['12'],
146
197
  'background.warning.light-hover': yellowAlpha['20'],
198
+ 'background.success.light': tealAlpha['12'],
147
199
  'background.success.light-hover': tealAlpha['20'],
148
- 'background.danger.light-hover': red[5],
149
- 'background.subdued.light-hover': dark[6],
150
- 'stroke.primary.default': green[6],
151
- 'text.danger.disabled': redAlpha['50'],
152
- 'stroke.success.default': teal[3],
153
- 'stroke.danger.default': red[7],
154
- 'stroke.subdued.strong': gray[3],
155
- 'stroke.warning.default': yellow[3],
156
- 'background.information.filled': indigo[8],
157
- 'background.information.filled-hover': indigo[9],
158
- 'background.information.light': indigoAlpha['15'],
200
+ 'background.danger.light': redAlpha['12'],
201
+ 'background.danger.light-hover': redAlpha['20'],
202
+ 'background.information.light': indigoAlpha['12'],
159
203
  'background.information.light-hover': indigoAlpha['20'],
160
- 'stroke.information.default': indigo[3],
161
- 'text.information.default': indigo[3],
162
- 'stroke.focus.default': green[3],
204
+
205
+ // Inverse fills (e.g. tooltips, default-on-dark anchors) — light surface,
206
+ // dark text/icon.
163
207
  'background.inverse.filled': dark[0],
164
208
  'background.inverse.filled-hover': dark[1],
165
- 'icon.light': dark[3],
166
- 'icon.danger.default': red[7],
167
- 'icon.danger.disabled': redAlpha['50'],
168
- 'text.danger.strong': red[9],
169
- 'text.warning.strong': yellow[9],
170
- 'text.success.strong': teal[9],
171
- 'text.information.strong': indigo[9],
172
- 'icon.warning.default': yellow[5],
173
- 'icon.warning.strong': yellow[9],
174
- 'icon.danger.strong': red[9],
175
- 'icon.information.default': indigo[5],
176
- 'icon.information.strong': indigo[9],
177
- 'icon.success.default': teal[5],
178
- 'icon.success.strong': teal[9],
179
- 'text.subdued.strong': gray[6],
180
- 'icon.default': dark[1],
181
- 'stroke.inverse': dark[6],
182
- 'stroke.subdued.default': gray[6],
183
- 'text.inverse': dark[9],
209
+
210
+ // Entity accents — soft translucent role washes for the icon block,
211
+ // strong saturated tone for the icon foreground.
212
+ 'background.entity.task.light': greenAlpha['12'],
213
+ 'background.entity.task.icon': green[3],
214
+ 'background.entity.initiative.light': indigoAlpha['15'],
215
+ 'background.entity.initiative.icon': indigo[3],
216
+ 'background.entity.goal.light': tealAlpha['12'],
217
+ 'background.entity.goal.icon': teal[3],
218
+ 'background.entity.meeting.light': yellowAlpha['15'],
219
+ 'background.entity.meeting.icon': yellow[4],
220
+ 'background.entity.deliverable.light': grapeAlpha['15'],
221
+ 'background.entity.deliverable.icon': grape[3],
222
+
223
+ // Text colors — sit on the dark surface scale. AA contrast against
224
+ // background.body (dark[8]) verified at component-build time.
225
+ 'text.title': dark[0],
226
+ 'text.default': dark[1],
227
+ 'text.subdued.default': dark[2],
228
+ 'text.subdued.strong': dark[1],
229
+ 'text.disabled.default': dark[3],
230
+ // `text.inverse` is the label color that sits on FILLED / SATURATED brand
231
+ // backgrounds (primary button green, danger red, avatar colour swatches,
232
+ // dark tooltip surfaces). Those backgrounds keep the same hue in both
233
+ // modes, so their contrast text must stay near-white — flipping this to a
234
+ // dark value produces unreadable black-on-green / black-on-red elements
235
+ // in dark mode. Keep this in sync with the light-mode value.
236
+ 'text.inverse': white,
237
+ 'text.link': green[3],
238
+
239
+ // Brand / role text — bright, readable on dark surfaces.
240
+ 'text.primary.default': green[3],
241
+ 'text.primary.light': green[4],
242
+ 'text.warning.default': yellow[4],
243
+ 'text.warning.strong': yellow[2],
244
+ 'text.success.default': teal[3],
245
+ 'text.success.strong': teal[2],
246
+ 'text.danger.default': red[4],
247
+ 'text.danger.strong': red[2],
248
+ 'text.danger.disabled': redAlpha['50'],
249
+ 'text.information.default': indigo[3],
250
+ 'text.information.strong': indigo[2],
251
+
252
+ // Strokes — borders, dividers, focus rings.
253
+ 'stroke.default': dark[4],
254
+ 'stroke.light': dark[5],
184
255
  'stroke.strong': dark[2],
185
- 'icon.inverse': dark[9],
256
+ 'stroke.subdued.default': dark[5],
257
+ 'stroke.subdued.strong': dark[3],
258
+ // `stroke.inverse` is the border color used by `outline-inverse` button /
259
+ // icon-button variants that sit on top of saturated coloured backgrounds.
260
+ // Same reasoning as `text.inverse` — the host surface doesn't flip, so the
261
+ // border on top stays white in both modes.
262
+ 'stroke.inverse': white,
186
263
  'stroke.icon': dark[1],
264
+ 'stroke.focus.default': green[4],
265
+ 'stroke.focus.strong': green[3],
266
+ 'stroke.primary.default': green[5],
267
+ 'stroke.success.default': teal[4],
268
+ 'stroke.danger.default': red[5],
269
+ 'stroke.warning.default': yellow[5],
270
+ 'stroke.information.default': indigo[4],
271
+
272
+ // Icons.
273
+ 'icon.default': dark[1],
274
+ 'icon.light': dark[3],
275
+ 'icon.medium': dark[2],
276
+ 'icon.inverse': dark[9],
187
277
  'icon.primary.default': green[3],
188
- 'icon.primary.strong': green[1],
189
- 'stroke.focus.strong': green[1],
190
- 'background.input': dark[6],
191
- 'background.backdrop': grayAlpha['9-60'],
192
- 'icon.medium': dark[4],
278
+ 'icon.primary.strong': green[2],
279
+ 'icon.danger.default': red[4],
280
+ 'icon.danger.strong': red[2],
281
+ 'icon.danger.disabled': redAlpha['50'],
282
+ 'icon.warning.default': yellow[4],
283
+ 'icon.warning.strong': yellow[2],
284
+ 'icon.success.default': teal[3],
285
+ 'icon.success.strong': teal[2],
286
+ 'icon.information.default': indigo[3],
287
+ 'icon.information.strong': indigo[2],
288
+
289
+ // Chart series — brighter values so series read clearly against the dark
290
+ // surface. Order matches the light map for stable legends.
291
+ 'chart.series.1': green[4],
292
+ 'chart.series.2': indigo[3],
293
+ 'chart.series.3': teal[3],
294
+ 'chart.series.4': yellow[4],
295
+ 'chart.series.5': red[4],
296
+ 'chart.series.6': grape[3],
193
297
  } as const;
194
298
 
195
299
  function semanticKeyToCssVarName(key: string): string {
@@ -1,41 +1,37 @@
1
1
  /**
2
2
  * Semantic design token CSS custom properties (radius, spacing, shadows, z-index, font).
3
3
  * Injected by ThemeProvider so components can use var(--radius-*), var(--spacing-*), etc.
4
+ *
5
+ * Shadows are emitted twice — once under the light scheme selector, once under
6
+ * the dark scheme selector — so `--shadow-*` flips automatically with the
7
+ * Mantine color scheme. All other tokens (radius, spacing, z-index, font) are
8
+ * mode-agnostic and live in a single shared block.
4
9
  */
5
10
 
6
11
  import { figmaRadius } from './radius';
7
- import { figmaShadows } from './shadows';
12
+ import { figmaShadows, figmaShadowsDark } from './shadows';
8
13
  import { figmaSpacing } from './spacing';
9
14
  import { figmaFontFamilies } from './typography';
10
15
  import { zIndex } from './zIndex';
11
16
 
12
- function buildSemanticTokensCss(): string {
17
+ function buildSharedTokensCss(): string {
13
18
  const lines: string[] = [
14
19
  ':root, [data-mantine-color-scheme="light"], [data-mantine-color-scheme="dark"] {',
15
20
  ];
16
21
 
17
- // Radius
18
22
  for (const [key, value] of Object.entries(figmaRadius)) {
19
23
  lines.push(` --radius-${key}: ${value};`);
20
24
  }
21
25
  lines.push(` --radius-default: ${figmaRadius.lg};`);
22
26
 
23
- // Spacing
24
27
  for (const [key, value] of Object.entries(figmaSpacing)) {
25
28
  lines.push(` --spacing-${key}: ${value};`);
26
29
  }
27
30
 
28
- // Shadows
29
- for (const [key, value] of Object.entries(figmaShadows)) {
30
- lines.push(` --shadow-${key}: ${value};`);
31
- }
32
-
33
- // Z-index
34
31
  for (const [key, value] of Object.entries(zIndex)) {
35
32
  lines.push(` --z-index-${key}: ${value};`);
36
33
  }
37
34
 
38
- // Font families
39
35
  lines.push(
40
36
  ` --font-family-monospace: "${figmaFontFamilies['font-family-monospace']}", "Courier New", monospace;`,
41
37
  );
@@ -50,4 +46,30 @@ function buildSemanticTokensCss(): string {
50
46
  return lines.join('\n');
51
47
  }
52
48
 
53
- export const semanticTokensCss = buildSemanticTokensCss();
49
+ function buildShadowCss(
50
+ shadowMap: Record<string, string>,
51
+ selector: string,
52
+ ): string {
53
+ const lines = [`${selector} {`];
54
+ for (const [key, value] of Object.entries(shadowMap)) {
55
+ // Emit both --shadow-* (used by vanilla-extract `tokens.shadow.*`) and
56
+ // --mantine-shadow-* (consumed by Mantine's Card/Paper/Modal defaults)
57
+ // so the entire component surface flips with the active color scheme.
58
+ lines.push(` --shadow-${key}: ${value};`);
59
+ lines.push(` --mantine-shadow-${key}: ${value};`);
60
+ }
61
+ lines.push('}');
62
+ return lines.join('\n');
63
+ }
64
+
65
+ const sharedTokensCss = buildSharedTokensCss();
66
+ const lightShadowsCss = buildShadowCss(
67
+ figmaShadows,
68
+ ':root, [data-mantine-color-scheme="light"]',
69
+ );
70
+ const darkShadowsCss = buildShadowCss(
71
+ figmaShadowsDark,
72
+ '[data-mantine-color-scheme="dark"]',
73
+ );
74
+
75
+ export const semanticTokensCss = `${sharedTokensCss}\n${lightShadowsCss}\n${darkShadowsCss}`;
@@ -1,11 +1,35 @@
1
1
  /**
2
- * AUTO-GENERATED - DO NOT EDIT
3
- * Generated from: packages/figma-tokens/source/shadows.json
2
+ * Shadow tokens.
3
+ *
4
+ * Both modes deliberately keep cards / papers / modals / drawers / headers
5
+ * flat: depth is carried by the body↔surface background delta plus the 1px
6
+ * `stroke.default` border on each surface. The original Figma export used a
7
+ * 10% black drop-shadow on every elevation level, which stacked up to a
8
+ * visually noisy "everything floats" look once cards were nested inside
9
+ * drawers, modals, and dashboards.
10
+ *
11
+ * `lg` is the one exception: floating overlays (popovers, picker flyouts)
12
+ * sit on top of scrollable canvas content where a border alone does not
13
+ * read as "lifted off the page". They get a single soft drop-shadow tuned
14
+ * per mode so they remain clearly elevated.
4
15
  */
5
16
 
6
17
  export const figmaShadows = {
7
- sm: '0px 1px 3px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px -1px rgba(0, 0, 0, 0.10)',
8
- md: '0px 4px 6px -1px rgba(0, 0, 0, 0.10), 0px 2px 4px -2px rgba(0, 0, 0, 0.10)',
9
- lg: '0px 10px 15px -3px rgba(0, 0, 0, 0.10), 0px 4px -4px 6px rgba(0, 0, 0, 0.10)',
10
- xl: '0px 20px 25px -5px rgba(0, 0, 0, 0.10), 0px 8px -6px 10px rgba(0, 0, 0, 0.10)',
18
+ sm: 'none',
19
+ md: 'none',
20
+ lg: '0px 8px 16px -4px rgba(0, 0, 0, 0.08), 0px 2px 4px -2px rgba(0, 0, 0, 0.06)',
21
+ xl: 'none',
22
+ } as const;
23
+
24
+ /**
25
+ * Dark-mode shadow values. Same flat treatment as light mode, but `lg` uses
26
+ * a heavier black alpha because the underlying surfaces are already dark —
27
+ * a 10% black drop-shadow that reads as elevation in light mode disappears
28
+ * entirely against `dark[7]`/`dark[8]`.
29
+ */
30
+ export const figmaShadowsDark = {
31
+ sm: 'none',
32
+ md: 'none',
33
+ lg: '0px 8px 16px -4px rgba(0, 0, 0, 0.25)',
34
+ xl: 'none',
11
35
  } as const;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Mantine typography style props that the design system intentionally locks
3
+ * down. Components driven by a `variant` (e.g. `Text`, `Title`, `Anchor`)
4
+ * Omit this set from their public props so callers can't bypass the variant
5
+ * system with one-off `fz="20px"` or `fw={700}` overrides.
6
+ *
7
+ * Internally those components also avoid forwarding these props to the
8
+ * underlying Mantine element — the variant is the only path that can set
9
+ * typography.
10
+ */
11
+ export type TypographyStyleProp =
12
+ | 'fw'
13
+ | 'fz'
14
+ | 'size'
15
+ | 'lh'
16
+ | 'ff'
17
+ | 'lts'
18
+ | 'fs'
19
+ | 'tt';
package/src/vite.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ // Hand-authored type declarations for the `@scalepad/ui/vite` preset.
2
+ // The runtime implementation lives at `./vite.js` (plain JS, not TS — see the
3
+ // note at the top of that file for why this file isn't authored as `.ts`).
4
+
5
+ import type { VanillaExtractPluginOptions } from '@vanilla-extract/vite-plugin';
6
+ import type { Plugin } from 'vite';
7
+
8
+ export interface ScalepadUiViteOptions {
9
+ /**
10
+ * Options forwarded to `@vanilla-extract/vite-plugin`. Leave undefined to
11
+ * use the plugin's defaults, which work for `@scalepad/ui` out of the box.
12
+ */
13
+ vanillaExtract?: VanillaExtractPluginOptions;
14
+ /**
15
+ * Extra packages to add to `optimizeDeps.include`. Use this when your app
16
+ * pulls in additional CommonJS deps through `@scalepad/ui` that Vite's
17
+ * scanner misses because the package is excluded from pre-bundling.
18
+ */
19
+ optimizeDepsInclude?: string[];
20
+ /**
21
+ * Override the default `optimizeDeps.include` list entirely. Most consumers
22
+ * should use `optimizeDepsInclude` to *extend* the defaults rather than
23
+ * replace them.
24
+ */
25
+ optimizeDepsIncludeOverride?: string[];
26
+ }
27
+
28
+ /**
29
+ * The default `optimizeDeps.include` list shipped with the preset. Exported
30
+ * so consumers can compose with it if they need full control over Vite's
31
+ * pre-bundling.
32
+ */
33
+ export const SCALEPAD_UI_OPTIMIZE_DEPS_INCLUDE: readonly string[];
34
+
35
+ export function scalepadUi(options?: ScalepadUiViteOptions): Plugin[];
36
+
37
+ export default scalepadUi;
package/src/vite.js ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Vite preset for `@scalepad/ui`.
3
+ *
4
+ * @scalepad/ui ships as TypeScript + vanilla-extract source. Two things must
5
+ * be wired into a consumer's Vite config for it to load cleanly:
6
+ *
7
+ * 1. The vanilla-extract Vite plugin, so every `.css.ts` file the package
8
+ * ships under `node_modules/@scalepad/ui/src/**` is compiled at build
9
+ * time.
10
+ * 2. A pair of `optimizeDeps` overrides:
11
+ * - `exclude: ['@scalepad/ui']` so Vite's esbuild pre-bundling step
12
+ * doesn't try to ingest `.css.ts` files (esbuild can't handle them
13
+ * on its own and the failure mode is opaque).
14
+ * - `include: [...]` for the CommonJS transitive deps Vite's scanner
15
+ * stops walking through once the package is excluded from
16
+ * pre-bundling.
17
+ *
18
+ * This preset wraps both into a single plugin so consumers can do:
19
+ *
20
+ * import { scalepadUi } from '@scalepad/ui/vite';
21
+ *
22
+ * export default defineConfig({
23
+ * plugins: [react(), scalepadUi()],
24
+ * });
25
+ *
26
+ * The `@vanilla-extract/vite-plugin` ships as a runtime dependency of
27
+ * `@scalepad/ui`, so consumers don't need to install it themselves.
28
+ *
29
+ * NOTE: this file is intentionally authored as plain `.js` with a sibling
30
+ * `.d.ts`, NOT as `.ts`. Vite consumers import it from their `vite.config.ts`,
31
+ * which Vite transpiles via esbuild and then evaluates through Node's runtime.
32
+ * Node 22+ refuses to strip TypeScript types from files inside `node_modules`
33
+ * (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so the runtime artifact
34
+ * MUST be `.js`. Every other file in this package goes through the consumer's
35
+ * bundler instead of Node directly and can stay as `.ts`.
36
+ */
37
+ import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
38
+
39
+ /**
40
+ * The default `optimizeDeps.include` list. These are the CommonJS / mixed
41
+ * deps that `@scalepad/ui` reaches through transitively, which Vite's
42
+ * scanner can't discover on its own once `@scalepad/ui` is excluded from
43
+ * pre-bundling.
44
+ *
45
+ * Exported so consumers can compose with it if they need full control.
46
+ */
47
+ export const SCALEPAD_UI_OPTIMIZE_DEPS_INCLUDE = Object.freeze([
48
+ '@mantine/dates',
49
+ '@mantine/schedule',
50
+ '@tiptap/react',
51
+ 'dayjs',
52
+ 'fast-deep-equal',
53
+ 'prop-types',
54
+ ]);
55
+
56
+ export function scalepadUi(options = {}) {
57
+ const includeFromOptions =
58
+ options.optimizeDepsIncludeOverride ?? [
59
+ ...SCALEPAD_UI_OPTIMIZE_DEPS_INCLUDE,
60
+ ...(options.optimizeDepsInclude ?? []),
61
+ ];
62
+
63
+ const optimizeDepsPlugin = {
64
+ name: '@scalepad/ui/vite-preset',
65
+ config: () => ({
66
+ optimizeDeps: {
67
+ exclude: ['@scalepad/ui'],
68
+ include: includeFromOptions,
69
+ },
70
+ }),
71
+ };
72
+
73
+ const veResult = vanillaExtractPlugin(options.vanillaExtract);
74
+ const vePlugins = Array.isArray(veResult) ? veResult : [veResult];
75
+
76
+ return [...vePlugins, optimizeDepsPlugin];
77
+ }
78
+
79
+ export default scalepadUi;
@@ -1,48 +0,0 @@
1
- // Import Geist font files as URLs
2
- import geistSansItalicFont from './assets/Geist-Italic[wght].ttf?url';
3
- import geistSansFont from './assets/Geist[wght].ttf?url';
4
- import geistMonoItalicFont from './assets/GeistMono-Italic[wght].ttf?url';
5
- import geistMonoFont from './assets/GeistMono[wght].ttf?url';
6
-
7
- // Inject @font-face rules (only once)
8
- if (
9
- typeof document !== 'undefined' &&
10
- !document.getElementById('geist-fonts')
11
- ) {
12
- const style = document.createElement('style');
13
- style.id = 'geist-fonts';
14
- style.textContent = `
15
- @font-face {
16
- font-family: 'Geist Sans';
17
- src: url('${geistSansFont}') format('truetype');
18
- font-weight: 100 900;
19
- font-style: normal;
20
- font-display: swap;
21
- }
22
-
23
- @font-face {
24
- font-family: 'Geist Sans';
25
- src: url('${geistSansItalicFont}') format('truetype');
26
- font-weight: 100 900;
27
- font-style: italic;
28
- font-display: swap;
29
- }
30
-
31
- @font-face {
32
- font-family: 'Geist Mono';
33
- src: url('${geistMonoFont}') format('truetype');
34
- font-weight: 100 900;
35
- font-style: normal;
36
- font-display: swap;
37
- }
38
-
39
- @font-face {
40
- font-family: 'Geist Mono';
41
- src: url('${geistMonoItalicFont}') format('truetype');
42
- font-weight: 100 900;
43
- font-style: italic;
44
- font-display: swap;
45
- }
46
- `;
47
- document.head.appendChild(style);
48
- }