@hgdc/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.
- package/README.md +43 -2
- package/dist/avatar.d.ts +11 -0
- package/dist/avatar.js +23 -0
- package/dist/badge.d.ts +9 -0
- package/dist/badge.js +21 -0
- package/dist/button.d.ts +19 -0
- package/dist/button.js +94 -0
- package/dist/cn.d.ts +10 -0
- package/dist/cn.js +5 -0
- package/dist/dialog.d.ts +14 -0
- package/dist/dialog.js +73 -0
- package/dist/drawer.d.ts +59 -0
- package/dist/drawer.js +77 -0
- package/dist/dropdown-menu.d.ts +36 -0
- package/dist/dropdown-menu.js +71 -0
- package/dist/empty.d.ts +12 -0
- package/dist/empty.js +33 -0
- package/dist/field.d.ts +34 -0
- package/dist/field.js +102 -0
- package/dist/floating.d.ts +40 -0
- package/dist/floating.js +40 -0
- package/dist/input-otp.d.ts +18 -0
- package/dist/input-otp.js +26 -0
- package/dist/input.d.ts +3 -0
- package/dist/input.js +36 -0
- package/dist/label.d.ts +3 -0
- package/dist/label.js +7 -0
- package/dist/segmented-control.d.ts +32 -0
- package/dist/segmented-control.js +25 -0
- package/dist/select.d.ts +21 -0
- package/dist/select.js +73 -0
- package/dist/separator.d.ts +3 -0
- package/dist/separator.js +7 -0
- package/dist/sonner.d.ts +3 -0
- package/dist/sonner.js +33 -0
- package/dist/spinner.d.ts +9 -0
- package/dist/spinner.js +11 -0
- package/dist/switch.d.ts +17 -0
- package/dist/switch.js +25 -0
- package/dist/textarea.d.ts +9 -0
- package/dist/textarea.js +16 -0
- package/dist/theme.d.ts +21 -0
- package/dist/theme.js +68 -0
- package/dist/use-media-query.d.ts +23 -0
- package/dist/use-media-query.js +51 -0
- package/package.json +29 -9
- package/styles.css +374 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
package/README.md
CHANGED
|
@@ -1,7 +1,48 @@
|
|
|
1
1
|
# @hgdc/ui
|
|
2
2
|
|
|
3
|
-
Interface de la H family : jetons de la DA, thèmes, polices et composants.
|
|
3
|
+
Interface de la H family : jetons de la DA, thèmes clair et sombre, polices et composants de base, pour une application React 19 + Tailwind v4.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```sh
|
|
6
|
+
bun add @hgdc/ui
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
/* src/styles.css */
|
|
11
|
+
@import "tailwindcss";
|
|
12
|
+
@import "@hgdc/ui/styles.css";
|
|
13
|
+
|
|
14
|
+
/* Écarts du produit, après l'import. */
|
|
15
|
+
:root {
|
|
16
|
+
--chart-1: oklch(0.55 0.25 296);
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { Button } from "@hgdc/ui/button"
|
|
22
|
+
import { ThemeProvider, themeInitScript, useTheme } from "@hgdc/ui/theme"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Contenu
|
|
26
|
+
|
|
27
|
+
| Import | Rôle |
|
|
28
|
+
| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `@hgdc/ui/styles.css` | Jetons OKLCH, thèmes (`.dark` sur `<html>`), Inter et Geist, utilitaires `surface`, `pressable`, `soft-in`… |
|
|
30
|
+
| `@hgdc/ui/theme` | `ThemeProvider`, `useTheme`, `themeInitScript` (à poser dans le `<head>` contre le flash de thème) |
|
|
31
|
+
| `@hgdc/ui/cn` | `cn()` (clsx + tailwind-merge) |
|
|
32
|
+
| `@hgdc/ui/use-media-query` | `useMediaQuery`, `useIsMobile` (sûrs au rendu serveur) |
|
|
33
|
+
| `@hgdc/ui/floating` | Classes des surfaces flottantes (menus, sélecteurs) |
|
|
34
|
+
| `avatar` `badge` `button` `dialog` `drawer` `dropdown-menu` `empty` `field` `input` `input-otp` | Primitives sur Base UI |
|
|
35
|
+
| `label` `segmented-control` `select` `separator` `sonner` `spinner` `switch` `textarea` | Primitives sur Base UI, toasts sonner, icônes Hugeicons |
|
|
36
|
+
|
|
37
|
+
Un sous-chemin par composant, sans fichier d'index : `import { Badge } from "@hgdc/ui/badge"`.
|
|
38
|
+
|
|
39
|
+
## Règles de la DA
|
|
40
|
+
|
|
41
|
+
- Chrome monochrome, un seul accent violet : focus, lien, élément actif, un geste principal par écran.
|
|
42
|
+
- Profondeur par la teinte : ni ombre ni bordure dans le flux, ombre réservée à ce qui flotte.
|
|
43
|
+
- Boutons en pilule, champs et menus en `rounded-2xl`, cartes en `rounded-3xl`.
|
|
44
|
+
- Un `Button` rendu en lien (`render={<Link />}`) reste un lien pour les lecteurs d'écran.
|
|
45
|
+
|
|
46
|
+
Mode d'emploi complet : [Adopter le kit](https://github.com/hugogebel-boop/hgdc-kit/blob/main/docs/guides/adopter-le-kit.md#7-interface). Décisions : [ADR 0005](https://github.com/hugogebel-boop/hgdc-kit/blob/main/docs/adr/0005-ui-css-brute-et-sous-chemins.md).
|
|
6
47
|
|
|
7
48
|
Tous droits réservés, Hugo Gebel Développement Conseil.
|
package/dist/avatar.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
declare function Avatar({ className, size, ...props }: AvatarPrimitive.Root.Props & {
|
|
4
|
+
size?: "default" | "sm" | "lg";
|
|
5
|
+
}): React.JSX.Element;
|
|
6
|
+
declare function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props): React.JSX.Element;
|
|
7
|
+
declare function AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props): React.JSX.Element;
|
|
8
|
+
declare function AvatarBadge({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
|
|
9
|
+
declare function AvatarGroup({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
10
|
+
declare function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
11
|
+
export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarBadge };
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "./cn.js";
|
|
5
|
+
function Avatar({ className, size = "default", ...props }) {
|
|
6
|
+
return (_jsx(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, className: cn("group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten", className), ...props }));
|
|
7
|
+
}
|
|
8
|
+
function AvatarImage({ className, ...props }) {
|
|
9
|
+
return (_jsx(AvatarPrimitive.Image, { "data-slot": "avatar-image", className: cn("aspect-square size-full rounded-full object-cover", className), ...props }));
|
|
10
|
+
}
|
|
11
|
+
function AvatarFallback({ className, ...props }) {
|
|
12
|
+
return (_jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", className: cn("flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs", className), ...props }));
|
|
13
|
+
}
|
|
14
|
+
function AvatarBadge({ className, ...props }) {
|
|
15
|
+
return (_jsx("span", { "data-slot": "avatar-badge", className: cn("absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none", "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden", "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", className), ...props }));
|
|
16
|
+
}
|
|
17
|
+
function AvatarGroup({ className, ...props }) {
|
|
18
|
+
return (_jsx("div", { "data-slot": "avatar-group", className: cn("group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background", className), ...props }));
|
|
19
|
+
}
|
|
20
|
+
function AvatarGroupCount({ className, ...props }) {
|
|
21
|
+
return (_jsx("div", { "data-slot": "avatar-group-count", className: cn("relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", className), ...props }));
|
|
22
|
+
}
|
|
23
|
+
export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarBadge };
|
package/dist/badge.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type VariantProps } from "class-variance-authority";
|
|
2
|
+
import type { ComponentProps } from "react";
|
|
3
|
+
import { type Variants } from "./cn.ts";
|
|
4
|
+
export type BadgeTone = "neutral" | "success" | "warning" | "info" | "destructive";
|
|
5
|
+
declare const badgeVariants: Variants<{
|
|
6
|
+
tone: BadgeTone;
|
|
7
|
+
}>;
|
|
8
|
+
declare function Badge({ className, tone, ...props }: ComponentProps<"span"> & VariantProps<typeof badgeVariants>): React.JSX.Element;
|
|
9
|
+
export { Badge, badgeVariants };
|
package/dist/badge.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import { cn } from "./cn.js";
|
|
4
|
+
const badgeVariants = cva("inline-flex w-fit items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-3", {
|
|
5
|
+
variants: {
|
|
6
|
+
tone: {
|
|
7
|
+
neutral: "bg-muted text-muted-foreground",
|
|
8
|
+
success: "bg-status-active/15 text-status-active",
|
|
9
|
+
warning: "bg-status-paused/15 text-status-paused",
|
|
10
|
+
info: "bg-status-done/15 text-status-done",
|
|
11
|
+
destructive: "bg-destructive/10 text-destructive",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
tone: "neutral",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
function Badge({ className, tone, ...props }) {
|
|
19
|
+
return (_jsx("span", { "data-slot": "badge", "data-tone": tone ?? "neutral", className: cn(badgeVariants({ tone, className })), ...props }));
|
|
20
|
+
}
|
|
21
|
+
export { Badge, badgeVariants };
|
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { type Variants } from "./cn.ts";
|
|
4
|
+
type ButtonVariant = "default" | "outline" | "secondary" | "ghost" | "destructive" | "link";
|
|
5
|
+
type ButtonSize = "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
|
|
6
|
+
declare const buttonVariants: Variants<{
|
|
7
|
+
variant: ButtonVariant;
|
|
8
|
+
size: ButtonSize;
|
|
9
|
+
}>;
|
|
10
|
+
type ButtonProps = ButtonPrimitive.Props & VariantProps<typeof buttonVariants> & {
|
|
11
|
+
/**
|
|
12
|
+
* When true, the button is disabled and its children are replaced with a
|
|
13
|
+
* spinner. If `children` is a plain string, it is passed as the spinner
|
|
14
|
+
* label so the action stays readable while in-flight.
|
|
15
|
+
*/
|
|
16
|
+
isLoading?: boolean | undefined;
|
|
17
|
+
};
|
|
18
|
+
declare function Button({ className, variant, size, isLoading, disabled, children, ...props }: ButtonProps): React.JSX.Element;
|
|
19
|
+
export { Button, buttonVariants, type ButtonProps };
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { isValidElement } from "react";
|
|
6
|
+
import { Spinner } from "./spinner.js";
|
|
7
|
+
import { cn } from "./cn.js";
|
|
8
|
+
const buttonVariants = cva(
|
|
9
|
+
// Appui : combine `:active` (CSS pseudo, marche sur tout élément pressable —
|
|
10
|
+
// DropdownMenuTrigger, Link stylisés via buttonVariants, etc.) ET
|
|
11
|
+
// `data-pressed` (attribut Base UI, fiable au doigt). Pas d'exclusion
|
|
12
|
+
// `aria-haspopup` : les déclencheurs de menu répondent aussi.
|
|
13
|
+
// `transition-property` explicite avec `scale` (Tailwind v4 sépare
|
|
14
|
+
// `scale:` de `transform:`).
|
|
15
|
+
//
|
|
16
|
+
// Le bouton s'enfonce (0.97), il ne grossit pas. C'est la métaphore
|
|
17
|
+
// physique : on appuie sur une touche, elle descend. Un bouton qui grandit
|
|
18
|
+
// sous le doigt donne l'impression de venir vers soi, ce qu'aucun objet
|
|
19
|
+
// pressé ne fait.
|
|
20
|
+
//
|
|
21
|
+
// Timing asymétrique : l'appui répond en 100 ms parce que la personne
|
|
22
|
+
// regarde à cet instant précis ; la relâche revient en 300 ms avec un petit
|
|
23
|
+
// rebond (`--ease-bounce`, révision du 2026-09-24).
|
|
24
|
+
//
|
|
25
|
+
// Au survol d'un vrai pointeur (`hover:` ne s'applique qu'aux pointeurs
|
|
26
|
+
// fins en Tailwind v4), le bouton s'avance d'un rien (1.02), comme sur le
|
|
27
|
+
// site ; aucune ombre, jamais : ce qui répond au survol est la couleur et
|
|
28
|
+
// l'échelle.
|
|
29
|
+
//
|
|
30
|
+
// Ni bordure transparente ni `bg-clip-padding` (retirés le 2026-09-27) : le
|
|
31
|
+
// fond s'arrêtait un pixel avant le bord, et dans une feuille du bas posée à
|
|
32
|
+
// une coordonnée fractionnaire (hauteur en `svh`, glissement), iOS laissait
|
|
33
|
+
// voir ce pixel comme un filet sombre en haut de la pilule, en thème sombre.
|
|
34
|
+
// Le focus et l'erreur passent par l'anneau seul.
|
|
35
|
+
"group/button touch-target inline-flex shrink-0 items-center justify-center rounded-full text-sm font-medium whitespace-nowrap [transition-property:color,background-color,opacity,scale,box-shadow] duration-(--duration-slow) ease-(--ease-bounce) outline-none select-none focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.96] active:duration-(--duration-instant) active:ease-out disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-3 aria-invalid:ring-destructive/40 data-[pressed]:scale-[0.96] data-[pressed]:duration-(--duration-instant) data-[pressed]:ease-out motion-safe:hover:scale-[1.02] dark:aria-invalid:ring-destructive/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
36
|
+
variants: {
|
|
37
|
+
variant: {
|
|
38
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/85",
|
|
39
|
+
// Un filet à 70 % plutôt qu'une bordure pleine : c'est le seul
|
|
40
|
+
// contour de l'interface, et il est là pour dire « secondaire ».
|
|
41
|
+
outline: "bg-background ring-1 ring-border/70 ring-inset hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-transparent dark:hover:bg-input/30",
|
|
42
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
43
|
+
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
44
|
+
destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:ring-destructive/30 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
45
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
46
|
+
},
|
|
47
|
+
// L'échelle de la barre du haut (révision du 2026-09-25) : un bouton a
|
|
48
|
+
// la hauteur de la maison et de la cloche, 44 px au doigt et 40 au-dessus
|
|
49
|
+
// de `lg`, et un bouton-icône leur icône de 20 px. Avant, la page vivait
|
|
50
|
+
// sur 36 et 32 px à côté de ronds de 44 : deux échelles sur un écran.
|
|
51
|
+
// `sm` reste un cran plus bas, pour une carte ou une ligne de tableau.
|
|
52
|
+
size: {
|
|
53
|
+
default: "h-11 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 lg:h-10",
|
|
54
|
+
xs: "h-7 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5",
|
|
55
|
+
sm: "h-10 gap-1.5 px-3.5 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 lg:h-9",
|
|
56
|
+
lg: "h-12 gap-2 px-5 text-base has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4 lg:h-11",
|
|
57
|
+
icon: "size-11 lg:size-10 [&_svg:not([class*='size-'])]:size-5",
|
|
58
|
+
"icon-xs": "size-7 [&_svg:not([class*='size-'])]:size-3.5",
|
|
59
|
+
"icon-sm": "size-10 lg:size-9 [&_svg:not([class*='size-'])]:size-4.5",
|
|
60
|
+
"icon-lg": "size-12 lg:size-11 [&_svg:not([class*='size-'])]:size-5",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
defaultVariants: {
|
|
64
|
+
variant: "default",
|
|
65
|
+
size: "default",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
function Button({ className, variant = "default", size = "default", isLoading, disabled, children, ...props }) {
|
|
69
|
+
// Sur les variantes `icon*` le bouton n'a pas de padding horizontal pour
|
|
70
|
+
// accueillir un label — on n'expose que le spinner pour éviter un overflow.
|
|
71
|
+
const isIconSize = typeof size === "string" && size.startsWith("icon");
|
|
72
|
+
const spinnerLabel = !isIconSize && typeof children === "string" ? children : undefined;
|
|
73
|
+
const classes = cn(buttonVariants({ variant, size, className: typeof className === "string" ? className : undefined }));
|
|
74
|
+
// Rendu en lien (`render={<Link />}` ou `<a>`) : le lien garde sa sémantique de lien. Passé par
|
|
75
|
+
// `ButtonPrimitive`, même avec `nativeButton={false}`, il recevait `role="button"` (doc Base UI :
|
|
76
|
+
// un lien ne se rend pas par Button, il en prend seulement le style).
|
|
77
|
+
const { render, nativeButton: _nativeButton, focusableWhenDisabled: _focusable, ...linkProps } = props;
|
|
78
|
+
const linkElement = isValidElement(render) && render.type !== "button" ? render : undefined;
|
|
79
|
+
const link = useRender({
|
|
80
|
+
render: linkElement,
|
|
81
|
+
enabled: linkElement !== undefined,
|
|
82
|
+
props: {
|
|
83
|
+
...linkProps,
|
|
84
|
+
"data-slot": "button",
|
|
85
|
+
className: classes,
|
|
86
|
+
"aria-disabled": isLoading || disabled || undefined,
|
|
87
|
+
children,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
if (link)
|
|
91
|
+
return link;
|
|
92
|
+
return (_jsx(ButtonPrimitive, { "data-slot": "button", className: classes, disabled: isLoading || disabled, ...props, children: isLoading ? _jsx(Spinner, { ...(spinnerLabel !== undefined ? { label: spinnerLabel } : {}) }) : children }));
|
|
93
|
+
}
|
|
94
|
+
export { Button, buttonVariants };
|
package/dist/cn.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ClassProp } from "class-variance-authority/types";
|
|
2
|
+
import type { ClassValue } from "clsx";
|
|
3
|
+
export declare function cn(...inputs: Array<ClassValue>): string;
|
|
4
|
+
/**
|
|
5
|
+
* Signature d'une fonction `cva`, écrite à la main : `isolatedDeclarations` exige un type
|
|
6
|
+
* explicite sur tout ce qu'expose un paquet, et celui que `cva` infère ne s'écrit pas.
|
|
7
|
+
*/
|
|
8
|
+
export type Variants<V extends Record<string, string>> = (props?: {
|
|
9
|
+
[K in keyof V]?: V[K] | null;
|
|
10
|
+
} & ClassProp) => string;
|
package/dist/cn.js
ADDED
package/dist/dialog.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
declare function Dialog(props: DialogPrimitive.Root.Props): React.JSX.Element;
|
|
4
|
+
declare const DialogTrigger: typeof DialogPrimitive.Trigger;
|
|
5
|
+
declare const DialogClose: typeof DialogPrimitive.Close;
|
|
6
|
+
declare function DialogContent({ className, children, showClose, ...props }: DialogPrimitive.Popup.Props & {
|
|
7
|
+
showClose?: boolean;
|
|
8
|
+
}): React.JSX.Element;
|
|
9
|
+
declare function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
|
|
10
|
+
declare function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props): React.JSX.Element;
|
|
11
|
+
declare function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props): React.JSX.Element;
|
|
12
|
+
declare function DialogBody({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
|
|
13
|
+
declare function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
|
|
14
|
+
export { Dialog, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, };
|
package/dist/dialog.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
3
|
+
import Cancel01Icon from "@hugeicons/core-free-icons/Cancel01Icon";
|
|
4
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { Drawer, DrawerCloseButton, DrawerContent } from "./drawer.js";
|
|
7
|
+
import { useIsMobile } from "./use-media-query.js";
|
|
8
|
+
import { cn } from "./cn.js";
|
|
9
|
+
// La boîte est pleine et nette : c'est le fond qui est assombri et flouté,
|
|
10
|
+
// pas elle. Translucide et floutée elle-même, elle laissait transparaître le
|
|
11
|
+
// contenu qu'elle recouvrait — on lisait deux plans à la fois là où l'on
|
|
12
|
+
// cherchait justement à n'en montrer qu'un. Sur iPhone en app installée, ce
|
|
13
|
+
// second `backdrop-filter` coûtait en plus une recomposition à chaque image
|
|
14
|
+
// de l'animation d'entrée.
|
|
15
|
+
// Sous `lg`, une boîte de dialogue est une feuille du bas, et une feuille se
|
|
16
|
+
// referme au doigt. Elle en avait l'allure sans le geste : on la tirait vers
|
|
17
|
+
// le bas et rien ne bougeait (révision du 2026-09-23). Elle passe donc par le
|
|
18
|
+
// même `Drawer` que les autres feuilles ; Base UI bâtit le Drawer sur le
|
|
19
|
+
// Dialog, titre, description, déclencheur et croix sont les mêmes pièces.
|
|
20
|
+
const SheetContext = React.createContext(false);
|
|
21
|
+
function Dialog(props) {
|
|
22
|
+
const isMobile = useIsMobile();
|
|
23
|
+
if (!isMobile)
|
|
24
|
+
return _jsx(DialogPrimitive.Root, { ...props });
|
|
25
|
+
return (_jsx(SheetContext.Provider, { value: true, children: _jsx(Drawer, { side: "bottom", ...props }) }));
|
|
26
|
+
}
|
|
27
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
28
|
+
const DialogClose = DialogPrimitive.Close;
|
|
29
|
+
// Largeurs et hauteurs de la boîte : la feuille prend toute la largeur et
|
|
30
|
+
// borne elle-même sa hauteur, elles n'ont rien à y faire.
|
|
31
|
+
const BOX_ONLY = /^(?:[a-z0-9-]+:)*(?:max-w|w|max-h|h|overflow(?:-[xy])?)-/;
|
|
32
|
+
function sheetClasses(className) {
|
|
33
|
+
if (typeof className !== "string")
|
|
34
|
+
return undefined;
|
|
35
|
+
return className
|
|
36
|
+
.split(/\s+/)
|
|
37
|
+
.filter((token) => token !== "" && !BOX_ONLY.test(token) && !token.startsWith("lg:"))
|
|
38
|
+
.join(" ");
|
|
39
|
+
}
|
|
40
|
+
function DialogContent({ className, children, showClose = true, ...props }) {
|
|
41
|
+
const sheet = React.useContext(SheetContext);
|
|
42
|
+
if (sheet) {
|
|
43
|
+
return (_jsxs(DrawerContent, { ...props, children: [_jsx("div", { "data-slot": "dialog-content", className: cn("grid min-h-0 flex-1 grid-cols-1 content-start gap-4 overflow-y-auto overscroll-contain px-5 pt-3 pb-5", sheetClasses(className)), children: children }), showClose ? _jsx(DrawerCloseButton, {}) : null] }));
|
|
44
|
+
}
|
|
45
|
+
return (_jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-foreground/40 transition-opacity duration-(--duration-slow) ease-ios data-ending-style:opacity-0 data-starting-style:opacity-0 dark:bg-black/60" }), _jsxs(DialogPrimitive.Popup, { "data-slot": "dialog-content", className: cn(
|
|
46
|
+
// Pas d'anneau : la boîte est déjà détachée par le voile qui assombrit
|
|
47
|
+
// tout le reste. L'ombre, douce, reste la seule de l'interface avec
|
|
48
|
+
// celle des menus — elle dit « au-dessus », rien d'autre.
|
|
49
|
+
//
|
|
50
|
+
// Entrée et sortie en transitions (pas en keyframes) : une boîte
|
|
51
|
+
// fermée pendant son ouverture repart de là où elle en est. Elle
|
|
52
|
+
// monte de 0.97 à 1 sur la courbe iOS, centrée — une boîte modale
|
|
53
|
+
// n'a pas d'ancre dont partir.
|
|
54
|
+
"fixed z-50 grid gap-4 overflow-y-auto bg-popover text-popover-foreground shadow-2xl shadow-black/10 outline-none",
|
|
55
|
+
// Écran large seulement : sous `lg`, c'est la feuille ci-dessus.
|
|
56
|
+
"lg:top-1/2 lg:left-1/2 lg:max-h-[calc(100dvh-2rem)] lg:w-[calc(100%-2rem)] lg:max-w-lg lg:-translate-x-1/2 lg:-translate-y-1/2 lg:rounded-3xl lg:p-6 lg:transition-[opacity,scale] lg:duration-(--duration-slow) lg:ease-ios lg:data-ending-style:scale-[0.97] lg:data-ending-style:opacity-0 lg:data-ending-style:duration-(--duration-base) lg:data-starting-style:scale-[0.97] lg:data-starting-style:opacity-0", className), ...props, children: [children, showClose ? (_jsx(DialogPrimitive.Close, { className: "touch-target absolute top-4 right-4 flex size-10 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", "aria-label": "Fermer", children: _jsx(HugeiconsIcon, { icon: Cancel01Icon, strokeWidth: 2, className: "size-5" }) })) : null] })] }));
|
|
57
|
+
}
|
|
58
|
+
function DialogHeader({ className, ...props }) {
|
|
59
|
+
return _jsx("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-1.5 pr-8", className), ...props });
|
|
60
|
+
}
|
|
61
|
+
function DialogTitle({ className, ...props }) {
|
|
62
|
+
return (_jsx(DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn("font-heading text-lg font-medium tracking-tight", className), ...props }));
|
|
63
|
+
}
|
|
64
|
+
function DialogDescription({ className, ...props }) {
|
|
65
|
+
return (_jsx(DialogPrimitive.Description, { "data-slot": "dialog-description", className: cn("text-sm text-muted-foreground", className), ...props }));
|
|
66
|
+
}
|
|
67
|
+
function DialogBody({ className, ...props }) {
|
|
68
|
+
return _jsx("div", { "data-slot": "dialog-body", className: cn("", className), ...props });
|
|
69
|
+
}
|
|
70
|
+
function DialogFooter({ className, ...props }) {
|
|
71
|
+
return (_jsx("div", { "data-slot": "dialog-footer", className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props }));
|
|
72
|
+
}
|
|
73
|
+
export { Dialog, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, };
|
package/dist/drawer.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
|
|
2
|
+
import { type HTMLAttributes } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Panneau glissant, sur Base UI Drawer.
|
|
5
|
+
*
|
|
6
|
+
* Deux placements. `bottom` : une feuille qui monte du bas et se referme au
|
|
7
|
+
* doigt, pour un téléphone. `right` : un volet flottant à droite pour un écran
|
|
8
|
+
* large, le « Peek » de Linear — la liste reste visible derrière, et
|
|
9
|
+
* cliquable si le panneau n'est pas modal. `auto` choisit selon la largeur
|
|
10
|
+
* (`lg`), au même seuil que la barre de navigation.
|
|
11
|
+
*
|
|
12
|
+
* Le geste vient de Base UI : pendant le glissement, le panneau suit le doigt
|
|
13
|
+
* par `--drawer-swipe-movement-*` et la transition est coupée
|
|
14
|
+
* (`data-swiping`) ; au lâcher, elle reprend depuis la position courante.
|
|
15
|
+
* Rien ici n'est un keyframe : une ouverture interrompue se retourne sans
|
|
16
|
+
* rejouer l'animation entière.
|
|
17
|
+
*
|
|
18
|
+
* La feuille du bas suit la recette de la démo Base UI (révision du
|
|
19
|
+
* 2026-09-23) : `touch-action: none` sur la feuille, sinon le navigateur prend
|
|
20
|
+
* le doigt pour un défilement natif et le glissement s'interrompt en route.
|
|
21
|
+
* Une zone qui défile dedans garde son défilement : le navigateur ne combine
|
|
22
|
+
* `touch-action` que jusqu'au conteneur défilant le plus proche. Un fond qui déborde sous le
|
|
23
|
+
* bord (`--bleed`) évite le vide quand on tire vers le haut, la sortie dure
|
|
24
|
+
* selon la vitesse du geste (`--drawer-swipe-strength`), et le clavier virtuel
|
|
25
|
+
* est suivi (`VirtualKeyboardProvider`) : le champ touché reste visible.
|
|
26
|
+
*/
|
|
27
|
+
type DrawerSide = "bottom" | "right";
|
|
28
|
+
type DrawerProps = Omit<DrawerPrimitive.Root.Props, "swipeDirection"> & {
|
|
29
|
+
side?: DrawerSide | "auto";
|
|
30
|
+
};
|
|
31
|
+
declare function Drawer({ side, children, ...props }: DrawerProps): React.JSX.Element;
|
|
32
|
+
declare const DrawerTrigger: typeof DrawerPrimitive.Trigger;
|
|
33
|
+
declare const DrawerClose: typeof DrawerPrimitive.Close;
|
|
34
|
+
type DrawerContentProps = DrawerPrimitive.Popup.Props & {
|
|
35
|
+
/**
|
|
36
|
+
* Voile derrière le panneau. À couper pour un volet non modal, où la page
|
|
37
|
+
* doit rester lisible et cliquable.
|
|
38
|
+
*/
|
|
39
|
+
backdrop?: boolean;
|
|
40
|
+
};
|
|
41
|
+
declare function DrawerContent({ className, children, backdrop, ...props }: DrawerContentProps): React.JSX.Element;
|
|
42
|
+
declare function useDrawerSide(): DrawerSide;
|
|
43
|
+
/** La croix en haut à droite, même dessin que celle des boîtes de dialogue. */
|
|
44
|
+
declare function DrawerCloseButton({ className, ...props }: DrawerPrimitive.Close.Props): React.JSX.Element;
|
|
45
|
+
declare function DrawerHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>): React.JSX.Element;
|
|
46
|
+
declare function DrawerTitle({ className, ...props }: DrawerPrimitive.Title.Props): React.JSX.Element;
|
|
47
|
+
declare function DrawerDescription({ className, ...props }: DrawerPrimitive.Description.Props): React.JSX.Element;
|
|
48
|
+
/**
|
|
49
|
+
* La partie qui défile ; l'en-tête et le pied restent en place.
|
|
50
|
+
*
|
|
51
|
+
* `*:shrink-0` : posé en colonne flex (`flex flex-col`, le cas courant), un
|
|
52
|
+
* corps plus haut que la feuille ne défile pas, il comprime ses enfants. Un
|
|
53
|
+
* enfant `overflow-hidden` (le bandeau d'un plan) n'a plus de hauteur minimale
|
|
54
|
+
* et s'écrasait en une bande de quelques pixels sur téléphone.
|
|
55
|
+
*/
|
|
56
|
+
declare function DrawerBody({ className, ...props }: HTMLAttributes<HTMLDivElement>): React.JSX.Element;
|
|
57
|
+
declare function DrawerFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>): React.JSX.Element;
|
|
58
|
+
export { Drawer, DrawerTrigger, DrawerClose, DrawerCloseButton, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerBody, DrawerFooter, useDrawerSide, };
|
|
59
|
+
export type { DrawerSide };
|
package/dist/drawer.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
|
|
3
|
+
import Cancel01Icon from "@hugeicons/core-free-icons/Cancel01Icon";
|
|
4
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
5
|
+
import { createContext, useContext } from "react";
|
|
6
|
+
import { useIsMobile } from "./use-media-query.js";
|
|
7
|
+
import { cn } from "./cn.js";
|
|
8
|
+
const DrawerSideContext = createContext("bottom");
|
|
9
|
+
function Drawer({ side = "auto", children, ...props }) {
|
|
10
|
+
const isMobile = useIsMobile();
|
|
11
|
+
const resolved = side === "auto" ? (isMobile ? "bottom" : "right") : side;
|
|
12
|
+
return (_jsx(DrawerSideContext.Provider, { value: resolved, children: _jsx(DrawerPrimitive.Root, { swipeDirection: resolved === "bottom" ? "down" : "right", ...props, children: resolved === "bottom" && typeof children !== "function" ? (_jsx(DrawerPrimitive.VirtualKeyboardProvider, { children: children })) : (children) }) }));
|
|
13
|
+
}
|
|
14
|
+
const DrawerTrigger = DrawerPrimitive.Trigger;
|
|
15
|
+
const DrawerClose = DrawerPrimitive.Close;
|
|
16
|
+
const popupBase = "pointer-events-auto fixed z-50 flex flex-col bg-popover text-popover-foreground shadow-2xl shadow-black/10 outline-none transition-[translate] duration-(--duration-drawer) ease-ios data-swiping:duration-0 data-swiping:select-none";
|
|
17
|
+
const popupBySide = {
|
|
18
|
+
bottom: cn("inset-x-0 bottom-0 touch-none rounded-t-3xl",
|
|
19
|
+
// Tirée vers le haut, la feuille monte au-delà de son bord : le fond qui
|
|
20
|
+
// déborde (`--bleed`, compensé par la marge négative) comble le vide.
|
|
21
|
+
// Le clavier ouvert (`--drawer-keyboard-inset`, posé par
|
|
22
|
+
// `VirtualKeyboardProvider` sur le cadre) s'ajoute au pied : le contenu
|
|
23
|
+
// reste au-dessus du clavier au lieu de passer dessous.
|
|
24
|
+
"-mb-(--bleed) pb-[calc(env(safe-area-inset-bottom)+var(--bleed)+var(--drawer-keyboard-inset,0px))] [--bleed:3rem]", "translate-y-[calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y,0px))]", "data-ending-style:translate-y-[calc(100%-var(--bleed))] data-starting-style:translate-y-[calc(100%-var(--bleed))]",
|
|
25
|
+
// Lâchée d'un coup sec, elle part plus vite qu'un glissement lent.
|
|
26
|
+
"data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*var(--duration-drawer))]"),
|
|
27
|
+
right: cn("top-2 right-2 bottom-2 w-[calc(100%-1rem)] max-w-md rounded-3xl",
|
|
28
|
+
// Tirer vers la gauche ne fait rien : le volet est déjà contre son bord.
|
|
29
|
+
"translate-x-[max(0px,var(--drawer-swipe-movement-x,0px))]", "data-ending-style:translate-x-[calc(100%+0.5rem)] data-starting-style:translate-x-[calc(100%+0.5rem)]"),
|
|
30
|
+
};
|
|
31
|
+
function DrawerContent({ className, children, backdrop = true, ...props }) {
|
|
32
|
+
const side = useDrawerSide();
|
|
33
|
+
return (_jsxs(DrawerPrimitive.Portal, { children: [backdrop ? (_jsx(DrawerPrimitive.Backdrop, { className: "fixed inset-x-0 -inset-y-24 z-50 bg-black/30 opacity-[calc(1-var(--drawer-swipe-progress,0))] transition-opacity duration-(--duration-drawer) ease-ios data-ending-style:opacity-0 data-starting-style:opacity-0 data-swiping:duration-0 dark:bg-black/50" })) : null, _jsx(DrawerPrimitive.Viewport, { className: "pointer-events-none fixed inset-0 z-50", children: _jsx(DrawerPrimitive.Popup, { "data-slot": "drawer-content", className: cn(popupBase, popupBySide[side],
|
|
34
|
+
// La feuille prend la hauteur de son contenu, jusqu'en haut de
|
|
35
|
+
// l'écran (révision du 2026-10-01) : arrêtée aux deux tiers, un
|
|
36
|
+
// contenu long obligeait à défiler dedans. Clavier ouvert, elle
|
|
37
|
+
// monte d'office tout en haut : le multiplicateur fait passer la
|
|
38
|
+
// hauteur minimale de 0 à la hauteur maximale dès que le clavier
|
|
39
|
+
// a une hauteur, sans JavaScript.
|
|
40
|
+
// Tout l'écran, moins la barre d'état et un liseré qui laisse
|
|
41
|
+
// deviner la page derrière. Classes écrites en entier : Tailwind
|
|
42
|
+
// ne lit pas une classe composée.
|
|
43
|
+
side === "bottom" &&
|
|
44
|
+
"max-h-[calc(100svh-max(env(safe-area-inset-top),0.75rem)-0.5rem+var(--bleed))] min-h-[min(calc(var(--drawer-keyboard-inset,0px)*1000),calc(100svh-max(env(safe-area-inset-top),0.75rem)-0.5rem+var(--bleed)))]", className), ...props, children: _jsxs(DrawerPrimitive.Content, { className: "flex min-h-0 flex-1 flex-col", children: [side === "bottom" ? (_jsx("div", { "aria-hidden": true, className: "mx-auto mt-2.5 h-1 w-10 shrink-0 rounded-full bg-muted-foreground/30" })) : null, children] }) }) })] }));
|
|
45
|
+
}
|
|
46
|
+
function useDrawerSide() {
|
|
47
|
+
return useContext(DrawerSideContext);
|
|
48
|
+
}
|
|
49
|
+
/** La croix en haut à droite, même dessin que celle des boîtes de dialogue. */
|
|
50
|
+
function DrawerCloseButton({ className, ...props }) {
|
|
51
|
+
return (_jsx(DrawerPrimitive.Close, { "data-slot": "drawer-close", "aria-label": "Fermer", className: cn("touch-target absolute top-4 right-4 flex size-10 items-center justify-center rounded-full text-muted-foreground transition-colors outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring", className), ...props, children: _jsx(HugeiconsIcon, { icon: Cancel01Icon, strokeWidth: 2, className: "size-5" }) }));
|
|
52
|
+
}
|
|
53
|
+
function DrawerHeader({ className, ...props }) {
|
|
54
|
+
const side = useDrawerSide();
|
|
55
|
+
return (_jsx("div", { "data-slot": "drawer-header", className: cn("flex shrink-0 flex-col gap-1.5 px-6 pr-14 pb-3", side === "bottom" ? "pt-3" : "pt-5", className), ...props }));
|
|
56
|
+
}
|
|
57
|
+
function DrawerTitle({ className, ...props }) {
|
|
58
|
+
return (_jsx(DrawerPrimitive.Title, { "data-slot": "drawer-title", className: cn("font-heading text-lg font-medium tracking-tight", className), ...props }));
|
|
59
|
+
}
|
|
60
|
+
function DrawerDescription({ className, ...props }) {
|
|
61
|
+
return (_jsx(DrawerPrimitive.Description, { "data-slot": "drawer-description", className: cn("text-sm text-muted-foreground", className), ...props }));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* La partie qui défile ; l'en-tête et le pied restent en place.
|
|
65
|
+
*
|
|
66
|
+
* `*:shrink-0` : posé en colonne flex (`flex flex-col`, le cas courant), un
|
|
67
|
+
* corps plus haut que la feuille ne défile pas, il comprime ses enfants. Un
|
|
68
|
+
* enfant `overflow-hidden` (le bandeau d'un plan) n'a plus de hauteur minimale
|
|
69
|
+
* et s'écrasait en une bande de quelques pixels sur téléphone.
|
|
70
|
+
*/
|
|
71
|
+
function DrawerBody({ className, ...props }) {
|
|
72
|
+
return (_jsx("div", { "data-slot": "drawer-body", className: cn("min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-2 *:shrink-0", className), ...props }));
|
|
73
|
+
}
|
|
74
|
+
function DrawerFooter({ className, ...props }) {
|
|
75
|
+
return (_jsx("div", { "data-slot": "drawer-footer", className: cn("flex shrink-0 flex-col-reverse gap-2 px-6 pt-3 pb-5 sm:flex-row sm:justify-end", className), ...props }));
|
|
76
|
+
}
|
|
77
|
+
export { Drawer, DrawerTrigger, DrawerClose, DrawerCloseButton, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerBody, DrawerFooter, useDrawerSide, };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
declare function DropdownMenu({ ...props }: MenuPrimitive.Root.Props): React.JSX.Element;
|
|
4
|
+
declare function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props): React.JSX.Element;
|
|
5
|
+
declare function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props): React.JSX.Element;
|
|
6
|
+
declare function DropdownMenuContent({ align, alignOffset, side, sideOffset, collisionPadding, collisionAvoidance, className, ...props }: MenuPrimitive.Popup.Props & Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" | "collisionPadding" | "collisionAvoidance">): React.JSX.Element;
|
|
7
|
+
declare function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props): React.JSX.Element;
|
|
8
|
+
declare function DropdownMenuLabel({ className, inset, ...props }: MenuPrimitive.GroupLabel.Props & {
|
|
9
|
+
inset?: boolean;
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
declare function DropdownMenuItem({ className, inset, variant, ...props }: MenuPrimitive.Item.Props & {
|
|
12
|
+
inset?: boolean;
|
|
13
|
+
variant?: "default" | "destructive";
|
|
14
|
+
}): React.JSX.Element;
|
|
15
|
+
declare function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props): React.JSX.Element;
|
|
16
|
+
declare function DropdownMenuSubTrigger({ className, inset, children, ...props }: MenuPrimitive.SubmenuTrigger.Props & {
|
|
17
|
+
inset?: boolean;
|
|
18
|
+
}): React.JSX.Element;
|
|
19
|
+
declare function DropdownMenuSubContent({ align, alignOffset, side, sideOffset, className, ...props }: React.ComponentProps<typeof DropdownMenuContent>): React.JSX.Element;
|
|
20
|
+
declare function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }: MenuPrimitive.CheckboxItem.Props & {
|
|
21
|
+
inset?: boolean;
|
|
22
|
+
}): React.JSX.Element;
|
|
23
|
+
/**
|
|
24
|
+
* `label` titre le groupe. `Menu.RadioGroup` ne pose pas le contexte de
|
|
25
|
+
* `Menu.Group` : un `DropdownMenuLabel` glissé dedans lève l'erreur Base UI
|
|
26
|
+
* #31 et fait tomber le menu. Le titre passe donc par ici, jamais en enfant.
|
|
27
|
+
*/
|
|
28
|
+
declare function DropdownMenuRadioGroup({ label, ...props }: MenuPrimitive.RadioGroup.Props & {
|
|
29
|
+
label?: React.ReactNode;
|
|
30
|
+
}): React.JSX.Element;
|
|
31
|
+
declare function DropdownMenuRadioItem({ className, children, inset, ...props }: MenuPrimitive.RadioItem.Props & {
|
|
32
|
+
inset?: boolean;
|
|
33
|
+
}): React.JSX.Element;
|
|
34
|
+
declare function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props): React.JSX.Element;
|
|
35
|
+
declare function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
|
|
36
|
+
export { DropdownMenu, DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
3
|
+
import ArrowRight01Icon from "@hugeicons/core-free-icons/ArrowRight01Icon";
|
|
4
|
+
import Tick02Icon from "@hugeicons/core-free-icons/Tick02Icon";
|
|
5
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { FLOATING_COLLISION_AVOIDANCE, FLOATING_COLLISION_PADDING, FLOATING_MAX_WIDTH, FLOATING_SUBMENU_COLLISION_AVOIDANCE, FLOATING_SURFACE, } from "./floating.js";
|
|
8
|
+
import { cn } from "./cn.js";
|
|
9
|
+
function DropdownMenu({ ...props }) {
|
|
10
|
+
return _jsx(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
|
|
11
|
+
}
|
|
12
|
+
function DropdownMenuPortal({ ...props }) {
|
|
13
|
+
return _jsx(MenuPrimitive.Portal, { "data-slot": "dropdown-menu-portal", ...props });
|
|
14
|
+
}
|
|
15
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
16
|
+
return _jsx(MenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
17
|
+
}
|
|
18
|
+
function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4,
|
|
19
|
+
// Marge minimale aux bords de l'écran. Un menu ouvert depuis un élément collé
|
|
20
|
+
// au bord — l'action d'un message, celle d'une ligne de tableau — s'y appuyait
|
|
21
|
+
// sans respiration, quand il ne débordait pas.
|
|
22
|
+
collisionPadding = FLOATING_COLLISION_PADDING, collisionAvoidance = FLOATING_COLLISION_AVOIDANCE, className, ...props }) {
|
|
23
|
+
return (_jsx(MenuPrimitive.Portal, { children: _jsx(MenuPrimitive.Positioner, { className: "isolate z-50 outline-none", align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, collisionPadding: collisionPadding, collisionAvoidance: collisionAvoidance, children: _jsx(MenuPrimitive.Popup, { "data-slot": "dropdown-menu-content", className: cn(FLOATING_MAX_WIDTH,
|
|
24
|
+
// Même surface que Select et Combobox : opaque, ombre douce,
|
|
25
|
+
// pas d'anneau. Entrée en 150 ms depuis l'ancre (scale 0.98 → 1),
|
|
26
|
+
// sortie en 100 ms ; des transitions, pas des keyframes, pour
|
|
27
|
+
// qu'un ouvrir-fermer rapide reparte de la position courante.
|
|
28
|
+
FLOATING_SURFACE, "relative z-50 max-h-(--available-height) w-(--anchor-width) min-w-48 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-2xl p-1.5 transition-[scale,opacity] duration-(--duration-fast) ease-ios outline-none data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-ending-style:duration-(--duration-instant) data-starting-style:scale-[0.98] data-starting-style:opacity-0 **:data-[slot$=-item]:focus:bg-muted **:data-[slot$=-item]:data-highlighted:bg-muted **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-muted **:data-[slot$=-trigger]:aria-expanded:bg-muted! **:data-[variant=destructive]:focus:bg-destructive/10!", className), ...props }) }) }));
|
|
29
|
+
}
|
|
30
|
+
function DropdownMenuGroup({ ...props }) {
|
|
31
|
+
return _jsx(MenuPrimitive.Group, { "data-slot": "dropdown-menu-group", ...props });
|
|
32
|
+
}
|
|
33
|
+
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
34
|
+
return (_jsx(MenuPrimitive.GroupLabel, { "data-slot": "dropdown-menu-label", "data-inset": inset, className: cn("px-3 py-2.5 text-xs text-muted-foreground data-inset:pl-9.5", className), ...props }));
|
|
35
|
+
}
|
|
36
|
+
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
37
|
+
return (_jsx(MenuPrimitive.Item, { "data-slot": "dropdown-menu-item", "data-inset": inset, "data-variant": variant, className: cn("group/dropdown-menu-item relative flex cursor-default items-center gap-2.5 rounded-xl px-3 py-2 text-sm font-medium outline-hidden select-none focus:bg-muted focus:text-foreground data-inset:pl-9.5 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive max-lg:min-h-11 dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive", className), ...props }));
|
|
38
|
+
}
|
|
39
|
+
function DropdownMenuSub({ ...props }) {
|
|
40
|
+
return _jsx(MenuPrimitive.SubmenuRoot, { "data-slot": "dropdown-menu-sub", ...props });
|
|
41
|
+
}
|
|
42
|
+
function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
|
|
43
|
+
return (_jsxs(MenuPrimitive.SubmenuTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset, className: cn("flex cursor-default items-center gap-2 rounded-xl px-3 py-2 text-sm font-medium outline-hidden select-none focus:bg-muted focus:text-foreground data-inset:pl-9.5 data-popup-open:bg-muted data-popup-open:text-foreground data-open:bg-muted data-open:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [children, _jsx(HugeiconsIcon, { icon: ArrowRight01Icon, strokeWidth: 2, className: "ml-auto" })] }));
|
|
44
|
+
}
|
|
45
|
+
function DropdownMenuSubContent({ align = "start", alignOffset = -3, side = "right", sideOffset = 0, className, ...props }) {
|
|
46
|
+
return (_jsx(DropdownMenuContent, { "data-slot": "dropdown-menu-sub-content", collisionAvoidance: FLOATING_SUBMENU_COLLISION_AVOIDANCE, className: cn("relative w-auto min-w-36 rounded-2xl p-1.5 **:data-[slot$=-item]:focus:bg-muted **:data-[slot$=-item]:data-highlighted:bg-muted **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-muted **:data-[slot$=-trigger]:aria-expanded:bg-muted! **:data-[variant=destructive]:focus:bg-destructive/10!", className), align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, ...props }));
|
|
47
|
+
}
|
|
48
|
+
function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }) {
|
|
49
|
+
return (_jsxs(MenuPrimitive.CheckboxItem, { "data-slot": "dropdown-menu-checkbox-item", "data-inset": inset, className: cn("relative flex cursor-default items-center gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm font-medium outline-hidden select-none focus:bg-muted focus:text-foreground data-inset:pl-9.5 max-lg:min-h-11 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), checked: checked, ...props, children: [_jsx("span", { className: "pointer-events-none absolute right-2 flex items-center justify-center", "data-slot": "dropdown-menu-checkbox-item-indicator", children: _jsx(MenuPrimitive.CheckboxItemIndicator, { children: _jsx(HugeiconsIcon, { icon: Tick02Icon, strokeWidth: 2 }) }) }), children] }));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `label` titre le groupe. `Menu.RadioGroup` ne pose pas le contexte de
|
|
53
|
+
* `Menu.Group` : un `DropdownMenuLabel` glissé dedans lève l'erreur Base UI
|
|
54
|
+
* #31 et fait tomber le menu. Le titre passe donc par ici, jamais en enfant.
|
|
55
|
+
*/
|
|
56
|
+
function DropdownMenuRadioGroup({ label, ...props }) {
|
|
57
|
+
const group = _jsx(MenuPrimitive.RadioGroup, { "data-slot": "dropdown-menu-radio-group", ...props });
|
|
58
|
+
if (label === undefined)
|
|
59
|
+
return group;
|
|
60
|
+
return (_jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: label }), group] }));
|
|
61
|
+
}
|
|
62
|
+
function DropdownMenuRadioItem({ className, children, inset, ...props }) {
|
|
63
|
+
return (_jsxs(MenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", "data-inset": inset, className: cn("relative flex cursor-default items-center gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm font-medium outline-hidden select-none focus:bg-muted focus:text-foreground data-inset:pl-9.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute right-2 flex items-center justify-center", "data-slot": "dropdown-menu-radio-item-indicator", children: _jsx(MenuPrimitive.RadioItemIndicator, { children: _jsx(HugeiconsIcon, { icon: Tick02Icon, strokeWidth: 2 }) }) }), children] }));
|
|
64
|
+
}
|
|
65
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
66
|
+
return (_jsx(MenuPrimitive.Separator, { "data-slot": "dropdown-menu-separator", className: cn("-mx-1.5 my-1.5 h-px bg-border/50", className), ...props }));
|
|
67
|
+
}
|
|
68
|
+
function DropdownMenuShortcut({ className, ...props }) {
|
|
69
|
+
return (_jsx("span", { "data-slot": "dropdown-menu-shortcut", className: cn("ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-foreground", className), ...props }));
|
|
70
|
+
}
|
|
71
|
+
export { DropdownMenu, DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, };
|
package/dist/empty.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type VariantProps } from "class-variance-authority";
|
|
2
|
+
import { type Variants } from "./cn.ts";
|
|
3
|
+
declare function Empty({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
4
|
+
declare function EmptyHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
5
|
+
declare const emptyMediaVariants: Variants<{
|
|
6
|
+
variant: "default" | "icon";
|
|
7
|
+
}>;
|
|
8
|
+
declare function EmptyMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>): React.JSX.Element;
|
|
9
|
+
declare function EmptyTitle({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
10
|
+
declare function EmptyDescription({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element;
|
|
11
|
+
declare function EmptyContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
12
|
+
export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia };
|