@glyphui/ui 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -12
- package/dist/chart.d.ts +202 -0
- package/dist/chart.js +466 -0
- package/dist/color-mode-script-Bf7LYkOj.js +13 -0
- package/dist/color-mode-script-KdwItKN2.d.ts +16 -0
- package/dist/data-table.js +3 -2
- package/dist/{field-CCecIzdq.js → field-CVbywksE.js} +21 -5
- package/dist/{field-context-B6WOcK5m.js → field-context-7I-cWvvS.js} +3 -57
- package/dist/form.d.ts +11 -4
- package/dist/form.js +34 -8
- package/dist/glyph-context-DxGJrdvr.d.ts +50 -0
- package/dist/{index-BWK4czIG.d.ts → index-Bi7rZ6ba.d.ts} +11 -0
- package/dist/index.d.ts +2334 -283
- package/dist/index.js +2952 -326
- package/dist/react-router.d.ts +18 -0
- package/dist/react-router.js +44 -0
- package/dist/{states-Rp8wZ_jH.js → states-IvhjBgna.js} +2 -1
- package/dist/tv-DLbT66dC.js +70 -0
- package/dist/vite.d.ts +27 -0
- package/dist/vite.js +29 -0
- package/fonts.css +10 -0
- package/package.json +47 -5
- package/src/components/accordion/accordion.styles.ts +4 -4
- package/src/components/badge/badge.styles.ts +11 -16
- package/src/components/banner/banner.styles.ts +4 -4
- package/src/components/button/button.styles.ts +3 -1
- package/src/components/calendar/calendar.styles.ts +51 -0
- package/src/components/calendar/calendar.tsx +84 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/carousel/carousel.styles.ts +43 -0
- package/src/components/carousel/carousel.tsx +278 -0
- package/src/components/carousel/index.ts +13 -0
- package/src/components/collapsible/collapsible.tsx +66 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/context-menu/context-menu.styles.ts +19 -0
- package/src/components/context-menu/context-menu.tsx +226 -0
- package/src/components/context-menu/index.ts +19 -0
- package/src/components/copy-button/copy-button.tsx +117 -0
- package/src/components/copy-button/index.ts +1 -0
- package/src/components/date-picker/date-picker.styles.ts +42 -0
- package/src/components/date-picker/date-picker.tsx +322 -0
- package/src/components/date-picker/index.ts +8 -0
- package/src/components/dropdown-menu/dropdown-menu.styles.ts +2 -2
- package/src/components/eyebrow/eyebrow.styles.ts +1 -1
- package/src/components/field/field-context.ts +2 -0
- package/src/components/field/field.styles.ts +4 -2
- package/src/components/field/field.tsx +25 -4
- package/src/components/file-upload/file-upload.styles.ts +43 -0
- package/src/components/file-upload/file-upload.tsx +343 -0
- package/src/components/file-upload/index.ts +10 -0
- package/src/components/hover-card/hover-card.tsx +41 -0
- package/src/components/hover-card/index.ts +1 -0
- package/src/components/input-otp/index.ts +10 -0
- package/src/components/input-otp/input-otp.styles.ts +38 -0
- package/src/components/input-otp/input-otp.tsx +116 -0
- package/src/components/link/link.styles.ts +1 -1
- package/src/components/modal/index.ts +2 -0
- package/src/components/modal/modal.styles.ts +20 -0
- package/src/components/modal/modal.tsx +111 -0
- package/src/components/navigation-menu/index.ts +14 -0
- package/src/components/navigation-menu/navigation-menu.styles.ts +48 -0
- package/src/components/navigation-menu/navigation-menu.tsx +208 -0
- package/src/components/number-input/index.ts +2 -0
- package/src/components/number-input/number-input.styles.ts +41 -0
- package/src/components/number-input/number-input.tsx +211 -0
- package/src/components/password-input/index.ts +7 -0
- package/src/components/password-input/password-input.styles.ts +30 -0
- package/src/components/password-input/password-input.tsx +127 -0
- package/src/components/prose/prose.tsx +1 -1
- package/src/components/resizable/index.ts +9 -0
- package/src/components/resizable/resizable.styles.ts +28 -0
- package/src/components/resizable/resizable.tsx +69 -0
- package/src/components/scroll-area/scroll-area.tsx +23 -1
- package/src/components/segmented-control/segmented-control.tsx +17 -2
- package/src/components/sheet/sheet.styles.ts +7 -7
- package/src/components/slider/slider.tsx +9 -2
- package/src/components/stepper/index.ts +2 -0
- package/src/components/stepper/stepper.styles.ts +74 -0
- package/src/components/stepper/stepper.tsx +138 -0
- package/src/components/tag-input/index.ts +2 -0
- package/src/components/tag-input/tag-input.styles.ts +39 -0
- package/src/components/tag-input/tag-input.tsx +198 -0
- package/src/components/text/text.styles.ts +4 -4
- package/src/components/timeline/index.ts +8 -0
- package/src/components/timeline/timeline.styles.ts +56 -0
- package/src/components/timeline/timeline.tsx +91 -0
- package/src/components/toggle/index.ts +9 -0
- package/src/components/toggle/toggle.styles.ts +39 -0
- package/src/components/toggle/toggle.tsx +61 -0
- package/src/components/tree-view/index.ts +2 -0
- package/src/components/tree-view/tree-view.styles.ts +42 -0
- package/src/components/tree-view/tree-view.tsx +274 -0
- package/src/components/visually-hidden/index.ts +1 -0
- package/src/components/visually-hidden/visually-hidden.tsx +10 -0
- package/src/index.ts +20 -0
- package/src/integrations/react-router.tsx +39 -0
- package/src/integrations/vite.test-d.ts +6 -0
- package/src/integrations/vite.test.ts +28 -0
- package/src/integrations/vite.ts +33 -0
- package/src/internal/dev.ts +12 -0
- package/src/patterns/app-shell/app-shell.styles.ts +1 -1
- package/src/patterns/app-shell/navbar.tsx +34 -16
- package/src/patterns/app-shell/sidebar.tsx +7 -2
- package/src/patterns/chart/chart-parts.tsx +179 -0
- package/src/patterns/chart/chart.styles.ts +35 -0
- package/src/patterns/chart/chart.tsx +386 -0
- package/src/patterns/chart/index.ts +13 -0
- package/src/patterns/form/form.tsx +48 -6
- package/src/patterns/form/index.ts +1 -0
- package/src/provider/color-mode-script.ts +28 -0
- package/src/provider/color-mode.tsx +14 -20
- package/src/provider/glyph-context.ts +38 -0
- package/src/provider/glyph-provider.tsx +16 -4
- package/src/provider/index.ts +4 -0
- package/src/provider/theme-scope.tsx +7 -3
- package/styles.css +88 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { a as LinkComponentProps, n as GlyphRouter, r as IsActiveOptions } from "./glyph-context-DxGJrdvr.js";
|
|
2
|
+
//#region src/integrations/react-router.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* React Router's `Link`, taking Glyph's `href` prop. Hash links, external URLs and `mailto:`
|
|
5
|
+
* stay plain anchors, so in-page anchors scroll and external links leave the app.
|
|
6
|
+
*/
|
|
7
|
+
export declare function RouterLink({ href, ...props }: LinkComponentProps): import("react").JSX.Element;
|
|
8
|
+
/** Whether an in-app `href` matches a pathname, with React Router's `NavLink` semantics. */
|
|
9
|
+
export declare function matchPath(href: string, pathname: string, { end }?: IsActiveOptions): boolean;
|
|
10
|
+
/** Marks a link active when the current location matches it, like React Router's `NavLink`. */
|
|
11
|
+
export declare function useRouterIsActive(href: string, options?: IsActiveOptions): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Connects Glyph's navigation to React Router (v7 or later):
|
|
14
|
+
* `<GlyphProvider router={reactRouter}>`. Links render client-side, and Sidebar, Navbar
|
|
15
|
+
* and NavigationMenu items mark the current page automatically. Must render inside a router.
|
|
16
|
+
*/
|
|
17
|
+
export declare const reactRouter: GlyphRouter;
|
|
18
|
+
//#endregion
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Link, useLocation } from "react-router";
|
|
4
|
+
//#region src/integrations/react-router.tsx
|
|
5
|
+
const isOutsideApp = (href) => href.startsWith("#") || href.startsWith("//") || /^[a-z][a-z\d+.-]*:/i.test(href);
|
|
6
|
+
/**
|
|
7
|
+
* React Router's `Link`, taking Glyph's `href` prop. Hash links, external URLs and `mailto:`
|
|
8
|
+
* stay plain anchors, so in-page anchors scroll and external links leave the app.
|
|
9
|
+
*/
|
|
10
|
+
function RouterLink({ href, ...props }) {
|
|
11
|
+
if (isOutsideApp(href)) return /* @__PURE__ */ jsx("a", {
|
|
12
|
+
href,
|
|
13
|
+
...props
|
|
14
|
+
});
|
|
15
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
16
|
+
to: href,
|
|
17
|
+
...props
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const trimSlash = (path) => path.length > 1 ? path.replace(/\/+$/, "") : path;
|
|
21
|
+
/** Whether an in-app `href` matches a pathname, with React Router's `NavLink` semantics. */
|
|
22
|
+
function matchPath(href, pathname, { end = false } = {}) {
|
|
23
|
+
if (!href || isOutsideApp(href)) return false;
|
|
24
|
+
const target = trimSlash(new URL(href, "http://glyph.local").pathname);
|
|
25
|
+
const current = trimSlash(pathname);
|
|
26
|
+
if (end || target === "/") return current === target;
|
|
27
|
+
return current === target || current.startsWith(`${target}/`);
|
|
28
|
+
}
|
|
29
|
+
/** Marks a link active when the current location matches it, like React Router's `NavLink`. */
|
|
30
|
+
function useRouterIsActive(href, options) {
|
|
31
|
+
const { pathname } = useLocation();
|
|
32
|
+
return matchPath(href, pathname, options);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Connects Glyph's navigation to React Router (v7 or later):
|
|
36
|
+
* `<GlyphProvider router={reactRouter}>`. Links render client-side, and Sidebar, Navbar
|
|
37
|
+
* and NavigationMenu items mark the current page automatically. Must render inside a router.
|
|
38
|
+
*/
|
|
39
|
+
const reactRouter = {
|
|
40
|
+
Link: RouterLink,
|
|
41
|
+
useIsActive: useRouterIsActive
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { RouterLink, matchPath, reactRouter, useRouterIsActive };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { a as themedBorder, o as transition, r as focusRing, s as useComponentConfig, t as tv } from "./tv-DLbT66dC.js";
|
|
3
|
+
import { c as AlertIcon, d as CheckIcon, g as InboxIcon, n as useFieldControl, o as Spinner, r as Button, v as MinusIcon } from "./field-context-7I-cWvvS.js";
|
|
3
4
|
import { Checkbox } from "radix-ui";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/checkbox/checkbox.tsx
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { textTokens } from "@glyphui/tokens";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
import { createTV } from "tailwind-variants";
|
|
5
|
+
//#region src/provider/glyph-context.ts
|
|
6
|
+
const GlyphContext = createContext(null);
|
|
7
|
+
/** The active theme and app-level settings. Must be used under `GlyphProvider`. */
|
|
8
|
+
function useGlyph() {
|
|
9
|
+
const value = useContext(GlyphContext);
|
|
10
|
+
if (!value) throw new Error("useGlyph must be used inside <GlyphProvider>.");
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
/** The router-aware link component configured on `GlyphProvider`. */
|
|
14
|
+
function useLinkComponent() {
|
|
15
|
+
return useContext(GlyphContext)?.linkComponent ?? "a";
|
|
16
|
+
}
|
|
17
|
+
const neverActive = () => false;
|
|
18
|
+
/**
|
|
19
|
+
* Whether `href` is the current page, using the router passed to `GlyphProvider`.
|
|
20
|
+
* Returns `explicit` when it is set, so an `active` prop always wins.
|
|
21
|
+
*/
|
|
22
|
+
function useIsActive(href, options) {
|
|
23
|
+
const isActive = useContext(GlyphContext)?.useIsActive ?? neverActive;
|
|
24
|
+
const { explicit, ...matchOptions } = options ?? {};
|
|
25
|
+
const matched = isActive(href ?? "", matchOptions);
|
|
26
|
+
if (explicit !== void 0) return explicit;
|
|
27
|
+
return href ? matched : false;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/hooks/use-component-config.ts
|
|
31
|
+
/**
|
|
32
|
+
* Applies the theme's per-component settings (`theme.components[name]`):
|
|
33
|
+
* fills unset variant props from `defaultVariants` and returns the extra slot classes.
|
|
34
|
+
* Every styled component calls this first.
|
|
35
|
+
*/
|
|
36
|
+
function useComponentConfig(name, props) {
|
|
37
|
+
const override = (useContext(GlyphContext)?.theme)?.components[name];
|
|
38
|
+
if (!override) return [props, {}];
|
|
39
|
+
let merged = props;
|
|
40
|
+
const defaults = override.defaultVariants;
|
|
41
|
+
if (defaults) {
|
|
42
|
+
merged = { ...props };
|
|
43
|
+
for (const [key, value] of Object.entries(defaults)) if (merged[key] === void 0) merged[key] = value;
|
|
44
|
+
}
|
|
45
|
+
return [merged, override.className ?? {}];
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/utils/styles.ts
|
|
49
|
+
/** Shared class fragments. Kept as literals so Tailwind's scanner picks them up. */
|
|
50
|
+
const focusRing = "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
|
|
51
|
+
const themedBorder = "border-(length:--glyph-border-width)";
|
|
52
|
+
const transition = "transition-[color,background-color,border-color,box-shadow,transform,opacity] duration-(--glyph-duration-base) ease-standard";
|
|
53
|
+
/** Popover surfaces: Select, Tooltip-like menus. */
|
|
54
|
+
const popover = "z-50 overflow-hidden rounded-[min(var(--radius-md),var(--radius-lg))] border-(length:--glyph-border-width) border-border bg-raised text-foreground shadow-lg animate-pop-in";
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/utils/tw-merge-config.ts
|
|
57
|
+
/**
|
|
58
|
+
* Teaches tailwind-merge about Glyph's custom utilities so `cn('text-h1', 'text-small')`
|
|
59
|
+
* keeps only the last size instead of treating `text-h1` as a color.
|
|
60
|
+
*/
|
|
61
|
+
const twMergeConfig = { extend: { theme: {
|
|
62
|
+
text: [...textTokens],
|
|
63
|
+
radius: ["pill"]
|
|
64
|
+
} } };
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/utils/tv.ts
|
|
67
|
+
/** `tailwind-variants` preconfigured with Glyph's merge rules. All component styles use this. */
|
|
68
|
+
const tv = createTV({ twMergeConfig });
|
|
69
|
+
//#endregion
|
|
70
|
+
export { themedBorder as a, GlyphContext as c, useIsActive as d, useLinkComponent as f, popover as i, neverActive as l, twMergeConfig as n, transition as o, focusRing as r, useComponentConfig as s, tv as t, useGlyph as u };
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { t as ColorModeOptions } from "./color-mode-script-KdwItKN2.js";
|
|
2
|
+
//#region src/integrations/vite.d.ts
|
|
3
|
+
/** The subset of Vite's `Plugin` this plugin uses, so `vite` stays an optional peer. */
|
|
4
|
+
export interface GlyphVitePlugin {
|
|
5
|
+
name: string;
|
|
6
|
+
transformIndexHtml: {
|
|
7
|
+
order: 'pre';
|
|
8
|
+
handler: () => {
|
|
9
|
+
tag: 'script';
|
|
10
|
+
children: string;
|
|
11
|
+
injectTo: 'head-prepend';
|
|
12
|
+
}[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Injects Glyph's color-mode script into `index.html` so dark-mode visitors never see a
|
|
17
|
+
* light flash. Pass the same `defaultMode` and `storageKey` as `GlyphProvider`.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* // vite.config.ts
|
|
21
|
+
* import { glyphColorMode } from '@glyphui/ui/vite'
|
|
22
|
+
* export default defineConfig({ plugins: [react(), tailwindcss(), glyphColorMode()] })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function glyphColorMode(options?: ColorModeOptions): GlyphVitePlugin;
|
|
26
|
+
//#endregion
|
|
27
|
+
export type { ColorModeOptions };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { r as getColorModeScript } from "./color-mode-script-Bf7LYkOj.js";
|
|
3
|
+
//#region src/integrations/vite.ts
|
|
4
|
+
/**
|
|
5
|
+
* Injects Glyph's color-mode script into `index.html` so dark-mode visitors never see a
|
|
6
|
+
* light flash. Pass the same `defaultMode` and `storageKey` as `GlyphProvider`.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // vite.config.ts
|
|
10
|
+
* import { glyphColorMode } from '@glyphui/ui/vite'
|
|
11
|
+
* export default defineConfig({ plugins: [react(), tailwindcss(), glyphColorMode()] })
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
function glyphColorMode(options = {}) {
|
|
15
|
+
const script = getColorModeScript(options);
|
|
16
|
+
return {
|
|
17
|
+
name: "glyph:color-mode",
|
|
18
|
+
transformIndexHtml: {
|
|
19
|
+
order: "pre",
|
|
20
|
+
handler: () => [{
|
|
21
|
+
tag: "script",
|
|
22
|
+
children: script,
|
|
23
|
+
injectTo: "head-prepend"
|
|
24
|
+
}]
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { glyphColorMode };
|
package/fonts.css
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Geist and Geist Mono, the fonts the default `glyph` theme uses. Import once, before the
|
|
3
|
+
* Glyph stylesheet:
|
|
4
|
+
*
|
|
5
|
+
* @import "@glyphui/ui/fonts.css";
|
|
6
|
+
* @import "tailwindcss";
|
|
7
|
+
* @import "@glyphui/ui/styles.css";
|
|
8
|
+
*/
|
|
9
|
+
@import "@fontsource-variable/geist";
|
|
10
|
+
@import "@fontsource-variable/geist-mono";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glyphui/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React components on Tailwind CSS v4 that take their entire look from one theme file. Forms, data tables, app layouts and async states included.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Mohak Gupta (https://github.com/khalmohak)",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"theme",
|
|
25
25
|
"dark-mode",
|
|
26
26
|
"forms",
|
|
27
|
-
"data-table"
|
|
27
|
+
"data-table",
|
|
28
|
+
"react-router"
|
|
28
29
|
],
|
|
29
30
|
"type": "module",
|
|
30
31
|
"sideEffects": [
|
|
@@ -46,23 +47,47 @@
|
|
|
46
47
|
"types": "./dist/data-table.d.ts",
|
|
47
48
|
"default": "./dist/data-table.js"
|
|
48
49
|
},
|
|
50
|
+
"./chart": {
|
|
51
|
+
"@glyphui/source": "./src/patterns/chart/index.ts",
|
|
52
|
+
"types": "./dist/chart.d.ts",
|
|
53
|
+
"default": "./dist/chart.js"
|
|
54
|
+
},
|
|
55
|
+
"./react-router": {
|
|
56
|
+
"@glyphui/source": "./src/integrations/react-router.tsx",
|
|
57
|
+
"types": "./dist/react-router.d.ts",
|
|
58
|
+
"default": "./dist/react-router.js"
|
|
59
|
+
},
|
|
60
|
+
"./vite": {
|
|
61
|
+
"@glyphui/source": "./src/integrations/vite.ts",
|
|
62
|
+
"types": "./dist/vite.d.ts",
|
|
63
|
+
"default": "./dist/vite.js"
|
|
64
|
+
},
|
|
49
65
|
"./styles.css": "./styles.css",
|
|
66
|
+
"./fonts.css": "./fonts.css",
|
|
50
67
|
"./package.json": "./package.json"
|
|
51
68
|
},
|
|
52
69
|
"files": [
|
|
53
70
|
"dist",
|
|
54
71
|
"src",
|
|
55
72
|
"styles.css",
|
|
73
|
+
"fonts.css",
|
|
56
74
|
"!src/**/*.test.tsx",
|
|
57
75
|
"!src/**/*.test-d.tsx",
|
|
58
76
|
"!src/test"
|
|
59
77
|
],
|
|
60
78
|
"dependencies": {
|
|
61
|
-
"@
|
|
79
|
+
"@fontsource-variable/geist": "^5.3.0",
|
|
80
|
+
"@fontsource-variable/geist-mono": "^5.3.0",
|
|
81
|
+
"@glyphui/tokens": "^0.4.0",
|
|
62
82
|
"@standard-schema/spec": "^1.1.0",
|
|
63
83
|
"clsx": "^2.1.1",
|
|
64
84
|
"cmdk": "^1.1.1",
|
|
85
|
+
"date-fns": "^4.4.0",
|
|
86
|
+
"embla-carousel-react": "^8.6.0",
|
|
87
|
+
"input-otp": "^1.5.0",
|
|
65
88
|
"radix-ui": "^1.6.7",
|
|
89
|
+
"react-day-picker": "^9.14.0",
|
|
90
|
+
"react-resizable-panels": "^3.0.6",
|
|
66
91
|
"sonner": "^2.0.8",
|
|
67
92
|
"tailwind-merge": "^3.7.0",
|
|
68
93
|
"tailwind-variants": "^3.3.1"
|
|
@@ -73,7 +98,10 @@
|
|
|
73
98
|
"react": ">=19",
|
|
74
99
|
"react-dom": ">=19",
|
|
75
100
|
"react-hook-form": "^7.55.0",
|
|
76
|
-
"
|
|
101
|
+
"react-router": ">=7",
|
|
102
|
+
"recharts": "^3.0.0",
|
|
103
|
+
"tailwindcss": ">=4.1",
|
|
104
|
+
"vite": ">=5"
|
|
77
105
|
},
|
|
78
106
|
"peerDependenciesMeta": {
|
|
79
107
|
"@hookform/resolvers": {
|
|
@@ -84,6 +112,15 @@
|
|
|
84
112
|
},
|
|
85
113
|
"react-hook-form": {
|
|
86
114
|
"optional": true
|
|
115
|
+
},
|
|
116
|
+
"react-router": {
|
|
117
|
+
"optional": true
|
|
118
|
+
},
|
|
119
|
+
"recharts": {
|
|
120
|
+
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"vite": {
|
|
123
|
+
"optional": true
|
|
87
124
|
}
|
|
88
125
|
},
|
|
89
126
|
"devDependencies": {
|
|
@@ -96,12 +133,16 @@
|
|
|
96
133
|
"@testing-library/user-event": "^14.6.7",
|
|
97
134
|
"@types/react": "^19.2.18",
|
|
98
135
|
"@types/react-dom": "^19.2.7",
|
|
136
|
+
"@vitest/coverage-v8": "3.2.7",
|
|
99
137
|
"jsdom": "^27.4.0",
|
|
100
138
|
"react": "^19.2.8",
|
|
101
139
|
"react-dom": "^19.2.8",
|
|
102
140
|
"react-hook-form": "^7.89.0",
|
|
141
|
+
"react-router": "^7.18.4",
|
|
142
|
+
"recharts": "^3.10.1",
|
|
103
143
|
"tsdown": "^0.23.0",
|
|
104
144
|
"typescript": "~5.9.3",
|
|
145
|
+
"vite": "^7.3.6",
|
|
105
146
|
"vitest": "^3.2.7",
|
|
106
147
|
"zod": "^4.6.5"
|
|
107
148
|
},
|
|
@@ -113,6 +154,7 @@
|
|
|
113
154
|
"build": "tsdown",
|
|
114
155
|
"typecheck": "tsc",
|
|
115
156
|
"test": "vitest run",
|
|
116
|
-
"
|
|
157
|
+
"test:coverage": "vitest run --coverage",
|
|
158
|
+
"check:exports": "attw --pack . --profile esm-only --exclude-entrypoints ./styles.css ./fonts.css"
|
|
117
159
|
}
|
|
118
160
|
}
|
|
@@ -15,10 +15,10 @@ export const accordionStyles = tv({
|
|
|
15
15
|
],
|
|
16
16
|
chevron:
|
|
17
17
|
'size-4 shrink-0 text-faint-foreground transition-transform duration-(--glyph-duration-base) ease-standard group-data-[state=open]:rotate-180',
|
|
18
|
-
content:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
content:
|
|
19
|
+
'overflow-hidden text-small text-muted-foreground data-[state=open]:animate-collapse-down data-[state=closed]:animate-collapse-up',
|
|
20
|
+
// Height animates open and closed using Radix's measured content height.
|
|
21
|
+
contentInner: 'pb-4',
|
|
22
22
|
},
|
|
23
23
|
variants: {
|
|
24
24
|
variant: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { themedBorder } from '../../utils/styles'
|
|
2
2
|
import { tv, type VariantProps } from '../../utils/tv'
|
|
3
3
|
|
|
4
|
-
// Tinted text
|
|
4
|
+
// Tinted text uses the theme's status text colors, which stay readable on tinted backgrounds.
|
|
5
5
|
const tones = {
|
|
6
6
|
neutral: {
|
|
7
7
|
soft: 'bg-muted text-muted-foreground',
|
|
@@ -9,34 +9,29 @@ const tones = {
|
|
|
9
9
|
outline: 'border-border-strong text-foreground',
|
|
10
10
|
},
|
|
11
11
|
primary: {
|
|
12
|
-
soft: 'bg-primary/14 text-
|
|
12
|
+
soft: 'bg-primary/14 text-primary-text',
|
|
13
13
|
solid: 'bg-primary text-primary-foreground',
|
|
14
|
-
outline:
|
|
15
|
-
'border-primary/50 text-[color-mix(in_oklab,var(--color-primary)_62%,var(--color-foreground))]',
|
|
14
|
+
outline: 'border-primary/50 text-primary-text',
|
|
16
15
|
},
|
|
17
16
|
success: {
|
|
18
|
-
soft: 'bg-success/14 text-
|
|
17
|
+
soft: 'bg-success/14 text-success-text',
|
|
19
18
|
solid: 'bg-success text-success-foreground',
|
|
20
|
-
outline:
|
|
21
|
-
'border-success/50 text-[color-mix(in_oklab,var(--color-success)_62%,var(--color-foreground))]',
|
|
19
|
+
outline: 'border-success/50 text-success-text',
|
|
22
20
|
},
|
|
23
21
|
warning: {
|
|
24
|
-
soft: 'bg-warning/
|
|
22
|
+
soft: 'bg-warning/14 text-warning-text',
|
|
25
23
|
solid: 'bg-warning text-warning-foreground',
|
|
26
|
-
outline:
|
|
27
|
-
'border-warning/50 text-[color-mix(in_oklab,var(--color-warning)_62%,var(--color-foreground))]',
|
|
24
|
+
outline: 'border-warning/50 text-warning-text',
|
|
28
25
|
},
|
|
29
26
|
danger: {
|
|
30
|
-
soft: 'bg-danger/14 text-
|
|
27
|
+
soft: 'bg-danger/14 text-danger-text',
|
|
31
28
|
solid: 'bg-danger text-danger-foreground',
|
|
32
|
-
outline:
|
|
33
|
-
'border-danger/50 text-[color-mix(in_oklab,var(--color-danger)_62%,var(--color-foreground))]',
|
|
29
|
+
outline: 'border-danger/50 text-danger-text',
|
|
34
30
|
},
|
|
35
31
|
info: {
|
|
36
|
-
soft: 'bg-info/14 text-
|
|
32
|
+
soft: 'bg-info/14 text-info-text',
|
|
37
33
|
solid: 'bg-info text-info-foreground',
|
|
38
|
-
outline:
|
|
39
|
-
'border-info/50 text-[color-mix(in_oklab,var(--color-info)_62%,var(--color-foreground))]',
|
|
34
|
+
outline: 'border-info/50 text-info-text',
|
|
40
35
|
},
|
|
41
36
|
} as const
|
|
42
37
|
|
|
@@ -15,16 +15,16 @@ export const bannerStyles = tv({
|
|
|
15
15
|
neutral: { root: 'border-border bg-subtle text-foreground', icon: 'text-muted-foreground' },
|
|
16
16
|
primary: { root: 'border-primary bg-primary text-primary-foreground' },
|
|
17
17
|
info: {
|
|
18
|
-
root: 'border-info/25 bg-info/10 text-
|
|
18
|
+
root: 'border-info/25 bg-info/10 text-info-text',
|
|
19
19
|
},
|
|
20
20
|
success: {
|
|
21
|
-
root: 'border-success/25 bg-success/10 text-
|
|
21
|
+
root: 'border-success/25 bg-success/10 text-success-text',
|
|
22
22
|
},
|
|
23
23
|
warning: {
|
|
24
|
-
root: 'border-warning/30 bg-warning/14 text-
|
|
24
|
+
root: 'border-warning/30 bg-warning/14 text-warning-text',
|
|
25
25
|
},
|
|
26
26
|
danger: {
|
|
27
|
-
root: 'border-danger/25 bg-danger/10 text-
|
|
27
|
+
root: 'border-danger/25 bg-danger/10 text-danger-text',
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
30
|
},
|
|
@@ -22,7 +22,9 @@ export const buttonStyles = tv({
|
|
|
22
22
|
},
|
|
23
23
|
ghost: { base: 'text-muted-foreground hover:bg-muted hover:text-foreground' },
|
|
24
24
|
danger: { base: 'bg-danger text-danger-foreground shadow-sm hover:bg-danger/88' },
|
|
25
|
-
link: {
|
|
25
|
+
link: {
|
|
26
|
+
base: 'h-auto px-0 text-primary-text underline-offset-4 shadow-none hover:underline',
|
|
27
|
+
},
|
|
26
28
|
},
|
|
27
29
|
size: {
|
|
28
30
|
sm: { base: 'h-(--glyph-control-sm) px-3 text-small', icon: 'size-3.5' },
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { StylesEntry } from '../../utils/registry'
|
|
2
|
+
import { focusRing, transition } from '../../utils/styles'
|
|
3
|
+
import { tv } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
export const calendarStyles = tv({
|
|
6
|
+
slots: {
|
|
7
|
+
root: 'relative w-fit p-3 text-small text-foreground',
|
|
8
|
+
months: 'relative flex flex-col gap-6 sm:flex-row',
|
|
9
|
+
month: 'grid content-start gap-3',
|
|
10
|
+
monthCaption: 'flex h-8 items-center justify-center px-9',
|
|
11
|
+
captionLabel: 'font-medium text-foreground',
|
|
12
|
+
nav: 'pointer-events-none absolute inset-x-3 top-3 z-10 flex items-center justify-between',
|
|
13
|
+
navButton: [
|
|
14
|
+
'pointer-events-auto inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground',
|
|
15
|
+
'hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-40',
|
|
16
|
+
transition,
|
|
17
|
+
focusRing,
|
|
18
|
+
],
|
|
19
|
+
chevron: 'size-4',
|
|
20
|
+
monthGrid: 'border-collapse',
|
|
21
|
+
weekdays: '',
|
|
22
|
+
weekday: 'size-9 p-0 text-caption font-normal text-faint-foreground',
|
|
23
|
+
weeks: '',
|
|
24
|
+
week: '',
|
|
25
|
+
day: 'size-9 p-0 text-center align-middle',
|
|
26
|
+
dayButton: [
|
|
27
|
+
'inline-flex size-9 cursor-pointer items-center justify-center rounded-md text-small tabular-nums text-foreground',
|
|
28
|
+
'hover:bg-muted',
|
|
29
|
+
transition,
|
|
30
|
+
focusRing,
|
|
31
|
+
],
|
|
32
|
+
// Selected days keep primary-foreground on primary, whatever else applies to them.
|
|
33
|
+
today: '[&>button]:font-semibold [&:not([data-selected])>button]:text-primary-text',
|
|
34
|
+
selected:
|
|
35
|
+
'[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary/90',
|
|
36
|
+
outside: '[&:not([data-selected])>button]:text-faint-foreground',
|
|
37
|
+
disabled: '[&>button]:pointer-events-none [&>button]:opacity-35 [&>button]:line-through',
|
|
38
|
+
rangeStart: 'rounded-l-md bg-primary/14',
|
|
39
|
+
rangeMiddle:
|
|
40
|
+
'bg-primary/14 [&>button]:rounded-none [&>button]:bg-transparent! [&>button]:text-foreground! [&>button]:hover:bg-primary/10!',
|
|
41
|
+
rangeEnd: 'rounded-r-md bg-primary/14',
|
|
42
|
+
hidden: 'invisible',
|
|
43
|
+
footer: 'pt-2 text-small text-muted-foreground',
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
declare module '@glyphui/tokens' {
|
|
48
|
+
interface GlyphComponentRegistry {
|
|
49
|
+
Calendar: StylesEntry<typeof calendarStyles>
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DayPicker, type DayPickerProps } from 'react-day-picker'
|
|
2
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
3
|
+
import { calendarStyles } from './calendar.styles'
|
|
4
|
+
|
|
5
|
+
export type CalendarProps = DayPickerProps
|
|
6
|
+
|
|
7
|
+
function ChevronIcon({
|
|
8
|
+
className,
|
|
9
|
+
orientation = 'left',
|
|
10
|
+
}: {
|
|
11
|
+
className?: string
|
|
12
|
+
orientation?: 'up' | 'down' | 'left' | 'right'
|
|
13
|
+
}) {
|
|
14
|
+
const d = {
|
|
15
|
+
left: 'm15 18-6-6 6-6',
|
|
16
|
+
right: 'm9 18 6-6-6-6',
|
|
17
|
+
up: 'm18 15-6-6-6 6',
|
|
18
|
+
down: 'm6 9 6 6 6-6',
|
|
19
|
+
}[orientation]
|
|
20
|
+
return (
|
|
21
|
+
<svg
|
|
22
|
+
viewBox="0 0 24 24"
|
|
23
|
+
fill="none"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
strokeWidth="2"
|
|
26
|
+
strokeLinecap="round"
|
|
27
|
+
strokeLinejoin="round"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
className={className}
|
|
30
|
+
>
|
|
31
|
+
<path d={d} />
|
|
32
|
+
</svg>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A month grid for picking dates, built on react-day-picker and styled by the theme.
|
|
38
|
+
* Supports `mode="single" | "multiple" | "range"`, disabled dates and several months.
|
|
39
|
+
*/
|
|
40
|
+
export function Calendar(inProps: CalendarProps) {
|
|
41
|
+
const [props, slots] = useComponentConfig('Calendar', inProps)
|
|
42
|
+
const { className, classNames, components, showOutsideDays = true, ...rest } = props
|
|
43
|
+
const s = calendarStyles()
|
|
44
|
+
return (
|
|
45
|
+
<DayPicker
|
|
46
|
+
showOutsideDays={showOutsideDays}
|
|
47
|
+
className={s.root({ class: [slots.root, className] })}
|
|
48
|
+
classNames={{
|
|
49
|
+
months: s.months({ class: slots.months }),
|
|
50
|
+
month: s.month({ class: slots.month }),
|
|
51
|
+
month_caption: s.monthCaption({ class: slots.monthCaption }),
|
|
52
|
+
caption_label: s.captionLabel({ class: slots.captionLabel }),
|
|
53
|
+
nav: s.nav({ class: slots.nav }),
|
|
54
|
+
button_previous: s.navButton({ class: slots.navButton }),
|
|
55
|
+
button_next: s.navButton({ class: slots.navButton }),
|
|
56
|
+
chevron: s.chevron({ class: slots.chevron }),
|
|
57
|
+
month_grid: s.monthGrid({ class: slots.monthGrid }),
|
|
58
|
+
weekdays: s.weekdays({ class: slots.weekdays }),
|
|
59
|
+
weekday: s.weekday({ class: slots.weekday }),
|
|
60
|
+
weeks: s.weeks({ class: slots.weeks }),
|
|
61
|
+
week: s.week({ class: slots.week }),
|
|
62
|
+
day: s.day({ class: slots.day }),
|
|
63
|
+
day_button: s.dayButton({ class: slots.dayButton }),
|
|
64
|
+
today: s.today({ class: slots.today }),
|
|
65
|
+
selected: s.selected({ class: slots.selected }),
|
|
66
|
+
outside: s.outside({ class: slots.outside }),
|
|
67
|
+
disabled: s.disabled({ class: slots.disabled }),
|
|
68
|
+
range_start: s.rangeStart({ class: slots.rangeStart }),
|
|
69
|
+
range_middle: s.rangeMiddle({ class: slots.rangeMiddle }),
|
|
70
|
+
range_end: s.rangeEnd({ class: slots.rangeEnd }),
|
|
71
|
+
hidden: s.hidden({ class: slots.hidden }),
|
|
72
|
+
footer: s.footer({ class: slots.footer }),
|
|
73
|
+
...classNames,
|
|
74
|
+
}}
|
|
75
|
+
components={{
|
|
76
|
+
Chevron: ({ className: chevronClass, orientation }) => (
|
|
77
|
+
<ChevronIcon className={chevronClass} orientation={orientation} />
|
|
78
|
+
),
|
|
79
|
+
...components,
|
|
80
|
+
}}
|
|
81
|
+
{...(rest as DayPickerProps)}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { StylesEntry } from '../../utils/registry'
|
|
2
|
+
import { focusRing, transition } from '../../utils/styles'
|
|
3
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
export const carouselStyles = tv({
|
|
6
|
+
slots: {
|
|
7
|
+
root: 'relative',
|
|
8
|
+
viewport: 'overflow-hidden rounded-lg',
|
|
9
|
+
container: 'flex',
|
|
10
|
+
item: 'min-w-0 shrink-0 grow-0 basis-full',
|
|
11
|
+
control: 'absolute z-10 border-border bg-surface/85 shadow-sm backdrop-blur disabled:opacity-0',
|
|
12
|
+
dots: 'mt-3 flex items-center justify-center gap-1.5',
|
|
13
|
+
dot: [
|
|
14
|
+
'h-2 w-2 cursor-pointer rounded-pill bg-border-strong',
|
|
15
|
+
transition,
|
|
16
|
+
focusRing,
|
|
17
|
+
'hover:bg-muted-foreground aria-[current=true]:w-5 aria-[current=true]:bg-primary',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
variants: {
|
|
21
|
+
orientation: {
|
|
22
|
+
horizontal: {
|
|
23
|
+
container: '-ml-4',
|
|
24
|
+
item: 'pl-4',
|
|
25
|
+
control: 'top-1/2 -translate-y-1/2',
|
|
26
|
+
},
|
|
27
|
+
vertical: {
|
|
28
|
+
container: '-mt-4 h-full flex-col',
|
|
29
|
+
item: 'pt-4',
|
|
30
|
+
control: 'left-1/2 -translate-x-1/2 [&_svg]:rotate-90',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: { orientation: 'horizontal' },
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export type CarouselVariants = VariantProps<typeof carouselStyles>
|
|
38
|
+
|
|
39
|
+
declare module '@glyphui/tokens' {
|
|
40
|
+
interface GlyphComponentRegistry {
|
|
41
|
+
Carousel: StylesEntry<typeof carouselStyles>
|
|
42
|
+
}
|
|
43
|
+
}
|