@scalepad/ui 0.1.0 → 0.1.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 +1 -1
- package/package.json +1 -3
- package/src/ThemeProvider.tsx +1 -1
- package/src/components/Anchor/Anchor.css.ts +163 -0
- package/src/components/Anchor/Anchor.figma.tsx +57 -0
- package/src/components/Anchor/Anchor.tsx +114 -13
- package/src/components/Anchor/index.ts +1 -1
- package/src/components/FilterMenu/FilterSubMenuTypes/SearchableFilterSubmenu.tsx +8 -5
- package/src/components/FilterMenu/helpers.ts +5 -2
- package/src/components/Typography/Text.tsx +2 -4
- package/src/components/Typography/Title.tsx +2 -4
- package/src/index.ts +1 -1
- package/src/inter-font.ts +21 -0
- package/src/utils/typography-props.ts +19 -0
- package/src/geist-fonts.ts +0 -48
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ pnpm add @mantine/core@9.0.1 @mantine/dates@9.0.1 @mantine/hooks@9.0.1 \
|
|
|
25
25
|
@tiptap/extension-link@3.22.3 @tiptap/extension-placeholder@3.22.3 \
|
|
26
26
|
@tiptap/extension-underline@3.22.3 @tiptap/pm@3.22.3 @tiptap/react@3.22.3 \
|
|
27
27
|
@tiptap/starter-kit@3.22.3 @tiptap/suggestion@3.22.3 \
|
|
28
|
-
@vanilla-extract/css@^1.16.2 clsx@^2.1.1 dayjs@^1.11.19
|
|
28
|
+
@vanilla-extract/css@^1.16.2 clsx@^2.1.1 dayjs@^1.11.19 \
|
|
29
29
|
lucide-react@^0.469.0 react@^19.0.0 react-dom@^19.0.0 \
|
|
30
30
|
react-intersection-observer@^10.0.0 recharts@^3.6.0
|
|
31
31
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalepad/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "ScalePad LM Design System — React + Mantine 9 + vanilla-extract component library",
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
"@vanilla-extract/css": "^1.16.2",
|
|
59
59
|
"clsx": "^2.1.1",
|
|
60
60
|
"dayjs": "^1.11.19",
|
|
61
|
-
"geist": "^1.5.1",
|
|
62
61
|
"lucide-react": "^0.469.0",
|
|
63
62
|
"react": "^19.0.0",
|
|
64
63
|
"react-dom": "^19.0.0",
|
|
@@ -102,7 +101,6 @@
|
|
|
102
101
|
"@vitest/coverage-v8": "^4.0.17",
|
|
103
102
|
"clsx": "^2.1.1",
|
|
104
103
|
"dayjs": "^1.11.19",
|
|
105
|
-
"geist": "^1.5.1",
|
|
106
104
|
"lucide-react": "^0.469.0",
|
|
107
105
|
"playwright": "^1.57.0",
|
|
108
106
|
"prop-types": "^15.8.1",
|
package/src/ThemeProvider.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import '@mantine/notifications/styles.css';
|
|
|
7
7
|
// oxlint-disable-next-line import/no-unassigned-import
|
|
8
8
|
import '@mantine/schedule/styles.css';
|
|
9
9
|
// oxlint-disable-next-line import/no-unassigned-import
|
|
10
|
-
import './
|
|
10
|
+
import './inter-font';
|
|
11
11
|
|
|
12
12
|
import type { ReactNode } from 'react';
|
|
13
13
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchor component styles – vanilla-extract with semantic design tokens.
|
|
3
|
+
*
|
|
4
|
+
* Anchor is a polymorphic component that renders as `<a>` (default) or
|
|
5
|
+
* `<button>`. It covers two related use cases with the same visual model:
|
|
6
|
+
* inline links inside body copy, and text-only "buttons" (no fill, no
|
|
7
|
+
* border) like the mockup's "Generate" / "Regenerate".
|
|
8
|
+
*
|
|
9
|
+
* Typography is driven entirely by `variant` (BodyVariant) — one class per
|
|
10
|
+
* variant generated from `textStyleVariants`. There is no inline `fz`/`fw`
|
|
11
|
+
* forwarding; the variant class is the only path that sets typography.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { style, styleVariants } from '@vanilla-extract/css';
|
|
15
|
+
|
|
16
|
+
import { textStyleVariants, type BodyVariant } from '../../tokens/text-styles';
|
|
17
|
+
import { mantineVars } from '../../theme/mantineVars';
|
|
18
|
+
import { tokens } from '../../theme/themeContract.css';
|
|
19
|
+
|
|
20
|
+
const focusRing = {
|
|
21
|
+
outline: `2px solid ${tokens.color.stroke.focusStrong}`,
|
|
22
|
+
outlineOffset: 2,
|
|
23
|
+
borderRadius: tokens.radius.sm,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Base styles applied to every Anchor: clear underline at rest (the design
|
|
28
|
+
* system's links underline only on interaction), reset background/border so
|
|
29
|
+
* `component="button"` looks identical to `<a>`, and apply the focus ring
|
|
30
|
+
* + hover underline.
|
|
31
|
+
*/
|
|
32
|
+
export const root = style({
|
|
33
|
+
backgroundColor: 'transparent',
|
|
34
|
+
border: 'none',
|
|
35
|
+
padding: 0,
|
|
36
|
+
margin: 0,
|
|
37
|
+
cursor: 'pointer',
|
|
38
|
+
textDecoration: 'none',
|
|
39
|
+
textDecorationThickness: '1px',
|
|
40
|
+
textUnderlineOffset: '2px',
|
|
41
|
+
transition: 'color 150ms ease, text-decoration-color 150ms ease',
|
|
42
|
+
selectors: {
|
|
43
|
+
'&:hover': {
|
|
44
|
+
textDecoration: 'underline',
|
|
45
|
+
},
|
|
46
|
+
'&:focus-visible': focusRing,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* One class per BodyVariant — typography is generated from the same
|
|
52
|
+
* `textStyleVariants` source that `Text` and `Title` use, so they cannot
|
|
53
|
+
* drift.
|
|
54
|
+
*/
|
|
55
|
+
export const variant = styleVariants(textStyleVariants, styles => ({
|
|
56
|
+
fontFamily: styles.fontFamily,
|
|
57
|
+
fontWeight: styles.fontWeight,
|
|
58
|
+
fontSize: styles.fontSize,
|
|
59
|
+
lineHeight: styles.lineHeight,
|
|
60
|
+
letterSpacing: styles.letterSpacing,
|
|
61
|
+
textTransform: 'textTransform' in styles ? styles.textTransform : undefined,
|
|
62
|
+
})) satisfies Record<BodyVariant, string>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One class per AnchorTone. Color at rest + hover. The `:hover` underline
|
|
66
|
+
* comes from `root`; tone classes only set `color`. Dark-mode overrides
|
|
67
|
+
* pin to the same semantic tokens (which already swap per theme via the
|
|
68
|
+
* theme contract).
|
|
69
|
+
*/
|
|
70
|
+
export const tone = styleVariants({
|
|
71
|
+
default: {
|
|
72
|
+
color: tokens.color.text.default,
|
|
73
|
+
selectors: {
|
|
74
|
+
'&:hover': { color: tokens.color.text.primaryDefault },
|
|
75
|
+
'&:focus-visible': { color: tokens.color.text.primaryDefault },
|
|
76
|
+
[`${mantineVars.darkSelector} &`]: { color: tokens.color.text.default },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
primary: {
|
|
80
|
+
color: tokens.color.text.primaryDefault,
|
|
81
|
+
selectors: {
|
|
82
|
+
'&:hover': { color: tokens.color.text.primaryLight },
|
|
83
|
+
'&:focus-visible': { color: tokens.color.text.primaryLight },
|
|
84
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
85
|
+
color: tokens.color.text.primaryDefault,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
danger: {
|
|
90
|
+
color: tokens.color.text.dangerDefault,
|
|
91
|
+
selectors: {
|
|
92
|
+
'&:hover': { color: tokens.color.text.dangerStrong },
|
|
93
|
+
'&:focus-visible': { color: tokens.color.text.dangerStrong },
|
|
94
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
95
|
+
color: tokens.color.text.dangerDefault,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
subdued: {
|
|
100
|
+
color: tokens.color.text.subduedStrong,
|
|
101
|
+
selectors: {
|
|
102
|
+
'&:hover': { color: tokens.color.text.default },
|
|
103
|
+
'&:focus-visible': { color: tokens.color.text.default },
|
|
104
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
105
|
+
color: tokens.color.text.subduedStrong,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Layout for the case where `leftSection` and/or `rightSection` is set.
|
|
113
|
+
* Without icons we leave the root as plain inline text so it flows
|
|
114
|
+
* naturally inside a paragraph.
|
|
115
|
+
*/
|
|
116
|
+
export const withIcons = style({
|
|
117
|
+
display: 'inline-flex',
|
|
118
|
+
alignItems: 'center',
|
|
119
|
+
verticalAlign: 'baseline',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/** Gap between icon section(s) and label, sized to match Figma `Anchor`. */
|
|
123
|
+
export const gap = styleVariants({
|
|
124
|
+
xs: { gap: 4 },
|
|
125
|
+
sm: { gap: 4 },
|
|
126
|
+
md: { gap: 6 },
|
|
127
|
+
lg: { gap: 6 },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Wrapper around left / right icon nodes. `display: inline-flex` keeps the
|
|
132
|
+
* icon visually centered with the label without forcing the caller to wrap
|
|
133
|
+
* their lucide icon themselves.
|
|
134
|
+
*/
|
|
135
|
+
export const iconSection = style({
|
|
136
|
+
display: 'inline-flex',
|
|
137
|
+
alignItems: 'center',
|
|
138
|
+
flexShrink: 0,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Disabled state — used by both `<a aria-disabled>` and `<button disabled>`.
|
|
143
|
+
* `pointer-events: none` neutralises hover/click without removing focus
|
|
144
|
+
* ability when needed (e.g. screenreader can still navigate to the
|
|
145
|
+
* `aria-disabled` link).
|
|
146
|
+
*/
|
|
147
|
+
export const disabled = style({
|
|
148
|
+
color: tokens.color.text.disabledDefault,
|
|
149
|
+
cursor: 'not-allowed',
|
|
150
|
+
pointerEvents: 'none',
|
|
151
|
+
selectors: {
|
|
152
|
+
'&:hover': {
|
|
153
|
+
textDecoration: 'none',
|
|
154
|
+
color: tokens.color.text.disabledDefault,
|
|
155
|
+
},
|
|
156
|
+
'&:focus-visible': {
|
|
157
|
+
color: tokens.color.text.disabledDefault,
|
|
158
|
+
},
|
|
159
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
160
|
+
color: tokens.color.text.disabledDefault,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import figma from '@figma/code-connect';
|
|
2
|
+
|
|
3
|
+
import { Anchor } from './Anchor';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Code Connect mapping for the LM Design System `Anchor` component_set
|
|
7
|
+
* (formerly `Link Button`, renamed to keep design + code names aligned).
|
|
8
|
+
*
|
|
9
|
+
* The component_set lives on page `Anchor` (node `842:44446`) in
|
|
10
|
+
* `LM Design System` (`VCLfybgU3OaUUPrQdBaVmP`). Variants exposed:
|
|
11
|
+
*
|
|
12
|
+
* - `Variant`: Default | Primary | Danger | Subdued — color tone.
|
|
13
|
+
* - `Size`: Mini | Small | Default | Large — icon-gap scale.
|
|
14
|
+
* - `Roundness`, `State` (Default | Hover & Active | Focus | Disabled):
|
|
15
|
+
* not surfaced as code props. Hover / focus / disabled are interaction
|
|
16
|
+
* state; `Roundness` only changes the focus-ring shape.
|
|
17
|
+
* - Boolean-gated icon swaps `Show left icon` + `⮑ Left icon` and
|
|
18
|
+
* `Show right icon` + `⮑ Right icon` map to `leftSection` / `rightSection`.
|
|
19
|
+
*/
|
|
20
|
+
figma.connect(
|
|
21
|
+
Anchor,
|
|
22
|
+
'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=842-44446',
|
|
23
|
+
{
|
|
24
|
+
props: {
|
|
25
|
+
tone: figma.enum('Variant', {
|
|
26
|
+
Default: 'default',
|
|
27
|
+
Primary: 'primary',
|
|
28
|
+
Danger: 'danger',
|
|
29
|
+
Subdued: 'subdued',
|
|
30
|
+
}),
|
|
31
|
+
size: figma.enum('Size', {
|
|
32
|
+
Default: 'md',
|
|
33
|
+
Large: 'lg',
|
|
34
|
+
Small: 'sm',
|
|
35
|
+
Mini: 'xs',
|
|
36
|
+
}),
|
|
37
|
+
leftSection: figma.boolean('Show left icon', {
|
|
38
|
+
true: figma.instance('⮑ Left icon'),
|
|
39
|
+
false: undefined,
|
|
40
|
+
}),
|
|
41
|
+
rightSection: figma.boolean('Show right icon', {
|
|
42
|
+
true: figma.instance('⮑ Right icon'),
|
|
43
|
+
false: undefined,
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
example: props => (
|
|
47
|
+
<Anchor
|
|
48
|
+
tone={props.tone}
|
|
49
|
+
size={props.size}
|
|
50
|
+
leftSection={props.leftSection}
|
|
51
|
+
rightSection={props.rightSection}
|
|
52
|
+
>
|
|
53
|
+
Label
|
|
54
|
+
</Anchor>
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
);
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design System Anchor Component
|
|
3
|
+
*
|
|
4
|
+
* Polymorphic — renders as `<a>` by default and `component="button"` for
|
|
5
|
+
* action triggers (e.g. inline "Generate" / "Regenerate" actions). Covers
|
|
6
|
+
* both inline links and text-only "buttons" with one component.
|
|
7
|
+
*
|
|
8
|
+
* Typography is locked to the design-system body variants (no `fz`/`fw`/
|
|
9
|
+
* `lh`/etc. forwarded). Color is controlled via `tone`; use `c` only for
|
|
10
|
+
* one-off escapes (e.g. `c="inherit"` to flow color from parent).
|
|
11
|
+
*
|
|
12
|
+
* @example Inline link
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Anchor href="/clients/123">Open client</Anchor>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example Text-only button (mockup pattern)
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <Anchor
|
|
20
|
+
* component="button"
|
|
21
|
+
* type="button"
|
|
22
|
+
* tone="primary"
|
|
23
|
+
* leftSection={<RefreshCcw size={14} />}
|
|
24
|
+
* onClick={onRegenerate}
|
|
25
|
+
* >
|
|
26
|
+
* Regenerate link
|
|
27
|
+
* </Anchor>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
|
|
1
31
|
import { forwardRef, type ReactNode } from 'react';
|
|
2
32
|
|
|
3
33
|
import {
|
|
@@ -5,39 +35,110 @@ import {
|
|
|
5
35
|
createPolymorphicComponent,
|
|
6
36
|
type AnchorProps as MantineAnchorProps,
|
|
7
37
|
} from '@mantine/core';
|
|
38
|
+
import { clsx } from 'clsx';
|
|
8
39
|
|
|
9
|
-
import { textStyleVariants, type BodyVariant } from '../../tokens/text-styles';
|
|
10
40
|
import { resolveColorToken } from '../../utils/color-props';
|
|
41
|
+
import type { TypographyStyleProp } from '../../utils/typography-props';
|
|
42
|
+
|
|
43
|
+
import * as classes from './Anchor.css';
|
|
44
|
+
|
|
45
|
+
import type { BodyVariant, TextColor } from '../../tokens';
|
|
11
46
|
|
|
12
|
-
|
|
47
|
+
export type AnchorTone = 'default' | 'primary' | 'danger' | 'subdued';
|
|
13
48
|
|
|
14
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Box size — controls the icon-to-label gap. Typography is independent and
|
|
51
|
+
* comes from `variant`. Maps to Figma `Anchor`'s `Size` property
|
|
52
|
+
* (Mini/Small/Default/Large).
|
|
53
|
+
*/
|
|
54
|
+
export type AnchorSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
15
55
|
|
|
16
56
|
export type AnchorProps = Omit<
|
|
17
57
|
MantineAnchorProps,
|
|
18
|
-
|
|
58
|
+
TypographyStyleProp | 'c' | 'children' | 'variant' | 'color'
|
|
19
59
|
> & {
|
|
60
|
+
/** Typography variant from the design system. Default `body1`. */
|
|
20
61
|
variant?: BodyVariant;
|
|
62
|
+
/**
|
|
63
|
+
* Color tone. Maps to the Figma `Anchor` component's `Variant` property.
|
|
64
|
+
* Default `primary` (matches Mantine's link-green default and existing
|
|
65
|
+
* `<Anchor component="button">` usage).
|
|
66
|
+
*/
|
|
67
|
+
tone?: AnchorTone;
|
|
68
|
+
/** Box size — controls icon gap. Default `md`. */
|
|
69
|
+
size?: AnchorSize;
|
|
70
|
+
/** Optional icon node rendered before the label. */
|
|
71
|
+
leftSection?: ReactNode;
|
|
72
|
+
/** Optional icon node rendered after the label. */
|
|
73
|
+
rightSection?: ReactNode;
|
|
74
|
+
/**
|
|
75
|
+
* Disabled state. For `<a>` renders `aria-disabled` and removes hover;
|
|
76
|
+
* for `<button>` adds the native `disabled` attribute.
|
|
77
|
+
*/
|
|
78
|
+
disabled?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Color override. Use sparingly — `tone` should cover semantic cases.
|
|
81
|
+
* `inherit` flows the color from the parent (e.g. inside a tinted `<Text>`).
|
|
82
|
+
*/
|
|
21
83
|
c?: TextColor | 'inherit';
|
|
22
84
|
children?: ReactNode;
|
|
23
85
|
};
|
|
24
86
|
|
|
25
87
|
const AnchorBase = forwardRef<HTMLAnchorElement, AnchorProps>(
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
|
|
88
|
+
(
|
|
89
|
+
{
|
|
90
|
+
variant = 'body1',
|
|
91
|
+
tone = 'primary',
|
|
92
|
+
size = 'md',
|
|
93
|
+
leftSection,
|
|
94
|
+
rightSection,
|
|
95
|
+
disabled,
|
|
96
|
+
className,
|
|
97
|
+
children,
|
|
98
|
+
c,
|
|
99
|
+
...rest
|
|
100
|
+
},
|
|
101
|
+
ref,
|
|
102
|
+
) => {
|
|
103
|
+
const hasIcons = leftSection != null || rightSection != null;
|
|
104
|
+
// Mantine forwards arbitrary `component` via polymorphic typing.
|
|
105
|
+
// Narrow at runtime so we can apply the right disabled semantics
|
|
106
|
+
// (native `disabled` for `<button>`, `aria-disabled` for `<a>`).
|
|
107
|
+
const isButton = (rest as { component?: unknown }).component === 'button';
|
|
108
|
+
|
|
109
|
+
const cls = clsx(
|
|
110
|
+
classes.root,
|
|
111
|
+
classes.variant[variant],
|
|
112
|
+
classes.tone[tone],
|
|
113
|
+
hasIcons && classes.withIcons,
|
|
114
|
+
hasIcons && classes.gap[size],
|
|
115
|
+
disabled && classes.disabled,
|
|
116
|
+
className,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const resolvedC = c !== undefined ? resolveColorToken(c) : undefined;
|
|
29
120
|
|
|
30
121
|
return (
|
|
31
122
|
<MantineAnchor
|
|
32
123
|
ref={ref}
|
|
33
|
-
|
|
34
|
-
fz={variantStyles.fontSize}
|
|
35
|
-
lh={variantStyles.lineHeight}
|
|
36
|
-
lts={variantStyles.letterSpacing}
|
|
37
|
-
ff={variantStyles.fontFamily}
|
|
124
|
+
className={cls}
|
|
38
125
|
c={resolvedC}
|
|
126
|
+
aria-disabled={!isButton && disabled ? true : undefined}
|
|
127
|
+
{...(isButton && disabled ? { disabled: true } : {})}
|
|
39
128
|
{...rest}
|
|
40
|
-
|
|
129
|
+
>
|
|
130
|
+
{leftSection != null && (
|
|
131
|
+
<span className={classes.iconSection} aria-hidden>
|
|
132
|
+
{leftSection}
|
|
133
|
+
</span>
|
|
134
|
+
)}
|
|
135
|
+
{children}
|
|
136
|
+
{rightSection != null && (
|
|
137
|
+
<span className={classes.iconSection} aria-hidden>
|
|
138
|
+
{rightSection}
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
</MantineAnchor>
|
|
41
142
|
);
|
|
42
143
|
},
|
|
43
144
|
);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Anchor } from './Anchor';
|
|
2
|
-
export type { AnchorProps } from './Anchor';
|
|
2
|
+
export type { AnchorProps, AnchorSize, AnchorTone } from './Anchor';
|
|
@@ -193,12 +193,16 @@ export function SearchableFilterSubmenu({
|
|
|
193
193
|
) : displayedItems.length > 0 ? (
|
|
194
194
|
<>
|
|
195
195
|
{displayedItems.map(item => (
|
|
196
|
+
// Toggling flows through the Checkbox's native onChange (input
|
|
197
|
+
// click + label click both delegate there). We deliberately
|
|
198
|
+
// don't put `onClick` on Menu.Item: Mantine's Checkbox renders
|
|
199
|
+
// its label as a `<label for=input>` whose click event keeps
|
|
200
|
+
// bubbling after delegating to the input, so a Menu.Item.onClick
|
|
201
|
+
// would catch the bubbled event and fire a second toggle that
|
|
202
|
+
// net-cancels the first. `onKeyDown` for space stays so the
|
|
203
|
+
// row can still be toggled when focused via keyboard.
|
|
196
204
|
<Menu.Item
|
|
197
205
|
key={item.id}
|
|
198
|
-
onClick={e => {
|
|
199
|
-
e.stopPropagation();
|
|
200
|
-
toggleItem(item.id);
|
|
201
|
-
}}
|
|
202
206
|
closeMenuOnClick={false}
|
|
203
207
|
onKeyDown={e => {
|
|
204
208
|
if (e.key === ' ') {
|
|
@@ -212,7 +216,6 @@ export function SearchableFilterSubmenu({
|
|
|
212
216
|
<Checkbox
|
|
213
217
|
checked={selectedIds.has(item.id)}
|
|
214
218
|
onChange={() => toggleItem(item.id)}
|
|
215
|
-
onClick={e => e.stopPropagation()}
|
|
216
219
|
size="sm"
|
|
217
220
|
label={item.name}
|
|
218
221
|
/>
|
|
@@ -14,8 +14,11 @@ export function extractFilterValue(
|
|
|
14
14
|
);
|
|
15
15
|
|
|
16
16
|
if (schema.type === 'boolean') {
|
|
17
|
-
// Boolean filters
|
|
18
|
-
|
|
17
|
+
// Boolean filters are binary — they have no concept of items. The
|
|
18
|
+
// canonical writer (`createFilterCategory`) emits a stub `{id, name:'True'}`
|
|
19
|
+
// entry, but external consumers also produce the category with `items: []`,
|
|
20
|
+
// so we treat presence of the category as the on-state.
|
|
21
|
+
return Boolean(category);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
if (schema.type === 'multi-select') {
|
|
@@ -31,15 +31,13 @@ import {
|
|
|
31
31
|
type TextStyleDefinition,
|
|
32
32
|
} from '../../tokens';
|
|
33
33
|
import { resolveColorToken } from '../../utils/color-props';
|
|
34
|
+
import type { TypographyStyleProp } from '../../utils/typography-props';
|
|
34
35
|
|
|
35
36
|
import type { TextColor } from '../../tokens';
|
|
36
37
|
|
|
37
|
-
/** Typography styling props that are controlled by the variant and should not be set directly */
|
|
38
|
-
type TypographyStyleProps = 'fw' | 'fz' | 'size' | 'lh' | 'ff' | 'lts' | 'fs';
|
|
39
|
-
|
|
40
38
|
export type TextProps = Omit<
|
|
41
39
|
MantineTextProps,
|
|
42
|
-
|
|
40
|
+
TypographyStyleProp | 'c' | 'children'
|
|
43
41
|
> & {
|
|
44
42
|
/** Text style variant from the design system. Defaults to 'body1'. */
|
|
45
43
|
variant?: BodyVariant;
|
|
@@ -24,12 +24,10 @@ import {
|
|
|
24
24
|
type HeadingVariant,
|
|
25
25
|
} from '../../tokens/text-styles';
|
|
26
26
|
import { resolveColorToken } from '../../utils/color-props';
|
|
27
|
+
import type { TypographyStyleProp } from '../../utils/typography-props';
|
|
27
28
|
|
|
28
29
|
import type { TextColor } from '../../tokens/color-types';
|
|
29
30
|
|
|
30
|
-
/** Typography styling props that are controlled by the variant and should not be set directly */
|
|
31
|
-
type TypographyStyleProps = 'fw' | 'fz' | 'size' | 'lh' | 'ff' | 'lts' | 'fs';
|
|
32
|
-
|
|
33
31
|
const variantToOrder: Record<HeadingVariant, TitleOrder> = {
|
|
34
32
|
heading1: 1,
|
|
35
33
|
heading2: 2,
|
|
@@ -40,7 +38,7 @@ const variantToOrder: Record<HeadingVariant, TitleOrder> = {
|
|
|
40
38
|
|
|
41
39
|
export type TitleProps = Omit<
|
|
42
40
|
MantineTitleProps,
|
|
43
|
-
|
|
41
|
+
TypographyStyleProp | 'c' | 'children' | 'order'
|
|
44
42
|
> & {
|
|
45
43
|
/** Heading style variant from the design system. */
|
|
46
44
|
variant?: HeadingVariant;
|
package/src/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type {
|
|
|
30
30
|
FilterCategory,
|
|
31
31
|
} from './components/AppliedFiltersManagerBar';
|
|
32
32
|
export { Anchor } from './components/Anchor';
|
|
33
|
-
export type { AnchorProps } from './components/Anchor';
|
|
33
|
+
export type { AnchorProps, AnchorSize, AnchorTone } from './components/Anchor';
|
|
34
34
|
export { Badge } from './components/Badge';
|
|
35
35
|
export type { BadgeProps } from './components/Badge';
|
|
36
36
|
export { BulkActionBar } from './components/BulkActionBar';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Inject the Inter font from Google Fonts as a `<link rel="stylesheet">`.
|
|
2
|
+
//
|
|
3
|
+
// Loading the stylesheet via JS instead of `import './foo.css'` keeps the
|
|
4
|
+
// design system robust against consumer bundlers that don't resolve `.css`
|
|
5
|
+
// files inside `node_modules/@scalepad/ui`. Idempotent — safe to import
|
|
6
|
+
// from multiple entry points.
|
|
7
|
+
|
|
8
|
+
const INTER_FONT_LINK_ID = 'scalepad-ui-inter-font';
|
|
9
|
+
const INTER_FONT_HREF =
|
|
10
|
+
'https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap';
|
|
11
|
+
|
|
12
|
+
if (
|
|
13
|
+
typeof document !== 'undefined' &&
|
|
14
|
+
!document.getElementById(INTER_FONT_LINK_ID)
|
|
15
|
+
) {
|
|
16
|
+
const link = document.createElement('link');
|
|
17
|
+
link.id = INTER_FONT_LINK_ID;
|
|
18
|
+
link.rel = 'stylesheet';
|
|
19
|
+
link.href = INTER_FONT_HREF;
|
|
20
|
+
document.head.appendChild(link);
|
|
21
|
+
}
|
|
@@ -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/geist-fonts.ts
DELETED
|
@@ -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
|
-
}
|