@nexub/react 4.2.0 → 4.4.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,5 +1,5 @@
1
1
  {
2
- "version": "4.2.0",
2
+ "version": "4.4.0",
3
3
  "standard": "WCAG 2.2 Level AA",
4
4
  "method": {
5
5
  "automated": "axe-core rules tagged wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa, run on every Storybook story in light and dark on every pull request; a new violation fails the build.",
@@ -73,7 +73,7 @@
73
73
  "slug": "aurora",
74
74
  "name": "Aurora",
75
75
  "package": "@nexub/react",
76
- "automated": "not covered",
76
+ "automated": "pass",
77
77
  "exceptions": [],
78
78
  "keyboard": [],
79
79
  "screenReader": [
@@ -194,6 +194,17 @@
194
194
  "Prefer CardLink over wrapping the whole card in `<a>`: the link’s accessible name stays the title instead of every word on the card."
195
195
  ]
196
196
  },
197
+ {
198
+ "slug": "celebrate",
199
+ "name": "Celebrate",
200
+ "package": "@nexub/react",
201
+ "automated": "pass",
202
+ "exceptions": [],
203
+ "keyboard": [],
204
+ "screenReader": [
205
+ "Decorative (`aria-hidden`). Announce the win in text, for example in a Toast or a status message."
206
+ ]
207
+ },
197
208
  {
198
209
  "slug": "checkbox-card",
199
210
  "name": "Checkbox Card",
@@ -371,6 +382,17 @@
371
382
  "keyboard": [],
372
383
  "screenReader": []
373
384
  },
385
+ {
386
+ "slug": "floating-input",
387
+ "name": "Floating Input",
388
+ "package": "@nexub/react",
389
+ "automated": "pass",
390
+ "exceptions": [],
391
+ "keyboard": [],
392
+ "screenReader": [
393
+ "A real `<label>` tied to the input, so the name never disappears."
394
+ ]
395
+ },
374
396
  {
375
397
  "slug": "form-field",
376
398
  "name": "Form Field",
@@ -719,6 +741,17 @@
719
741
  "keyboard": [],
720
742
  "screenReader": []
721
743
  },
744
+ {
745
+ "slug": "scroll-progress",
746
+ "name": "Scroll Progress",
747
+ "package": "@nexub/react",
748
+ "automated": "pass",
749
+ "exceptions": [],
750
+ "keyboard": [],
751
+ "screenReader": [
752
+ "Decorative (`aria-hidden`); hidden under reduced motion and where scroll-driven animations are not supported."
753
+ ]
754
+ },
722
755
  {
723
756
  "slug": "search-input",
724
757
  "name": "Search Input",
@@ -755,6 +788,17 @@
755
788
  "keyboard": [],
756
789
  "screenReader": []
757
790
  },
791
+ {
792
+ "slug": "shared-element",
793
+ "name": "Shared Element",
794
+ "package": "@nexub/react",
795
+ "automated": "pass",
796
+ "exceptions": [],
797
+ "keyboard": [],
798
+ "screenReader": [
799
+ "Visual only: run the change in `startViewTransition()` and move focus to the new view as usual."
800
+ ]
801
+ },
758
802
  {
759
803
  "slug": "sheet",
760
804
  "name": "Sheet",
@@ -26,5 +26,8 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
26
26
  leadingIcon?: React.ReactNode;
27
27
  trailingIcon?: React.ReactNode;
28
28
  asChild?: boolean;
29
+ /** 4.3: the button drifts a few pixels toward the pointer on hover. For one key action per screen.
30
+ * Off for touch and reduced motion. */
31
+ magnetic?: boolean;
29
32
  }
30
33
  export declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { Slot } from "@radix-ui/react-slot";
5
5
  import { Loader2 } from "@nexub/icons";
6
6
  import { cva } from "class-variance-authority";
7
+ import { usePointerSheen } from "../../hooks/usePointerSheen.js";
7
8
  import { cn } from "../../utils/cn.js";
8
9
  import { disabledContainer, disabledText, pressable } from "../../utils/control.js";
9
10
  import { glassSurfaceClass } from "../../utils/glass.js";
@@ -81,19 +82,24 @@ const Button = React.forwardRef(
81
82
  leadingIcon,
82
83
  trailingIcon,
83
84
  asChild = false,
85
+ magnetic = false,
84
86
  disabled,
85
87
  onClick,
88
+ onPointerMove,
86
89
  children,
87
90
  ...props
88
91
  }, ref) => {
89
92
  const status = statusProp ?? (loading ? "loading" : "idle");
93
+ const handlePointerMove = usePointerSheen(magnetic, onPointerMove);
90
94
  const sharedProps = {
91
95
  className: cn(
92
96
  buttonVariants({ variant, size, fullWidth }),
93
97
  tone ? toneClasses[tone] : "",
94
98
  !asChild && status === "error" && "nx-shake",
99
+ magnetic && "nx-magnetic",
95
100
  className
96
101
  ),
102
+ onPointerMove: handlePointerMove,
97
103
  "data-tone": tone,
98
104
  // Styling hook for opt-in layers (vivid, marketing); no styles depend on it by default.
99
105
  "data-variant": variant ?? "primary",
@@ -8,8 +8,9 @@ export declare const cardVariants: (props?: ({
8
8
  export interface CardProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof cardVariants> {
9
9
  tone?: ToneValue;
10
10
  surface?: 'solid' | 'glass';
11
- /** `sheen`: a soft light follows the pointer (opt-in; off for touch and reduced motion). */
12
- effect?: 'none' | 'sheen';
11
+ /** `sheen`: a soft light follows the pointer. `tilt` (4.3): the card also leans toward the pointer in 3D.
12
+ * Opt-in; off for touch and reduced motion. */
13
+ effect?: 'none' | 'sheen' | 'tilt';
13
14
  /** Product accent for `variant="showcase"`: a coloured top edge and tint in marketing mode. Sets `data-product`. */
14
15
  accent?: 'leap' | 'bookub' | 'payub' | 'founderub';
15
16
  asChild?: boolean;
@@ -52,13 +52,13 @@ const Card = React.forwardRef(
52
52
  ({ className, variant = "flat", surface = "solid", effect = "none", tone, accent, asChild = false, onPointerMove, children, ...props }, ref) => {
53
53
  const Comp = asChild ? Slot : "div";
54
54
  const toneStyle = tone ? toneBorderMap[tone] : "";
55
- const sheen = effect === "sheen";
55
+ const sheen = effect === "sheen" || effect === "tilt";
56
56
  const handlePointerMove = usePointerSheen(sheen, onPointerMove);
57
57
  return /* @__PURE__ */ jsx(
58
58
  Comp,
59
59
  {
60
60
  ref,
61
- className: cn(cardVariants({ variant, surface }), toneStyle, sheen && "nx-sheen", className),
61
+ className: cn(cardVariants({ variant, surface }), toneStyle, sheen && "nx-sheen", effect === "tilt" && "nx-tilt", className),
62
62
  onPointerMove: handlePointerMove,
63
63
  "data-tone": tone,
64
64
  "data-variant": variant ?? "flat",
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ export interface CelebrateProps {
3
+ /** Bursts each time this goes from false to true. */
4
+ active: boolean;
5
+ /** Number of pieces. @default 28 */
6
+ count?: number;
7
+ className?: string;
8
+ }
9
+ export declare function Celebrate({ active, count, className }: CelebrateProps): React.JSX.Element | null;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.js";
5
+ const COLORS = ["bg-chart-1", "bg-chart-2", "bg-chart-3", "bg-chart-4", "bg-chart-5", "bg-chart-6", "bg-chart-7", "bg-chart-8"];
6
+ function Celebrate({ active, count = 28, className }) {
7
+ const ref = React.useRef(null);
8
+ const [burst, setBurst] = React.useState(0);
9
+ const was = React.useRef(false);
10
+ React.useEffect(() => {
11
+ if (active && !was.current) setBurst((b) => b + 1);
12
+ was.current = active;
13
+ }, [active]);
14
+ React.useLayoutEffect(() => {
15
+ const el = ref.current;
16
+ if (!burst || !el) return;
17
+ for (const piece of Array.from(el.children)) {
18
+ const angle = Math.random() * Math.PI * 2;
19
+ const distance = 60 + Math.random() * 90;
20
+ piece.style.setProperty("--x", `${Math.cos(angle) * distance}px`);
21
+ piece.style.setProperty("--y", `${Math.sin(angle) * distance + 40}px`);
22
+ piece.style.setProperty("--r", `${Math.random() * 540 - 270}deg`);
23
+ piece.style.setProperty("--d", `${Math.random() * 90}ms`);
24
+ }
25
+ }, [burst]);
26
+ if (!burst) return null;
27
+ return /* @__PURE__ */ jsx("div", { ref, "aria-hidden": "true", className: cn("nx-celebrate", className), children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx("span", { className: COLORS[i % COLORS.length] }, i)) }, burst);
28
+ }
29
+ export {
30
+ Celebrate
31
+ };
@@ -0,0 +1,2 @@
1
+ export { Celebrate } from './Celebrate.js';
2
+ export type { CelebrateProps } from './Celebrate.js';
@@ -0,0 +1,4 @@
1
+ import { Celebrate } from "./Celebrate.js";
2
+ export {
3
+ Celebrate
4
+ };
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ export interface FloatingInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'placeholder'> {
3
+ /** Always visible: inside the field when empty, above the value otherwise. */
4
+ label: string;
5
+ }
6
+ export declare const FloatingInput: React.ForwardRefExoticComponent<FloatingInputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,30 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../utils/cn.js";
4
+ const FloatingInput = React.forwardRef(
5
+ ({ label, id, className, ...props }, ref) => {
6
+ const auto = React.useId();
7
+ const inputId = id ?? auto;
8
+ return /* @__PURE__ */ jsxs("div", { className: cn("nx-float-field relative", className), children: [
9
+ /* @__PURE__ */ jsx(
10
+ "input",
11
+ {
12
+ ref,
13
+ id: inputId,
14
+ placeholder: " ",
15
+ className: cn(
16
+ "h-14 w-full rounded-shape-field border border-[var(--color-outline)] bg-[var(--color-surface)] px-4 pt-5 text-body-md text-[var(--color-on-surface)]",
17
+ "outline-none transition-[border-color,box-shadow] focus-visible:border-[var(--color-primary)] focus-visible:shadow-[0_0_0_1px_var(--color-primary)]",
18
+ "aria-[invalid=true]:border-[var(--color-error)] disabled:cursor-not-allowed disabled:border-[var(--color-outline-variant)] disabled:text-[var(--color-text-disabled)]"
19
+ ),
20
+ ...props
21
+ }
22
+ ),
23
+ /* @__PURE__ */ jsx("label", { htmlFor: inputId, children: label })
24
+ ] });
25
+ }
26
+ );
27
+ FloatingInput.displayName = "FloatingInput";
28
+ export {
29
+ FloatingInput
30
+ };
@@ -0,0 +1,2 @@
1
+ export { FloatingInput } from './FloatingInput.js';
2
+ export type { FloatingInputProps } from './FloatingInput.js';
@@ -0,0 +1,4 @@
1
+ import { FloatingInput } from "./FloatingInput.js";
2
+ export {
3
+ FloatingInput
4
+ };
@@ -0,0 +1,3 @@
1
+ import * as React from 'react';
2
+ export type ScrollProgressProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>;
3
+ export declare const ScrollProgress: React.ForwardRefExoticComponent<ScrollProgressProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,8 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../utils/cn.js";
4
+ const ScrollProgress = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, "aria-hidden": "true", className: cn("nx-scroll-progress", className), ...props }));
5
+ ScrollProgress.displayName = "ScrollProgress";
6
+ export {
7
+ ScrollProgress
8
+ };
@@ -0,0 +1,2 @@
1
+ export { ScrollProgress } from './ScrollProgress.js';
2
+ export type { ScrollProgressProps } from './ScrollProgress.js';
@@ -0,0 +1,4 @@
1
+ import { ScrollProgress } from "./ScrollProgress.js";
2
+ export {
3
+ ScrollProgress
4
+ };
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ export interface SharedElementProps {
3
+ /** Same name on both sides of the transition, e.g. `avatar-${user.id}`. */
4
+ name: string;
5
+ children: React.ReactElement;
6
+ }
7
+ /** Turns any string into a valid <custom-ident> for view-transition-name. */
8
+ export declare const transitionName: (name: string) => string;
9
+ export declare function SharedElement({ name, children }: SharedElementProps): React.JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ const transitionName = (name) => `nx-${name.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
6
+ function SharedElement({ name, children }) {
7
+ const ref = React.useRef(null);
8
+ React.useLayoutEffect(() => {
9
+ const el = ref.current;
10
+ if (!el) return;
11
+ el.style.setProperty("view-transition-name", transitionName(name));
12
+ return () => {
13
+ el.style.removeProperty("view-transition-name");
14
+ };
15
+ }, [name]);
16
+ return /* @__PURE__ */ jsx(Slot, { ref, children });
17
+ }
18
+ export {
19
+ SharedElement,
20
+ transitionName
21
+ };
@@ -0,0 +1,2 @@
1
+ export { SharedElement, transitionName } from './SharedElement.js';
2
+ export type { SharedElementProps } from './SharedElement.js';
@@ -0,0 +1,5 @@
1
+ import { SharedElement, transitionName } from "./SharedElement.js";
2
+ export {
3
+ SharedElement,
4
+ transitionName
5
+ };
@@ -14,6 +14,8 @@ function usePointerSheen(enabled, onPointerMove) {
14
14
  const r = el.getBoundingClientRect();
15
15
  el.style.setProperty("--nx-mx", `${clientX - r.left}px`);
16
16
  el.style.setProperty("--nx-my", `${clientY - r.top}px`);
17
+ el.style.setProperty("--nx-px", String(r.width ? (clientX - r.left) / r.width : 0.5));
18
+ el.style.setProperty("--nx-py", String(r.height ? (clientY - r.top) / r.height : 0.5));
17
19
  });
18
20
  },
19
21
  [onPointerMove]
package/dist/index.d.ts CHANGED
@@ -36,6 +36,14 @@ export type { NumberInputProps } from './components/NumberInput/index.js';
36
36
  export { MoneyInput } from './components/MoneyInput/index.js';
37
37
  export type { MoneyInputProps } from './components/MoneyInput/index.js';
38
38
  export { Amount } from './components/Amount/index.js';
39
+ export { SharedElement, transitionName } from './components/SharedElement/index.js';
40
+ export type { SharedElementProps } from './components/SharedElement/index.js';
41
+ export { ScrollProgress } from './components/ScrollProgress/index.js';
42
+ export type { ScrollProgressProps } from './components/ScrollProgress/index.js';
43
+ export { FloatingInput } from './components/FloatingInput/index.js';
44
+ export type { FloatingInputProps } from './components/FloatingInput/index.js';
45
+ export { Celebrate } from './components/Celebrate/index.js';
46
+ export type { CelebrateProps } from './components/Celebrate/index.js';
39
47
  export { Aurora } from './components/Aurora/index.js';
40
48
  export type { AuroraProps } from './components/Aurora/index.js';
41
49
  export { BarChart, LineChart, DonutChart, Sparkline, ChartLegend, niceMax, niceDomain } from './components/Chart/index.js';
package/dist/index.js CHANGED
@@ -21,6 +21,10 @@ import { Textarea, textareaVariants } from "./components/Textarea/index.js";
21
21
  import { NumberInput } from "./components/NumberInput/index.js";
22
22
  import { MoneyInput } from "./components/MoneyInput/index.js";
23
23
  import { Amount } from "./components/Amount/index.js";
24
+ import { SharedElement, transitionName } from "./components/SharedElement/index.js";
25
+ import { ScrollProgress } from "./components/ScrollProgress/index.js";
26
+ import { FloatingInput } from "./components/FloatingInput/index.js";
27
+ import { Celebrate } from "./components/Celebrate/index.js";
24
28
  import { Aurora } from "./components/Aurora/index.js";
25
29
  import { BarChart, LineChart, DonutChart, Sparkline, ChartLegend, niceMax, niceDomain } from "./components/Chart/index.js";
26
30
  import { NumberTicker } from "./components/NumberTicker/index.js";
@@ -131,6 +135,7 @@ export {
131
135
  ButtonGroup,
132
136
  Card,
133
137
  CardLink,
138
+ Celebrate,
134
139
  ChartLegend,
135
140
  CheckList,
136
141
  CheckListItem,
@@ -162,6 +167,7 @@ export {
162
167
  Flag,
163
168
  Flex,
164
169
  Float,
170
+ FloatingInput,
165
171
  FormError,
166
172
  FormField,
167
173
  GradientDivider,
@@ -202,11 +208,13 @@ export {
202
208
  Reveal,
203
209
  Scale,
204
210
  ScrollArea,
211
+ ScrollProgress,
205
212
  SearchInput,
206
213
  Section,
207
214
  SegmentedControl,
208
215
  Select,
209
216
  Separator,
217
+ SharedElement,
210
218
  Sheet,
211
219
  Sidebar,
212
220
  Skeleton,
@@ -272,6 +280,7 @@ export {
272
280
  textareaVariants,
273
281
  toast,
274
282
  toggleVariants,
283
+ transitionName,
275
284
  useControllableState,
276
285
  useDisclosure,
277
286
  useIsomorphicLayoutEffect,