@recursica/mantine-adapter 0.50.1 → 0.50.2

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/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/borderux/recursica.git",
14
14
  "directory": "packages/mantine-adapter"
15
15
  },
16
- "version": "0.50.1",
16
+ "version": "0.50.2",
17
17
  "type": "module",
18
18
  "main": "./dist/mantine-adapter.cjs",
19
19
  "module": "./dist/mantine-adapter.js",
@@ -13,3 +13,15 @@ Mantine natively exposes an abstract `size` prop (`"sm" | "md" | "lg" | "xl"`) t
13
13
  ### 2. Scroll Dividers behavior
14
14
 
15
15
  Mantine internally handles scroll state natively, dynamically showing/hiding a divider line when content overflows in `.body`. This logic is tightly coupled to React DOM measurements internally. Our component inherits this dynamic behavior rather than statically rendering a permanent divider, matching Mantine's robust overflow UX. However, we aggressively override the generated `border-bottom` via CSS modules to ensure that when it _does_ appear, it correctly utilizes the `--recursica_ui-kit_components_modal_colors_scroll-divider` variable and `--recursica_ui-kit_components_modal_properties_scroll-divider-thickness` token.
16
+
17
+ ### 3. Title truncation
18
+
19
+ `.title` truncates with an ellipsis (`overflow: hidden`, `white-space: nowrap`, `text-overflow: ellipsis`) rather than wrapping. It also needs `flex: 1 1 auto; min-width: 0;` since it's a flex child of `.header` alongside the close button — without `min-width: 0`, a flex item won't shrink below its content's intrinsic width, so ellipsis never engages. `.header`'s `display: flex` is likewise explicit rather than relied upon from Mantine's own header class, so the mui-adapter's plain-`<div>` header gets identical layout.
20
+
21
+ ### 4. Width was pinned to Mantine's `md` size, not content-driven
22
+
23
+ Despite §1 above, `.content` never actually scaled fluidly: Mantine's own CSS sets `flex: 0 0 var(--modal-size)` (defaulting to 440px) on the Content element, and our module only added `min-width`/`max-width` without touching `flex`. A fixed flex-basis with `flex-shrink: 0` pins the box at exactly 440px regardless of those bounds, so they were unreachable dead code — e.g. "Authentication Required" (the `Default` story's title) didn't fit at 440px and silently wrapped to two lines. Overriding to `flex: 0 1 auto` makes the width shrink-to-fit the content within `min-width`/`max-width`, which is what makes title truncation (§3) only kick in once a title would exceed `max-width` rather than truncating titles that would otherwise fit. `.content[data-full-screen]` restores Mantine's own `flex: 0 0 100%` since the shrink-to-fit override would otherwise stop `fullScreen` from filling the viewport.
24
+
25
+ ### 5. Close button restyled to match Button
26
+
27
+ `.close` reuses Button's text-variant/icon-only/small tokens (radius, padding, background/icon color, hover overlay, focus ring) instead of Mantine's native CloseButton look. Two overrides need `!important`/an explicit reset to win: Mantine's `CloseIcon` sizes itself via an inline `style` (`--cb-icon-size`, default `70%`), which beats any plain class rule — same category as Tree's `--level-offset` override; and Mantine's own subtle-variant `:hover` background (from `CloseButton.css`) is pinned back to the Button background token so only our `::after` overlay renders hover feedback.
@@ -1,3 +1,14 @@
1
+ /* Brand-layer exemptions (recursica-allow-brand) — see recursica-token-analyzer README.md.
2
+ * Close button is styled to match the Button component's text/icon-only variant, so it reuses
3
+ * the same global hover/focus state tokens Button.module.css exempts.
4
+ * recursica-allow-brand: --recursica_brand_states_focus_blur
5
+ * recursica-allow-brand: --recursica_brand_states_focus_border-size
6
+ * recursica-allow-brand: --recursica_brand_states_focus_color
7
+ * recursica-allow-brand: --recursica_brand_states_focus_margin
8
+ * recursica-allow-brand: --recursica_brand_states_hover_color
9
+ * recursica-allow-brand: --recursica_brand_states_hover_opacity
10
+ */
11
+
1
12
  .root {
2
13
  }
3
14
 
@@ -6,6 +17,12 @@
6
17
  }
7
18
 
8
19
  .content {
20
+ /* HARDCODE: Mantine pins content to a fixed flex-basis from its own size scale
21
+ (`flex: 0 0 var(--modal-size)`, defaulting to 440px). Overriding to `0 1 auto` makes width
22
+ content-driven (shrink-to-fit) instead of fixed, so min-width/max-width below actually bound
23
+ it rather than being unreachable dead code. */
24
+ flex: 0 1 auto;
25
+
9
26
  /* Geometric Bounds */
10
27
  min-width: var(--recursica_ui-kit_components_modal_properties_min-width);
11
28
  max-width: var(--recursica_ui-kit_components_modal_properties_max-width);
@@ -39,7 +56,16 @@
39
56
  flex-direction: column;
40
57
  }
41
58
 
59
+ .content[data-full-screen] {
60
+ /* HARDCODE: preserve Mantine's native full-screen sizing — the shrink-to-fit override above
61
+ would otherwise apply here too and stop `fullScreen` from filling the viewport. */
62
+ flex: 0 0 100%;
63
+ }
64
+
42
65
  .header {
66
+ display: flex; /* HARDCODE: puts the title and close button side-by-side so the title has a bounded width to truncate against */
67
+ align-items: center;
68
+ justify-content: space-between;
43
69
  padding: var(
44
70
  --recursica_ui-kit_components_modal_properties_header-footer-vertical-padding
45
71
  )
@@ -50,6 +76,12 @@
50
76
  }
