@recursica/mantine-adapter 0.20.0 → 0.21.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,10 +1,20 @@
1
1
  import { default as React } from 'react';
2
+ import { RecursicaLoaderProps } from '../Loader/Loader';
2
3
  import { ButtonProps as MantineButtonProps } from '@mantine/core';
3
4
  import { RecursicaOverStyled } from '../../utils/filterStylingProps';
4
5
  export interface RecursicaButtonProps {
6
+ /** The visual style variant of the button */
5
7
  variant?: "solid" | "outline" | "text";
8
+ /** The size of the button */
6
9
  size?: "default" | "small";
10
+ /** An optional icon element to display to the left of the button text. Replaces Mantine's leftSection. */
7
11
  icon?: React.ReactNode;
12
+ /** Which Recursica Loader variant to use */
13
+ loaderVariant?: RecursicaLoaderProps["variant"];
14
+ /** The size variant for the loader */
15
+ loaderSize?: RecursicaLoaderProps["size"];
16
+ /** Whether to use the Recursica loader or fallback to the Mantine loader */
17
+ useRecursicaLoader?: boolean;
8
18
  }
9
19
  export type ButtonProps = RecursicaOverStyled<Omit<MantineButtonProps, "variant" | "size" | "leftSection" | "fullWidth"> & RecursicaButtonProps>;
10
20
  /**
@@ -25,7 +35,7 @@ export type ButtonProps = RecursicaOverStyled<Omit<MantineButtonProps, "variant"
25
35
  */
26
36
  export declare const Button: (<C = "button">(props: import('@mantine/core').PolymorphicComponentProps<C, ButtonProps>) => React.ReactElement) & Omit<React.FunctionComponent<((ButtonProps & {
27
37
  component?: any;
28
- }) & Omit<Omit<any, "ref">, "className" | "classNames" | "style" | "styles" | "vars" | "p" | "px" | "py" | "pt" | "pb" | "pl" | "pr" | "bg" | "c" | "opacity" | "ff" | "fz" | "fw" | "lts" | "ta" | "lh" | "fs" | "tt" | "td" | "bd" | "w" | "miw" | "maw" | "h" | "mih" | "mah" | "m" | "my" | "mx" | "mt" | "mb" | "ml" | "mr" | "top" | "left" | "bottom" | "right" | "color" | "children" | "radius" | "__vars" | "__size" | "hiddenFrom" | "visibleFrom" | "lightHidden" | "darkHidden" | "mod" | "ms" | "me" | "ps" | "pe" | "bdrs" | "bgsz" | "bgp" | "bgr" | "bga" | "pos" | "inset" | "display" | "flex" | "unstyled" | "variant" | "attributes" | "overStyled" | "disabled" | "icon" | "justify" | "size" | "gradient" | "autoContrast" | "component" | "loading" | "rightSection" | "data-disabled" | "loaderProps"> & {
38
+ }) & Omit<Omit<any, "ref">, "className" | "classNames" | "style" | "styles" | "vars" | "p" | "px" | "py" | "pt" | "pb" | "pl" | "pr" | "bg" | "c" | "opacity" | "ff" | "fz" | "fw" | "lts" | "ta" | "lh" | "fs" | "tt" | "td" | "bd" | "w" | "miw" | "maw" | "h" | "mih" | "mah" | "m" | "my" | "mx" | "mt" | "mb" | "ml" | "mr" | "top" | "left" | "bottom" | "right" | "color" | "children" | "radius" | "__vars" | "__size" | "hiddenFrom" | "visibleFrom" | "lightHidden" | "darkHidden" | "mod" | "ms" | "me" | "ps" | "pe" | "bdrs" | "bgsz" | "bgp" | "bgr" | "bga" | "pos" | "inset" | "display" | "flex" | "unstyled" | "variant" | "attributes" | "overStyled" | "disabled" | "icon" | "justify" | "size" | "gradient" | "autoContrast" | "component" | "loading" | "rightSection" | "data-disabled" | "loaderProps" | "loaderVariant" | "loaderSize" | "useRecursicaLoader"> & {
29
39
  ref?: any;
30
40
  renderRoot?: (props: any) => any;
31
41
  }) | (ButtonProps & {
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.20.0",
16
+ "version": "0.21.0",
17
17
  "type": "module",
18
18
  "main": "./dist/mantine-adapter.cjs",
19
19
  "module": "./dist/mantine-adapter.js",
@@ -36,12 +36,16 @@
36
36
  }
37
37
 
38
38
  /* Internal Mantine reset & layout logic for Truncation */
39
- .root > * {
39
+ .root > *:not(.loader) {
40
40
  min-width: 0;
41
41
  position: relative;
42
42
  z-index: 1;
43
43
  }
44
44
 
45
+ .loader {
46
+ /* Mantine absolutely positions this; we just need a hook to exclude it from the child reset */
47
+ }
48
+
45
49
  .label {
46
50
  display: flex;
47
51
  align-items: center;
@@ -20,6 +20,30 @@ const meta: Meta<ButtonStoryProps> = {
20
20
  options: ["default", "small"],
21
21
  description: "The size of the button",
22
22
  },
23
+ loading: {
24
+ control: "boolean",
25
+ description: "Sets the button to a loading state",
26
+ },
27
+ useRecursicaLoader: {
28
+ control: "boolean",
29
+ description:
30
+ "Use the Recursica Loader component instead of the default Mantine loader",
31
+ },
32
+ loaderVariant: {
33
+ control: "select",
34
+ options: ["oval", "bars", "dots"],
35
+ description: "The visual variant of the Recursica Loader",
36
+ },
37
+ loaderSize: {
38
+ control: "select",
39
+ options: [undefined, "sm", "md", "lg", "small", "default", "large"],
40
+ description: "The size variant for the loader",
41
+ },
42
+ },
43
+ args: {
44
+ useRecursicaLoader: true,
45
+ loaderVariant: "oval",
46
+ loaderSize: undefined,
23
47
  },
24
48
  };
25
49
 
@@ -126,3 +150,20 @@ export const TruncatedLabel: Story = {
126
150
  </div>
127
151
  ),
128
152
  };
