@graundtech/fluent2-react-kit 0.5.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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/components/ui/accordion.d.ts +133 -0
- package/dist/components/ui/accordion.js +80 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/alert.d.ts +106 -0
- package/dist/components/ui/alert.js +73 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +42 -0
- package/dist/components/ui/avatar.js +55 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +36 -0
- package/dist/components/ui/badge.js +81 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +53 -0
- package/dist/components/ui/breadcrumb.js +149 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +24 -0
- package/dist/components/ui/button.js +86 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/card.d.ts +51 -0
- package/dist/components/ui/card.js +97 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +101 -0
- package/dist/components/ui/checkbox.js +57 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +212 -0
- package/dist/components/ui/combobox.js +236 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/command.d.ts +187 -0
- package/dist/components/ui/command.js +225 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +117 -0
- package/dist/components/ui/dialog.js +165 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +155 -0
- package/dist/components/ui/dropdown-menu.js +295 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/input.d.ts +44 -0
- package/dist/components/ui/input.js +43 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +39 -0
- package/dist/components/ui/label.js +30 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/link.d.ts +62 -0
- package/dist/components/ui/link.js +61 -0
- package/dist/components/ui/link.js.map +1 -0
- package/dist/components/ui/multi-select.d.ts +115 -0
- package/dist/components/ui/multi-select.js +133 -0
- package/dist/components/ui/multi-select.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +46 -0
- package/dist/components/ui/pagination.js +164 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +117 -0
- package/dist/components/ui/popover.js +62 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +98 -0
- package/dist/components/ui/progress.js +67 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +94 -0
- package/dist/components/ui/radio-group.js +65 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/select.d.ts +145 -0
- package/dist/components/ui/select.js +240 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +43 -0
- package/dist/components/ui/separator.js +27 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +81 -0
- package/dist/components/ui/skeleton.js +24 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +56 -0
- package/dist/components/ui/spinner.js +83 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/switch.d.ts +60 -0
- package/dist/components/ui/switch.js +63 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +107 -0
- package/dist/components/ui/tabs.js +102 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +28 -0
- package/dist/components/ui/textarea.js +36 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/toast.d.ts +185 -0
- package/dist/components/ui/toast.js +181 -0
- package/dist/components/ui/toast.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +115 -0
- package/dist/components/ui/tooltip.js +72 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +284 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +9 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +77 -0
- package/src/styles/tokens.css +596 -0
- package/src/styles/tokens.test.ts +216 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
4
|
+
import { VariantProps } from 'class-variance-authority';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Badge — Fluent 2-styled, shadcn-API badge.
|
|
8
|
+
*
|
|
9
|
+
* Aesthetics follow Fluent 2 (a 4px-radius chip — Fluent's fixed `medium`
|
|
10
|
+
* corner token, non-scaling, NOT a full pill — compact 20px height, 10px/600
|
|
11
|
+
* caption-2 label with 4px horizontal padding, flat filled surfaces, brand/
|
|
12
|
+
* status color tokens); the prop surface (`variant`, `asChild`,
|
|
13
|
+
* `badgeVariants`) follows shadcn/ui.
|
|
14
|
+
*
|
|
15
|
+
* Geometry is spec-exact: `rounded-md` (`--radius-md` = 4px, the Fluent badge
|
|
16
|
+
* corner token used at every badge size), `px-1` (4px = `spacingHorizontalXS`),
|
|
17
|
+
* `text-[10px]/[14px]` (Fluent "Caption 2 Strong", == the `--text-caption2`
|
|
18
|
+
* token value — see the layout comment for why the arbitrary length is used
|
|
19
|
+
* over the `text-caption2` utility) + `font-semibold` (600), fixed `h-5`
|
|
20
|
+
* (20px Medium).
|
|
21
|
+
*
|
|
22
|
+
* Server-safe: no `"use client"`, no hooks/handlers — the React import is
|
|
23
|
+
* type-only, so the file can be dropped straight into an RSC tree.
|
|
24
|
+
*
|
|
25
|
+
* Hover/active state steps only engage when the badge is rendered as an
|
|
26
|
+
* interactive element via `asChild` (`[a&]:` targets the case where the
|
|
27
|
+
* rendered tag is an `<a>`); a plain status label stays static.
|
|
28
|
+
*/
|
|
29
|
+
declare const badgeVariants: (props?: ({
|
|
30
|
+
variant?: "default" | "destructive" | "success" | "warning" | "secondary" | "outline" | null | undefined;
|
|
31
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
32
|
+
declare function Badge({ className, variant, asChild, ...props }: ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
|
|
33
|
+
asChild?: boolean;
|
|
34
|
+
}): react.JSX.Element;
|
|
35
|
+
|
|
36
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
[
|
|
7
|
+
// layout — Fluent 4px chip, compact 20px height, centered caption-2 label
|
|
8
|
+
"inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 whitespace-nowrap",
|
|
9
|
+
// Fluent "Caption 2 Strong" = 10px/14px, weight 600. Expressed as the
|
|
10
|
+
// arbitrary `text-[10px]/[14px]` rather than the `text-caption2` token
|
|
11
|
+
// utility on purpose: every variant also sets a `text-*` color, and
|
|
12
|
+
// tailwind-merge (unaware of the custom `caption2` font-size) treats
|
|
13
|
+
// `text-caption2` as a text-color and drops it behind the variant color;
|
|
14
|
+
// the `--text-caption2` var also lives in `@theme inline`, which isn't
|
|
15
|
+
// emitted to :root, so a `text-(length:--text-caption2)` reference would be
|
|
16
|
+
// undefined at runtime. The arbitrary length is twMerge-safe and resolves
|
|
17
|
+
// with no runtime var. Values equal the `--text-caption2` token exactly.
|
|
18
|
+
"rounded-md border px-1 text-[10px]/[14px] font-semibold select-none",
|
|
19
|
+
// motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion
|
|
20
|
+
// automatically (the duration tokens collapse to 0.01ms in tokens.css).
|
|
21
|
+
"outline-none transition-colors duration-fast ease-ease",
|
|
22
|
+
// focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap.
|
|
23
|
+
// Only reachable when the badge is an interactive `asChild` element.
|
|
24
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
25
|
+
// invalid — shadcn aria-invalid treatment
|
|
26
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
27
|
+
// icon defaults
|
|
28
|
+
"[&>svg]:pointer-events-none [&>svg]:size-3"
|
|
29
|
+
],
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
variant: {
|
|
33
|
+
// Fluent brand filled — flat, full brand state ramp on `asChild` links
|
|
34
|
+
// (spec §2.5): rest brand-80 → hover brand-70 → pressed brand-60 (light);
|
|
35
|
+
// rest brand-70 → hover brand-80 → pressed brand-60 (dark).
|
|
36
|
+
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-brand-70 [a&]:active:bg-brand-60 dark:[a&]:hover:bg-brand-80",
|
|
37
|
+
// Fluent standard/neutral — filled neutral surface + subtle border.
|
|
38
|
+
secondary: "border-border bg-secondary text-secondary-foreground [a&]:hover:bg-accent [a&]:active:bg-input dark:[a&]:hover:bg-input",
|
|
39
|
+
// Fluent danger — red fill. No red ramp is exposed, so hover/pressed
|
|
40
|
+
// step via opacity; the focus ring switches to destructive.
|
|
41
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90 [a&]:active:bg-destructive/80 focus-visible:ring-destructive",
|
|
42
|
+
// Fluent outline — transparent + border, neutral hover.
|
|
43
|
+
outline: "border-border bg-transparent text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground [a&]:active:bg-accent/80 dark:[a&]:hover:bg-input/50",
|
|
44
|
+
// Fluent success — green fill, AA-safe foreground from the status token pair.
|
|
45
|
+
success: "border-transparent bg-success text-success-foreground [a&]:hover:bg-success/90 [a&]:active:bg-success/80",
|
|
46
|
+
// Fluent warning badge — bright orange chip (`#f7630c`) with DARK
|
|
47
|
+
// static text (`#242424`), the opposite foreground polarity from
|
|
48
|
+
// --warning's white text. Uses the dedicated `--warning-badge` pair
|
|
49
|
+
// (~4.98:1 AA), matching Figma's WarningBackground3 + NeutralForeground1
|
|
50
|
+
// Static, so Alert's darkOrange --warning stays put (single-source
|
|
51
|
+
// decoupling; see tokens.css + tokens-research.md §12.9).
|
|
52
|
+
warning: "border-transparent bg-warning-badge text-warning-badge-foreground [a&]:hover:bg-warning-badge/90 [a&]:active:bg-warning-badge/80"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
defaultVariants: {
|
|
56
|
+
variant: "default"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
function Badge({
|
|
61
|
+
className,
|
|
62
|
+
variant = "default",
|
|
63
|
+
asChild = false,
|
|
64
|
+
...props
|
|
65
|
+
}) {
|
|
66
|
+
const Comp = asChild ? Slot : "span";
|
|
67
|
+
return /* @__PURE__ */ jsx(
|
|
68
|
+
Comp,
|
|
69
|
+
{
|
|
70
|
+
"data-slot": "badge",
|
|
71
|
+
"data-variant": variant,
|
|
72
|
+
className: cn(badgeVariants({ variant, className })),
|
|
73
|
+
...props
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
Badge,
|
|
79
|
+
badgeVariants
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/badge.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Badge — Fluent 2-styled, shadcn-API badge.\n *\n * Aesthetics follow Fluent 2 (a 4px-radius chip — Fluent's fixed `medium`\n * corner token, non-scaling, NOT a full pill — compact 20px height, 10px/600\n * caption-2 label with 4px horizontal padding, flat filled surfaces, brand/\n * status color tokens); the prop surface (`variant`, `asChild`,\n * `badgeVariants`) follows shadcn/ui.\n *\n * Geometry is spec-exact: `rounded-md` (`--radius-md` = 4px, the Fluent badge\n * corner token used at every badge size), `px-1` (4px = `spacingHorizontalXS`),\n * `text-[10px]/[14px]` (Fluent \"Caption 2 Strong\", == the `--text-caption2`\n * token value — see the layout comment for why the arbitrary length is used\n * over the `text-caption2` utility) + `font-semibold` (600), fixed `h-5`\n * (20px Medium).\n *\n * Server-safe: no `\"use client\"`, no hooks/handlers — the React import is\n * type-only, so the file can be dropped straight into an RSC tree.\n *\n * Hover/active state steps only engage when the badge is rendered as an\n * interactive element via `asChild` (`[a&]:` targets the case where the\n * rendered tag is an `<a>`); a plain status label stays static.\n */\nconst badgeVariants = cva(\n [\n // layout — Fluent 4px chip, compact 20px height, centered caption-2 label\n \"inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 whitespace-nowrap\",\n // Fluent \"Caption 2 Strong\" = 10px/14px, weight 600. Expressed as the\n // arbitrary `text-[10px]/[14px]` rather than the `text-caption2` token\n // utility on purpose: every variant also sets a `text-*` color, and\n // tailwind-merge (unaware of the custom `caption2` font-size) treats\n // `text-caption2` as a text-color and drops it behind the variant color;\n // the `--text-caption2` var also lives in `@theme inline`, which isn't\n // emitted to :root, so a `text-(length:--text-caption2)` reference would be\n // undefined at runtime. The arbitrary length is twMerge-safe and resolves\n // with no runtime var. Values equal the `--text-caption2` token exactly.\n \"rounded-md border px-1 text-[10px]/[14px] font-semibold select-none\",\n // motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion\n // automatically (the duration tokens collapse to 0.01ms in tokens.css).\n \"outline-none transition-colors duration-fast ease-ease\",\n // focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap.\n // Only reachable when the badge is an interactive `asChild` element.\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n // invalid — shadcn aria-invalid treatment\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n // icon defaults\n \"[&>svg]:pointer-events-none [&>svg]:size-3\",\n ],\n {\n variants: {\n variant: {\n // Fluent brand filled — flat, full brand state ramp on `asChild` links\n // (spec §2.5): rest brand-80 → hover brand-70 → pressed brand-60 (light);\n // rest brand-70 → hover brand-80 → pressed brand-60 (dark).\n default:\n \"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-brand-70 [a&]:active:bg-brand-60 dark:[a&]:hover:bg-brand-80\",\n // Fluent standard/neutral — filled neutral surface + subtle border.\n secondary:\n \"border-border bg-secondary text-secondary-foreground [a&]:hover:bg-accent [a&]:active:bg-input dark:[a&]:hover:bg-input\",\n // Fluent danger — red fill. No red ramp is exposed, so hover/pressed\n // step via opacity; the focus ring switches to destructive.\n destructive:\n \"border-transparent bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90 [a&]:active:bg-destructive/80 focus-visible:ring-destructive\",\n // Fluent outline — transparent + border, neutral hover.\n outline:\n \"border-border bg-transparent text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground [a&]:active:bg-accent/80 dark:[a&]:hover:bg-input/50\",\n // Fluent success — green fill, AA-safe foreground from the status token pair.\n success:\n \"border-transparent bg-success text-success-foreground [a&]:hover:bg-success/90 [a&]:active:bg-success/80\",\n // Fluent warning badge — bright orange chip (`#f7630c`) with DARK\n // static text (`#242424`), the opposite foreground polarity from\n // --warning's white text. Uses the dedicated `--warning-badge` pair\n // (~4.98:1 AA), matching Figma's WarningBackground3 + NeutralForeground1\n // Static, so Alert's darkOrange --warning stays put (single-source\n // decoupling; see tokens.css + tokens-research.md §12.9).\n warning:\n \"border-transparent bg-warning-badge text-warning-badge-foreground [a&]:hover:bg-warning-badge/90 [a&]:active:bg-warning-badge/80\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\nfunction Badge({\n className,\n variant = \"default\",\n asChild = false,\n ...props\n}: ComponentProps<\"span\"> &\n VariantProps<typeof badgeVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot : \"span\";\n\n return (\n <Comp\n data-slot=\"badge\"\n data-variant={variant}\n className={cn(badgeVariants({ variant, className }))}\n {...props}\n />\n );\n}\n\nexport { Badge, badgeVariants };\n"],"mappings":"AAuGI;AAvGJ,SAAS,YAAY;AACrB,SAAS,WAA8B;AAGvC,SAAS,UAAU;AAyBnB,MAAM,gBAAgB;AAAA,EACpB;AAAA;AAAA,IAEE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA;AAAA;AAAA;AAAA,QAIP,SACE;AAAA;AAAA,QAEF,WACE;AAAA;AAAA;AAAA,QAGF,aACE;AAAA;AAAA,QAEF,SACE;AAAA;AAAA,QAEF,SACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOF,SACE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEA,SAAS,MAAM;AAAA,EACb;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,GAAG;AACL,GAGK;AACH,QAAM,OAAO,UAAU,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,cAAc,EAAE,SAAS,UAAU,CAAC,CAAC;AAAA,MAClD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Breadcrumb — Fluent 2-styled, shadcn-API breadcrumb family.
|
|
6
|
+
*
|
|
7
|
+
* Pure markup, matching shadcn/ui's Breadcrumb structure exactly: `Breadcrumb`
|
|
8
|
+
* (`<nav aria-label="breadcrumb">`), `BreadcrumbList` (`<ol>`), `BreadcrumbItem`
|
|
9
|
+
* (`<li>`), `BreadcrumbLink` (`<a>`, `asChild`-capable via Radix `Slot`),
|
|
10
|
+
* `BreadcrumbPage` (`<span role="link" aria-current="page">` for the
|
|
11
|
+
* non-interactive current step), `BreadcrumbSeparator` (`<li
|
|
12
|
+
* role="presentation" aria-hidden="true">`) and `BreadcrumbEllipsis` (same
|
|
13
|
+
* presentation pattern, for a collapsed run of items) —
|
|
14
|
+
* https://ui.shadcn.com/docs/components/breadcrumb.
|
|
15
|
+
*
|
|
16
|
+
* Two deliberate divergences from shadcn, both driven by this kit's
|
|
17
|
+
* conventions doc (§9):
|
|
18
|
+
*
|
|
19
|
+
* 1. `BreadcrumbSeparator`'s default glyph and `BreadcrumbEllipsis`'s dots are
|
|
20
|
+
* inline `<svg>`, not lucide's `ChevronRight`/`MoreHorizontal` (shadcn) or
|
|
21
|
+
* `@fluentui/react-icons` (this kit's usual icon source). Importing from
|
|
22
|
+
* `@fluentui/react-icons` forces `"use client"` onto the whole file (its
|
|
23
|
+
* icon sizing module calls a client-only Griffel API at module scope — see
|
|
24
|
+
* conventions §9 / `select.tsx`), which would needlessly turn this static
|
|
25
|
+
* markup component into a client boundary. An inline SVG keeps `Breadcrumb`
|
|
26
|
+
* a Server Component, matching `Card`/`Separator`.
|
|
27
|
+
* 2. `BreadcrumbLink`'s hover treatment intentionally mirrors this kit's own
|
|
28
|
+
* `Link` component's `default` variant (`hover:text-foreground`, color
|
|
29
|
+
* transition only, underline only on hover via the surrounding
|
|
30
|
+
* `hover:underline` here is *not* applied — shadcn's breadcrumb link has no
|
|
31
|
+
* underline at all, even on hover, since it sits in a compact trail rather
|
|
32
|
+
* than prose) rather than inventing a new recipe: `hover:text-foreground
|
|
33
|
+
* transition-colors duration-fast ease-ease` plus the standard
|
|
34
|
+
* focus-visible ring (conventions §4). Kept text-only (no underline) to
|
|
35
|
+
* match shadcn's breadcrumb parity goal; reach for `Link` directly if a
|
|
36
|
+
* consumer wants the underlined treatment.
|
|
37
|
+
*
|
|
38
|
+
* No `variant`/`size` props and no `cva` table — shadcn's Breadcrumb ships
|
|
39
|
+
* none, so every part is a plain function component (matches `Card`).
|
|
40
|
+
*
|
|
41
|
+
* Server-safe: no `"use client"`, no hooks — the React import is type-only.
|
|
42
|
+
*/
|
|
43
|
+
declare function Breadcrumb({ className, ...props }: ComponentProps<"nav">): react.JSX.Element;
|
|
44
|
+
declare function BreadcrumbList({ className, ...props }: ComponentProps<"ol">): react.JSX.Element;
|
|
45
|
+
declare function BreadcrumbItem({ className, ...props }: ComponentProps<"li">): react.JSX.Element;
|
|
46
|
+
declare function BreadcrumbLink({ asChild, className, ...props }: ComponentProps<"a"> & {
|
|
47
|
+
asChild?: boolean;
|
|
48
|
+
}): react.JSX.Element;
|
|
49
|
+
declare function BreadcrumbPage({ className, ...props }: ComponentProps<"span">): react.JSX.Element;
|
|
50
|
+
declare function BreadcrumbSeparator({ children, className, ...props }: ComponentProps<"li">): react.JSX.Element;
|
|
51
|
+
declare function BreadcrumbEllipsis({ className, ...props }: ComponentProps<"span">): react.JSX.Element;
|
|
52
|
+
|
|
53
|
+
export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
function Breadcrumb({ className, ...props }) {
|
|
5
|
+
return /* @__PURE__ */ jsx(
|
|
6
|
+
"nav",
|
|
7
|
+
{
|
|
8
|
+
"aria-label": "breadcrumb",
|
|
9
|
+
"data-slot": "breadcrumb",
|
|
10
|
+
className: cn(className),
|
|
11
|
+
...props
|
|
12
|
+
}
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function BreadcrumbList({ className, ...props }) {
|
|
16
|
+
return /* @__PURE__ */ jsx(
|
|
17
|
+
"ol",
|
|
18
|
+
{
|
|
19
|
+
"data-slot": "breadcrumb-list",
|
|
20
|
+
className: cn(
|
|
21
|
+
// Trail text is Fluent NeutralForeground2 (#424242), one grey step
|
|
22
|
+
// darker than muted-foreground (Figma validation pass 2, 9077:5763).
|
|
23
|
+
"flex flex-wrap items-center gap-1.5 text-sm text-foreground-2 sm:gap-2.5",
|
|
24
|
+
className
|
|
25
|
+
),
|
|
26
|
+
...props
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
function BreadcrumbItem({ className, ...props }) {
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
"li",
|
|
33
|
+
{
|
|
34
|
+
"data-slot": "breadcrumb-item",
|
|
35
|
+
className: cn("inline-flex items-center gap-1.5", className),
|
|
36
|
+
...props
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
function BreadcrumbLink({
|
|
41
|
+
asChild = false,
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}) {
|
|
45
|
+
const Comp = asChild ? Slot : "a";
|
|
46
|
+
return /* @__PURE__ */ jsx(
|
|
47
|
+
Comp,
|
|
48
|
+
{
|
|
49
|
+
"data-slot": "breadcrumb-link",
|
|
50
|
+
className: cn(
|
|
51
|
+
// Fluent breadcrumb items are button-like pills — SubtleBackground
|
|
52
|
+
// hover/pressed fills on a 4px radius (the Figma item symbols are
|
|
53
|
+
// literally Type=Button; validation pass 2, node 9077:5740). The fill
|
|
54
|
+
// uses the kit's standard subtle-hover pair (--accent), the same
|
|
55
|
+
// one-grey-step drift already accepted for Select/Menu highlights.
|
|
56
|
+
"rounded-md px-1.5 py-0.5 outline-none transition-colors duration-fast ease-ease hover:bg-accent hover:text-foreground active:bg-accent/80",
|
|
57
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
58
|
+
className
|
|
59
|
+
),
|
|
60
|
+
...props
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function BreadcrumbPage({ className, ...props }) {
|
|
65
|
+
return /* @__PURE__ */ jsx(
|
|
66
|
+
"span",
|
|
67
|
+
{
|
|
68
|
+
"data-slot": "breadcrumb-page",
|
|
69
|
+
role: "link",
|
|
70
|
+
"aria-disabled": "true",
|
|
71
|
+
"aria-current": "page",
|
|
72
|
+
className: cn("px-1.5 py-0.5 font-semibold", className),
|
|
73
|
+
...props
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function BreadcrumbSeparator({
|
|
78
|
+
children,
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}) {
|
|
82
|
+
return /* @__PURE__ */ jsx(
|
|
83
|
+
"li",
|
|
84
|
+
{
|
|
85
|
+
"data-slot": "breadcrumb-separator",
|
|
86
|
+
role: "presentation",
|
|
87
|
+
"aria-hidden": "true",
|
|
88
|
+
className: cn("[&>svg]:size-3.5", className),
|
|
89
|
+
...props,
|
|
90
|
+
children: children ?? /* @__PURE__ */ jsx(
|
|
91
|
+
"svg",
|
|
92
|
+
{
|
|
93
|
+
viewBox: "0 0 16 16",
|
|
94
|
+
fill: "none",
|
|
95
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
96
|
+
children: /* @__PURE__ */ jsx(
|
|
97
|
+
"path",
|
|
98
|
+
{
|
|
99
|
+
d: "M6 3.5L10 8L6 12.5",
|
|
100
|
+
stroke: "currentColor",
|
|
101
|
+
strokeWidth: "1.5",
|
|
102
|
+
strokeLinecap: "round",
|
|
103
|
+
strokeLinejoin: "round"
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function BreadcrumbEllipsis({ className, ...props }) {
|
|
112
|
+
return /* @__PURE__ */ jsxs(
|
|
113
|
+
"span",
|
|
114
|
+
{
|
|
115
|
+
"data-slot": "breadcrumb-ellipsis",
|
|
116
|
+
role: "presentation",
|
|
117
|
+
"aria-hidden": "true",
|
|
118
|
+
className: cn("flex size-6 items-center justify-center", className),
|
|
119
|
+
...props,
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsxs(
|
|
122
|
+
"svg",
|
|
123
|
+
{
|
|
124
|
+
viewBox: "0 0 16 16",
|
|
125
|
+
fill: "none",
|
|
126
|
+
className: "size-4",
|
|
127
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
128
|
+
children: [
|
|
129
|
+
/* @__PURE__ */ jsx("circle", { cx: "2.5", cy: "8", r: "1.25", fill: "currentColor" }),
|
|
130
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "1.25", fill: "currentColor" }),
|
|
131
|
+
/* @__PURE__ */ jsx("circle", { cx: "13.5", cy: "8", r: "1.25", fill: "currentColor" })
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "More" })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
Breadcrumb,
|
|
142
|
+
BreadcrumbEllipsis,
|
|
143
|
+
BreadcrumbItem,
|
|
144
|
+
BreadcrumbLink,
|
|
145
|
+
BreadcrumbList,
|
|
146
|
+
BreadcrumbPage,
|
|
147
|
+
BreadcrumbSeparator
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=breadcrumb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/breadcrumb.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Breadcrumb — Fluent 2-styled, shadcn-API breadcrumb family.\n *\n * Pure markup, matching shadcn/ui's Breadcrumb structure exactly: `Breadcrumb`\n * (`<nav aria-label=\"breadcrumb\">`), `BreadcrumbList` (`<ol>`), `BreadcrumbItem`\n * (`<li>`), `BreadcrumbLink` (`<a>`, `asChild`-capable via Radix `Slot`),\n * `BreadcrumbPage` (`<span role=\"link\" aria-current=\"page\">` for the\n * non-interactive current step), `BreadcrumbSeparator` (`<li\n * role=\"presentation\" aria-hidden=\"true\">`) and `BreadcrumbEllipsis` (same\n * presentation pattern, for a collapsed run of items) —\n * https://ui.shadcn.com/docs/components/breadcrumb.\n *\n * Two deliberate divergences from shadcn, both driven by this kit's\n * conventions doc (§9):\n *\n * 1. `BreadcrumbSeparator`'s default glyph and `BreadcrumbEllipsis`'s dots are\n * inline `<svg>`, not lucide's `ChevronRight`/`MoreHorizontal` (shadcn) or\n * `@fluentui/react-icons` (this kit's usual icon source). Importing from\n * `@fluentui/react-icons` forces `\"use client\"` onto the whole file (its\n * icon sizing module calls a client-only Griffel API at module scope — see\n * conventions §9 / `select.tsx`), which would needlessly turn this static\n * markup component into a client boundary. An inline SVG keeps `Breadcrumb`\n * a Server Component, matching `Card`/`Separator`.\n * 2. `BreadcrumbLink`'s hover treatment intentionally mirrors this kit's own\n * `Link` component's `default` variant (`hover:text-foreground`, color\n * transition only, underline only on hover via the surrounding\n * `hover:underline` here is *not* applied — shadcn's breadcrumb link has no\n * underline at all, even on hover, since it sits in a compact trail rather\n * than prose) rather than inventing a new recipe: `hover:text-foreground\n * transition-colors duration-fast ease-ease` plus the standard\n * focus-visible ring (conventions §4). Kept text-only (no underline) to\n * match shadcn's breadcrumb parity goal; reach for `Link` directly if a\n * consumer wants the underlined treatment.\n *\n * No `variant`/`size` props and no `cva` table — shadcn's Breadcrumb ships\n * none, so every part is a plain function component (matches `Card`).\n *\n * Server-safe: no `\"use client\"`, no hooks — the React import is type-only.\n */\n\nfunction Breadcrumb({ className, ...props }: ComponentProps<\"nav\">) {\n return (\n <nav\n aria-label=\"breadcrumb\"\n data-slot=\"breadcrumb\"\n className={cn(className)}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbList({ className, ...props }: ComponentProps<\"ol\">) {\n return (\n <ol\n data-slot=\"breadcrumb-list\"\n className={cn(\n // Trail text is Fluent NeutralForeground2 (#424242), one grey step\n // darker than muted-foreground (Figma validation pass 2, 9077:5763).\n \"flex flex-wrap items-center gap-1.5 text-sm text-foreground-2 sm:gap-2.5\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbItem({ className, ...props }: ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"breadcrumb-item\"\n className={cn(\"inline-flex items-center gap-1.5\", className)}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbLink({\n asChild = false,\n className,\n ...props\n}: ComponentProps<\"a\"> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n data-slot=\"breadcrumb-link\"\n className={cn(\n // Fluent breadcrumb items are button-like pills — SubtleBackground\n // hover/pressed fills on a 4px radius (the Figma item symbols are\n // literally Type=Button; validation pass 2, node 9077:5740). The fill\n // uses the kit's standard subtle-hover pair (--accent), the same\n // one-grey-step drift already accepted for Select/Menu highlights.\n \"rounded-md px-1.5 py-0.5 outline-none transition-colors duration-fast ease-ease hover:bg-accent hover:text-foreground active:bg-accent/80\",\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbPage({ className, ...props }: ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"breadcrumb-page\"\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n // Current page is emphasized by WEIGHT ONLY (Body 1 -> Body 1 Strong,\n // same NeutralForeground2 trail color) — Figma validation pass 2, node\n // 9077:5783. Padding mirrors the link pill so items align.\n className={cn(\"px-1.5 py-0.5 font-semibold\", className)}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbSeparator({\n children,\n className,\n ...props\n}: ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"breadcrumb-separator\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\"[&>svg]:size-3.5\", className)}\n {...props}\n >\n {children ?? (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6 3.5L10 8L6 12.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n </li>\n );\n}\n\nfunction BreadcrumbEllipsis({ className, ...props }: ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"breadcrumb-ellipsis\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\"flex size-6 items-center justify-center\", className)}\n {...props}\n >\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n className=\"size-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle cx=\"2.5\" cy=\"8\" r=\"1.25\" fill=\"currentColor\" />\n <circle cx=\"8\" cy=\"8\" r=\"1.25\" fill=\"currentColor\" />\n <circle cx=\"13.5\" cy=\"8\" r=\"1.25\" fill=\"currentColor\" />\n </svg>\n <span className=\"sr-only\">More</span>\n </span>\n );\n}\n\nexport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n};\n"],"mappings":"AA+CI,cAoHE,YApHF;AA/CJ,SAAS,YAAY;AAGrB,SAAS,UAAU;AA0CnB,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAA0B;AAClE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAW;AAAA,MACX,aAAU;AAAA,MACV,WAAW,GAAG,SAAS;AAAA,MACtB,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,MAAM,GAAyB;AACrE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA;AAAA;AAAA,QAGT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,MAAM,GAAyB;AACrE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,oCAAoC,SAAS;AAAA,MAC1D,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,eAAe;AAAA,EACtB,UAAU;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAgD;AAC9C,QAAM,OAAO,UAAU,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,MAAM,GAA2B;AACvE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAK;AAAA,MACL,iBAAc;AAAA,MACd,gBAAa;AAAA,MAIb,WAAW,GAAG,+BAA+B,SAAS;AAAA,MACrD,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,oBAAoB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,WAAW,GAAG,oBAAoB,SAAS;AAAA,MAC1C,GAAG;AAAA,MAEH,sBACC;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,OAAM;AAAA,UAEN;AAAA,YAAC;AAAA;AAAA,cACC,GAAE;AAAA,cACF,QAAO;AAAA,cACP,aAAY;AAAA,cACZ,eAAc;AAAA,cACd,gBAAe;AAAA;AAAA,UACjB;AAAA;AAAA,MACF;AAAA;AAAA,EAEJ;AAEJ;AAEA,SAAS,mBAAmB,EAAE,WAAW,GAAG,MAAM,GAA2B;AAC3E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YACV,OAAM;AAAA,YAEN;AAAA,kCAAC,YAAO,IAAG,OAAM,IAAG,KAAI,GAAE,QAAO,MAAK,gBAAe;AAAA,cACrD,oBAAC,YAAO,IAAG,KAAI,IAAG,KAAI,GAAE,QAAO,MAAK,gBAAe;AAAA,cACnD,oBAAC,YAAO,IAAG,QAAO,IAAG,KAAI,GAAE,QAAO,MAAK,gBAAe;AAAA;AAAA;AAAA,QACxD;AAAA,QACA,oBAAC,UAAK,WAAU,WAAU,kBAAI;AAAA;AAAA;AAAA,EAChC;AAEJ;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
4
|
+
import { VariantProps } from 'class-variance-authority';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Button — Fluent 2-styled, shadcn-API button.
|
|
8
|
+
*
|
|
9
|
+
* Aesthetics follow Fluent 2 (32px medium height, 14px/600 label, `rounded-md`
|
|
10
|
+
* = 4px, flat surfaces, brand state ramp for the primary); the prop surface
|
|
11
|
+
* (`variant`, `size`, `asChild`, `buttonVariants`) follows shadcn/ui.
|
|
12
|
+
*
|
|
13
|
+
* Server-safe: no `"use client"`, no hooks/handlers — the React import is
|
|
14
|
+
* type-only, so the file can be dropped straight into an RSC tree.
|
|
15
|
+
*/
|
|
16
|
+
declare const buttonVariants: (props?: ({
|
|
17
|
+
variant?: "link" | "default" | "destructive" | "secondary" | "outline" | "ghost" | null | undefined;
|
|
18
|
+
size?: "default" | "sm" | "lg" | "icon-sm" | "icon" | "icon-lg" | null | undefined;
|
|
19
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
20
|
+
declare function Button({ className, variant, size, asChild, ...props }: ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
21
|
+
asChild?: boolean;
|
|
22
|
+
}): react.JSX.Element;
|
|
23
|
+
|
|
24
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
[
|
|
7
|
+
// layout
|
|
8
|
+
"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap",
|
|
9
|
+
"rounded-md font-semibold select-none cursor-pointer",
|
|
10
|
+
// motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion
|
|
11
|
+
// automatically (the duration tokens collapse to 0.01ms in tokens.css).
|
|
12
|
+
"outline-none transition-colors duration-fast ease-ease",
|
|
13
|
+
// disabled — opacity read (uniform across every variant/component; see conventions doc)
|
|
14
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
15
|
+
// focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap
|
|
16
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
17
|
+
// invalid — shadcn aria-invalid treatment
|
|
18
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
19
|
+
// icon defaults
|
|
20
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
21
|
+
],
|
|
22
|
+
{
|
|
23
|
+
variants: {
|
|
24
|
+
variant: {
|
|
25
|
+
// Fluent primary — brand fill, flat, full brand state ramp (spec §2.5).
|
|
26
|
+
// rest brand-80 → hover brand-70 → pressed brand-60 (light);
|
|
27
|
+
// rest brand-70 → hover brand-80 → pressed brand-60 (dark).
|
|
28
|
+
default: "bg-primary text-primary-foreground hover:bg-brand-70 active:bg-brand-60 dark:hover:bg-brand-80",
|
|
29
|
+
// Fluent danger — red fill. No red ramp is exposed, so hover/pressed
|
|
30
|
+
// step via opacity; the focus ring switches to destructive.
|
|
31
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80 focus-visible:ring-destructive",
|
|
32
|
+
// Fluent standard/neutral — filled neutral surface + border.
|
|
33
|
+
secondary: "border border-border bg-secondary text-secondary-foreground hover:bg-accent active:bg-input dark:hover:bg-input dark:active:bg-input",
|
|
34
|
+
// Fluent outline — transparent + border, neutral hover.
|
|
35
|
+
outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:bg-input/30 dark:hover:bg-input/50 dark:active:bg-input/70",
|
|
36
|
+
// Fluent subtle — transparent, neutral hover only.
|
|
37
|
+
ghost: "hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-accent/50 dark:active:bg-accent/70",
|
|
38
|
+
// Fluent link — brand text, underline on hover. Color ramp matches
|
|
39
|
+
// Fluent BrandForegroundLink (rest brand-70 → hover brand-60 → pressed
|
|
40
|
+
// brand-50 light; brand-100 → 110 → 120 dark), kept in sync with the
|
|
41
|
+
// Link component; the button keeps its own font-semibold weight.
|
|
42
|
+
link: "text-brand-70 underline-offset-4 hover:text-brand-60 hover:underline active:text-brand-50 dark:text-brand-100 dark:hover:text-brand-110 dark:active:text-brand-120"
|
|
43
|
+
},
|
|
44
|
+
size: {
|
|
45
|
+
// Fluent medium (default) — 32px height, 12px horizontal padding, 14px text.
|
|
46
|
+
default: "h-8 px-3 text-sm has-[>svg]:px-2.5",
|
|
47
|
+
// Fluent small — 24px height, 8px padding, 12px text, tighter gap.
|
|
48
|
+
sm: "h-6 gap-1.5 px-2 text-xs has-[>svg]:px-1.5",
|
|
49
|
+
// Fluent large — 40px height, 16px padding, 16px text.
|
|
50
|
+
lg: "h-10 px-4 text-base has-[>svg]:px-3.5",
|
|
51
|
+
// Square icon-only buttons at each height.
|
|
52
|
+
"icon-sm": "size-6",
|
|
53
|
+
icon: "size-8",
|
|
54
|
+
"icon-lg": "size-10"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
defaultVariants: {
|
|
58
|
+
variant: "default",
|
|
59
|
+
size: "default"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
function Button({
|
|
64
|
+
className,
|
|
65
|
+
variant = "default",
|
|
66
|
+
size = "default",
|
|
67
|
+
asChild = false,
|
|
68
|
+
...props
|
|
69
|
+
}) {
|
|
70
|
+
const Comp = asChild ? Slot : "button";
|
|
71
|
+
return /* @__PURE__ */ jsx(
|
|
72
|
+
Comp,
|
|
73
|
+
{
|
|
74
|
+
"data-slot": "button",
|
|
75
|
+
"data-variant": variant,
|
|
76
|
+
"data-size": size,
|
|
77
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
78
|
+
...props
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
Button,
|
|
84
|
+
buttonVariants
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/button.tsx"],"sourcesContent":["import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Button — Fluent 2-styled, shadcn-API button.\n *\n * Aesthetics follow Fluent 2 (32px medium height, 14px/600 label, `rounded-md`\n * = 4px, flat surfaces, brand state ramp for the primary); the prop surface\n * (`variant`, `size`, `asChild`, `buttonVariants`) follows shadcn/ui.\n *\n * Server-safe: no `\"use client\"`, no hooks/handlers — the React import is\n * type-only, so the file can be dropped straight into an RSC tree.\n */\nconst buttonVariants = cva(\n [\n // layout\n \"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap\",\n \"rounded-md font-semibold select-none cursor-pointer\",\n // motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion\n // automatically (the duration tokens collapse to 0.01ms in tokens.css).\n \"outline-none transition-colors duration-fast ease-ease\",\n // disabled — opacity read (uniform across every variant/component; see conventions doc)\n \"disabled:pointer-events-none disabled:opacity-50\",\n // focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n // invalid — shadcn aria-invalid treatment\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n // icon defaults\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n ],\n {\n variants: {\n variant: {\n // Fluent primary — brand fill, flat, full brand state ramp (spec §2.5).\n // rest brand-80 → hover brand-70 → pressed brand-60 (light);\n // rest brand-70 → hover brand-80 → pressed brand-60 (dark).\n default:\n \"bg-primary text-primary-foreground hover:bg-brand-70 active:bg-brand-60 dark:hover:bg-brand-80\",\n // Fluent danger — red fill. No red ramp is exposed, so hover/pressed\n // step via opacity; the focus ring switches to destructive.\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80 focus-visible:ring-destructive\",\n // Fluent standard/neutral — filled neutral surface + border.\n secondary:\n \"border border-border bg-secondary text-secondary-foreground hover:bg-accent active:bg-input dark:hover:bg-input dark:active:bg-input\",\n // Fluent outline — transparent + border, neutral hover.\n outline:\n \"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:bg-input/30 dark:hover:bg-input/50 dark:active:bg-input/70\",\n // Fluent subtle — transparent, neutral hover only.\n ghost:\n \"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-accent/50 dark:active:bg-accent/70\",\n // Fluent link — brand text, underline on hover. Color ramp matches\n // Fluent BrandForegroundLink (rest brand-70 → hover brand-60 → pressed\n // brand-50 light; brand-100 → 110 → 120 dark), kept in sync with the\n // Link component; the button keeps its own font-semibold weight.\n link: \"text-brand-70 underline-offset-4 hover:text-brand-60 hover:underline active:text-brand-50 dark:text-brand-100 dark:hover:text-brand-110 dark:active:text-brand-120\",\n },\n size: {\n // Fluent medium (default) — 32px height, 12px horizontal padding, 14px text.\n default: \"h-8 px-3 text-sm has-[>svg]:px-2.5\",\n // Fluent small — 24px height, 8px padding, 12px text, tighter gap.\n sm: \"h-6 gap-1.5 px-2 text-xs has-[>svg]:px-1.5\",\n // Fluent large — 40px height, 16px padding, 16px text.\n lg: \"h-10 px-4 text-base has-[>svg]:px-3.5\",\n // Square icon-only buttons at each height.\n \"icon-sm\": \"size-6\",\n icon: \"size-8\",\n \"icon-lg\": \"size-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\nfunction Button({\n className,\n variant = \"default\",\n size = \"default\",\n asChild = false,\n ...props\n}: ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n"],"mappings":"AA6FI;AA7FJ,SAAS,YAAY;AACrB,SAAS,WAA8B;AAGvC,SAAS,UAAU;AAYnB,MAAM,iBAAiB;AAAA,EACrB;AAAA;AAAA,IAEE;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA;AAAA;AAAA;AAAA,QAIP,SACE;AAAA;AAAA;AAAA,QAGF,aACE;AAAA;AAAA,QAEF,WACE;AAAA;AAAA,QAEF,SACE;AAAA;AAAA,QAEF,OACE;AAAA;AAAA;AAAA;AAAA;AAAA,QAKF,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA;AAAA,QAEJ,SAAS;AAAA;AAAA,QAET,IAAI;AAAA;AAAA,QAEJ,IAAI;AAAA;AAAA,QAEJ,WAAW;AAAA,QACX,MAAM;AAAA,QACN,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,OAAO;AAAA,EACd;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP,UAAU;AAAA,EACV,GAAG;AACL,GAGK;AACH,QAAM,OAAO,UAAU,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,aAAW;AAAA,MACX,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,MACzD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Card — Fluent 2-styled, shadcn-API card family.
|
|
6
|
+
*
|
|
7
|
+
* Structure matches shadcn/ui's current Card exactly: seven composable parts
|
|
8
|
+
* (`Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`,
|
|
9
|
+
* `CardContent`, `CardFooter`), each a plain `<div>` carrying its own
|
|
10
|
+
* `data-slot`. `CardHeader` is a grid so an optional `CardAction` can sit in
|
|
11
|
+
* its own column/row-span without extra markup (shadcn's `has-data-[slot=...]`
|
|
12
|
+
* trick) — see https://ui.shadcn.com/docs/components/card.
|
|
13
|
+
*
|
|
14
|
+
* Fluent 2 aesthetics layered on top of that skeleton:
|
|
15
|
+
* - `rounded-lg` + `border` + `shadow-4` (Fluent's resting elevation for a
|
|
16
|
+
* filled card — https://storybooks.fluentui.dev/react/?path=/docs/components-card--docs)
|
|
17
|
+
* instead of shadcn's default `rounded-xl` + `shadow-sm`.
|
|
18
|
+
* - `bg-card text-card-foreground` (shadcn semantic tokens, re-pointed for
|
|
19
|
+
* dark mode by tokens.css).
|
|
20
|
+
* - `CardTitle` is `font-semibold` (Fluent card titles are weight 600).
|
|
21
|
+
* - Spacing: Fluent's own medium card padding is 12px (`p-3`), noticeably
|
|
22
|
+
* tighter than shadcn's 24px (`p-6`) default. We split the difference and
|
|
23
|
+
* land on 16px (`gap-4` / `p-4` / `px-4`) — visibly tighter than shadcn so
|
|
24
|
+
* the Fluent influence reads, while keeping enough breathing room that the
|
|
25
|
+
* header/content/footer rhythm still feels like the familiar shadcn
|
|
26
|
+
* composition (a straight 12px port felt cramped next to Button's 32px
|
|
27
|
+
* controls). Document this if you change it.
|
|
28
|
+
*
|
|
29
|
+
* No `variant`/`size` props: shadcn's Card ships none, so there is no `cva`
|
|
30
|
+
* table here (unlike Button) — every part is a plain function component.
|
|
31
|
+
*
|
|
32
|
+
* `interactive` (kit extension): Fluent cards have hover/pressed states when
|
|
33
|
+
* clickable; passing `interactive` raises the resting `shadow-4` to `shadow-8`
|
|
34
|
+
* on hover (Fluent's interactive-card elevation step) with a pointer cursor
|
|
35
|
+
* and `data-interactive` hook. Visual-only — semantics/focus come from what
|
|
36
|
+
* you compose it with (wrap in an `<a>`/`<button>` or add role/tabIndex).
|
|
37
|
+
*
|
|
38
|
+
* Server-safe: no `"use client"`, no hooks — the React import is type-only.
|
|
39
|
+
*/
|
|
40
|
+
declare function Card({ className, interactive, ...props }: ComponentProps<"div"> & {
|
|
41
|
+
/** Fluent interactive-card elevation states (hover raises to shadow-8). */
|
|
42
|
+
interactive?: boolean;
|
|
43
|
+
}): react.JSX.Element;
|
|
44
|
+
declare function CardHeader({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
|
|
45
|
+
declare function CardTitle({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
|
|
46
|
+
declare function CardDescription({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
|
|
47
|
+
declare function CardAction({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
|
|
48
|
+
declare function CardContent({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
|
|
49
|
+
declare function CardFooter({ className, ...props }: ComponentProps<"div">): react.JSX.Element;
|
|
50
|
+
|
|
51
|
+
export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
function Card({
|
|
4
|
+
className,
|
|
5
|
+
interactive = false,
|
|
6
|
+
...props
|
|
7
|
+
}) {
|
|
8
|
+
return /* @__PURE__ */ jsx(
|
|
9
|
+
"div",
|
|
10
|
+
{
|
|
11
|
+
"data-slot": "card",
|
|
12
|
+
"data-interactive": interactive || void 0,
|
|
13
|
+
className: cn(
|
|
14
|
+
"flex flex-col gap-4 rounded-lg border bg-card py-4 text-card-foreground shadow-4",
|
|
15
|
+
interactive && "cursor-pointer transition-shadow duration-fast ease-ease hover:shadow-8 active:shadow-4",
|
|
16
|
+
className
|
|
17
|
+
),
|
|
18
|
+
...props
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function CardHeader({ className, ...props }) {
|
|
23
|
+
return /* @__PURE__ */ jsx(
|
|
24
|
+
"div",
|
|
25
|
+
{
|
|
26
|
+
"data-slot": "card-header",
|
|
27
|
+
className: cn(
|
|
28
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-4",
|
|
29
|
+
className
|
|
30
|
+
),
|
|
31
|
+
...props
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function CardTitle({ className, ...props }) {
|
|
36
|
+
return /* @__PURE__ */ jsx(
|
|
37
|
+
"div",
|
|
38
|
+
{
|
|
39
|
+
"data-slot": "card-title",
|
|
40
|
+
className: cn("font-semibold leading-none", className),
|
|
41
|
+
...props
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function CardDescription({ className, ...props }) {
|
|
46
|
+
return /* @__PURE__ */ jsx(
|
|
47
|
+
"div",
|
|
48
|
+
{
|
|
49
|
+
"data-slot": "card-description",
|
|
50
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
51
|
+
...props
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function CardAction({ className, ...props }) {
|
|
56
|
+
return /* @__PURE__ */ jsx(
|
|
57
|
+
"div",
|
|
58
|
+
{
|
|
59
|
+
"data-slot": "card-action",
|
|
60
|
+
className: cn(
|
|
61
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
62
|
+
className
|
|
63
|
+
),
|
|
64
|
+
...props
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
function CardContent({ className, ...props }) {
|
|
69
|
+
return /* @__PURE__ */ jsx(
|
|
70
|
+
"div",
|
|
71
|
+
{
|
|
72
|
+
"data-slot": "card-content",
|
|
73
|
+
className: cn("px-4", className),
|
|
74
|
+
...props
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function CardFooter({ className, ...props }) {
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
"div",
|
|
81
|
+
{
|
|
82
|
+
"data-slot": "card-footer",
|
|
83
|
+
className: cn("flex items-center px-4 [.border-t]:pt-4", className),
|
|
84
|
+
...props
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
Card,
|
|
90
|
+
CardAction,
|
|
91
|
+
CardContent,
|
|
92
|
+
CardDescription,
|
|
93
|
+
CardFooter,
|
|
94
|
+
CardHeader,
|
|
95
|
+
CardTitle
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=card.js.map
|