51
77
 
52
78
  .title {
79
+ flex: 1 1 auto; /* HARDCODE: let the title claim the space between the header edge and the close button */
80
+ min-width: 0; /* HARDCODE: required for text-overflow ellipsis to take effect on a flex child */
81
+ overflow: hidden;
82
+ white-space: nowrap;
83
+ text-overflow: ellipsis;
84
+
53
85
  color: var(--recursica_ui-kit_components_modal_properties_colors_title);
54
86
 
55
87
  /* Direct Figma Typography Mapping */
@@ -173,5 +205,91 @@
173
205
  }
174
206
 
175
207
  .close {
176
- /* Inherits standard icon button tokens natively, but we can override if required */
208
+ /* Matches the Button component's text-variant, icon-only, small-size visual treatment
209
+ (see Button.module.css) so the modal close control looks like a Recursica Button
210
+ rather than Mantine's native CloseButton. */
211
+ box-sizing: border-box;
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ position: relative;
216
+ overflow: hidden;
217
+ transition: all 0.2s ease;
218
+
219
+ height: var(
220
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_height
221
+ );
222
+ min-width: var(
223
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_min-width
224
+ );
225
+ padding: 0
226
+ var(
227
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_horizontal-padding
228
+ );
229
+ border-radius: var(
230
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_border-radius
231
+ );
232
+
233
+ border-style: solid;
234
+ border-width: var(
235
+ --recursica_ui-kit_components_button_variants_styles_text_properties_border-size
236
+ );
237
+ border-color: var(
238
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_border-color
239
+ );
240
+ background-color: var(
241
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_background-color
242
+ );
243
+ color: var(
244
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_icon-color
245
+ );
246
+ }
247
+
248
+ /* Mantine's CloseIcon sets width/height via an inline `style` attribute
249
+ (`--cb-icon-size`, default 70%), which beats any plain class rule regardless of specificity —
250
+ same category of override as Tree's `--level-offset` (see Tree/IMPLEMENTATION_NOTES.md).
251
+ `!important` is required here to land the Button's small icon-size token instead. */
252
+ .close svg {
253
+ position: relative;
254
+ z-index: 1;
255
+ width: var(
256
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
257
+ ) !important;
258
+ height: var(
259
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
260
+ ) !important;
261
+ }
262
+
263
+ .close::after {
264
+ content: "";
265
+ position: absolute;
266
+ inset: 0;
267
+ border-radius: inherit;
268
+ z-index: 0;
269
+ pointer-events: none;
270
+ transition: opacity 150ms ease;
271
+ opacity: 0;
272
+ background-color: var(--recursica_brand_states_hover_color);
273
+ }
274
+ .close:hover:not(:disabled)::after {
275
+ opacity: var(--recursica_brand_states_hover_opacity);
276
+ }
277
+
278
+ /* Mantine's own subtle-variant hover background (CloseButton.css) would otherwise show through
279
+ underneath the overlay above — pin it back to the Button's own background token so only the
280
+ `::after` overlay renders the hover feedback, matching Button's single-overlay treatment. */
281
+ .close:hover {
282
+ background-color: var(
283
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_background-color
284
+ );
285
+ }
286
+
287
+ .close:focus-visible {
288
+ outline: none;
289
+ box-shadow:
290
+ 0 0 0 var(--recursica_brand_states_focus_border-size)
291
+ var(--recursica_brand_states_focus_color),
292
+ 0 0 var(--recursica_brand_states_focus_blur)
293
+ var(--recursica_brand_states_focus_margin)
294
+ var(--recursica_brand_states_focus_color);
177
295
  }
@@ -19,7 +19,8 @@ export default meta;
19
19
  type Story = StoryObj<typeof Modal>;
20
20
 
21
21
  const DefaultWrapper = (args: ModalProps) => {
22
- const [opened, setOpened] = useState(false);
22
+ // Starts opened so the modal is visible without pressing a button first.
23
+ const [opened, setOpened] = useState(true);
23
24
  return (
24
25
  <>
25
26
  <Modal {...args} opened={opened} onClose={() => setOpened(false)}>
@@ -43,8 +44,33 @@ export const Default: Story = {
43
44
  render: (args) => <DefaultWrapper {...args} />,
44
45
  };
45
46
 
47
+ const LongTitleWrapper = (args: ModalProps) => {
48
+ const [opened, setOpened] = useState(true);
49
+ return (
50
+ <>
51
+ <Modal {...args} opened={opened} onClose={() => setOpened(false)}>
52
+ The title above is longer than the header can display, so it truncates
53
+ with an ellipsis instead of wrapping onto a second line.
54
+ <Modal.Footer>
55
+ <Button onClick={() => setOpened(false)}>Got it</Button>
56
+ </Modal.Footer>
57
+ </Modal>
58
+ <Button onClick={() => setOpened(true)}>Open Modal</Button>
59
+ </>
60
+ );
61
+ };
62
+
63
+ export const LongTitle: Story = {
64
+ args: {
65
+ title:
66
+ "This Modal Title Is Deliberately Long Enough To Exceed The Available Header Width",
67
+ },
68
+ render: (args) => <LongTitleWrapper {...args} />,
69
+ };
70
+
46
71
  const ScrollingWrapper = (args: ModalProps) => {
47
- const [opened, setOpened] = useState(false);
72
+ // Starts opened so the modal is visible without pressing a button first.
73
+ const [opened, setOpened] = useState(true);
48
74
  return (
49
75
  <>
50
76
  <Modal {...args} opened={opened} onClose={() => setOpened(false)}>