153
+
154
+ export const Loading: Story = {
155
+ args: {
156
+ children: "Saving Changes",
157
+ variant: "solid",
158
+ size: "default",
159
+ loading: true,
160
+ },
161
+ parameters: {
162
+ docs: {
163
+ description: {
164
+ story:
165
+ "When `loading={true}` is applied, the Button injects the Recursica `<Loader />` component. Per Recursica design rules, placing a Button in a loading state automatically forces the `disabled={true}` state on the underlying element. This ensures the button immediately receives the brand theme disabled opacities without relying solely on semantic logic.",
166
+ },
167
+ },
168
+ },
169
+ };
@@ -1,4 +1,6 @@
1
1
  import React, { forwardRef } from "react";
2
+ import { Loader } from "../Loader/Loader";
3
+ import type { RecursicaLoaderProps } from "../Loader/Loader";
2
4
  import {
3
5
  Button as MantineButton,
4
6
  type ButtonProps as MantineButtonProps,
@@ -11,9 +13,18 @@ import {
11
13
  import styles from "./Button.module.css";
12
14
 
13
15
  export interface RecursicaButtonProps {
16
+ /** The visual style variant of the button */
14
17
  variant?: "solid" | "outline" | "text";
18
+ /** The size of the button */
15
19
  size?: "default" | "small";
20
+ /** An optional icon element to display to the left of the button text. Replaces Mantine's leftSection. */
16
21
  icon?: React.ReactNode;
22
+ /** Which Recursica Loader variant to use */
23
+ loaderVariant?: RecursicaLoaderProps["variant"];
24
+ /** The size variant for the loader */
25
+ loaderSize?: RecursicaLoaderProps["size"];
26
+ /** Whether to use the Recursica loader or fallback to the Mantine loader */
27
+ useRecursicaLoader?: boolean;
17
28
  }
18
29
 
19
30
  export type ButtonProps = RecursicaOverStyled<
@@ -34,6 +45,9 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
34
45
  icon,
35
46
  children,
36
47
  overStyled = false,
48
+ loaderVariant = "oval",
49
+ loaderSize,
50
+ useRecursicaLoader = true,
37
51
  ...rest
38
52
  },
39
53
  ref,
@@ -75,6 +89,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
75
89
  root: styles.root,
76
90
  section: styles.section,
77
91
  label: styles.label,
92
+ loader: styles.loader,
78
93
  };
79
94
 
80
95
  const classNamesProp = restRecord.classNames;
@@ -94,6 +109,21 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
94
109
  ? `${styles.root} ${classNameProp}`
95
110
  : styles.root;
96
111
 
112
+ const userLoaderProps = restRecord.loaderProps as
113
+ | Record<string, any>
114
+ | undefined;
115
+
116
+ const resolvedLoaderSize =
117
+ loaderSize ?? (size === "small" ? "small" : "default");
118
+
119
+ let mergedLoaderProps = userLoaderProps;
120
+ if (useRecursicaLoader) {
121
+ mergedLoaderProps = {
122
+ children: <Loader variant={loaderVariant} size={resolvedLoaderSize} />,
123
+ ...userLoaderProps,
124
+ };
125
+ }
126
+
97
127
  return (
98
128
  <MantineButton
99
129
  ref={ref}
@@ -101,6 +131,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
101
131
  classNames={mergedClassNames}
102
132
  variant={mapVariant[variant]}
103
133
  size={mapSize[size]}
134
+ loaderProps={mergedLoaderProps}
104
135
  leftSection={
105
136
  icon != null ? (
106
137
  <span className={styles.iconWrapper} aria-hidden>
@@ -112,6 +143,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
112
143
  data-size={size}
113
144
  {...(isIconOnly ? { "data-icon-only": "" } : {})}
114
145
  {...sanitizedProps}
146
+ disabled={!!restRecord.disabled || !!restRecord.loading}
115
147
  >
116
148
  <span className={styles.labelText}>{children}</span>
117
149
  </MantineButton>
@@ -43,3 +43,19 @@ Mantine's `.mantine-Button-label` flex centering breaks primitive truncation log
43
43
  ## Disabled state: brand theme opacity (implicit)
44
44
 
45
45
  **Decision:** The UI kit enforces global brand theme disabled opacities. The `.root:disabled` logic implicitly overrides visibility locally via `var(--recursica_brand_states_disabled)`.
46
+
47
+ ---
48
+
49
+ ## Loader color contrast
50
+
51
+ **Decision:** When a Button is in a loading state, the `Recursica Loader` component is injected. The `Loader` component strictly defines its own colors and styles per variant, meaning it does not inherit the text color (`currentColor`) from the Button.
52
+
53
+ **Constraint:** This can lead to contrast issues (e.g., a blue dots loader inside a solid blue button). Design has explicitly decided not to address this at the moment. As such, developers using the `loading` prop must be aware that the loader's color is fixed by its internal tokens, not by the button's context.
54
+
55
+ ---
56
+
57
+ ## Loading state enforces disabled state
58
+
59
+ **Decision:** When `loading={true}` is passed to the Button, the component explicitly forces `disabled={true}` natively on the underlying element.
60
+
61
+ **Implementation:** This ensures that loading buttons automatically inherit the brand theme disabled opacities (via the `:disabled` CSS pseudo-class) rather than relying solely on Mantine's native `data-disabled` dataset logic, which may not trigger the strict visual fade required by the Recursica design system.
@@ -23,6 +23,12 @@
23
23
  );
24
24
  gap: var(--recursica_ui-kit_components_toast_properties_spacing);
25
25
 
26
+ /* Hide Mantine's native colored vertical line */
27
+ &::before {
28
+ display: none;
29
+ content: none;
30
+ }
31
+
26
32
  & .icon {
27
33
  width: var(--recursica_ui-kit_components_toast_properties_icon) !important;
28
34
  height: var(--recursica_ui-kit_components_toast_properties_icon) !important;