@cntyclub/ui-react 0.4.1 → 0.6.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/dist/index.d.ts +61 -16
- package/dist/index.js +137 -84
- package/dist/index.js.map +1 -1
- package/package.json +31 -10
- package/src/components/ui/help-tip.tsx +94 -0
- package/src/components/ui/sidebar.tsx +33 -2
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cntyclub/ui-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React component library for the Country Club UI Kit — Base UI primitives styled with the Country Club design system (Tailwind CSS v4)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -28,6 +28,33 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"comment-publishConfig": "In-repo (docs/storybook/vue) consume src/ via the exports above. When PUBLISHED to a registry, npm/pnpm swap in these fields so external installs get the compiled, self-contained dist/ (tsup bundles every dependency except react/react-dom) — no transpilePackages, no heavy transitive dep tree.",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./form": {
|
|
42
|
+
"types": "./dist/form.d.ts",
|
|
43
|
+
"import": "./dist/form.js",
|
|
44
|
+
"default": "./dist/form.js"
|
|
45
|
+
},
|
|
46
|
+
"./styles.css": "./src/styles.css",
|
|
47
|
+
"./package.json": "./package.json"
|
|
48
|
+
},
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
56
|
+
"prepublishOnly": "tsup"
|
|
57
|
+
},
|
|
31
58
|
"dependencies": {
|
|
32
59
|
"@base-ui/react": "1.1.0",
|
|
33
60
|
"@headlessui/react": "^2.2.9",
|
|
@@ -64,18 +91,12 @@
|
|
|
64
91
|
"react-dom": "^19.0.0"
|
|
65
92
|
},
|
|
66
93
|
"devDependencies": {
|
|
94
|
+
"@countryclub/typescript-config": "workspace:*",
|
|
67
95
|
"@types/react": "^19.1.0",
|
|
68
96
|
"@types/react-dom": "^19.1.0",
|
|
69
97
|
"react": "^19.1.0",
|
|
70
98
|
"react-dom": "^19.1.0",
|
|
71
99
|
"tsup": "^8.4.0",
|
|
72
|
-
"typescript": "^5.8.3"
|
|
73
|
-
"@countryclub/typescript-config": "0.1.0"
|
|
74
|
-
},
|
|
75
|
-
"scripts": {
|
|
76
|
-
"build": "tsup",
|
|
77
|
-
"dev": "tsup --watch",
|
|
78
|
-
"typecheck": "tsc --noEmit",
|
|
79
|
-
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\""
|
|
100
|
+
"typescript": "^5.8.3"
|
|
80
101
|
}
|
|
81
|
-
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CircleHelp } from "lucide-react";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils/css";
|
|
7
|
+
import {
|
|
8
|
+
Tooltip,
|
|
9
|
+
TooltipDescription,
|
|
10
|
+
TooltipPopup,
|
|
11
|
+
TooltipProvider,
|
|
12
|
+
TooltipTitle,
|
|
13
|
+
TooltipTrigger,
|
|
14
|
+
} from "./tooltip";
|
|
15
|
+
|
|
16
|
+
export interface HelpTipProps {
|
|
17
|
+
/** The help text shown in the tooltip. */
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
/** Optional bold heading rendered above the description text. */
|
|
20
|
+
title?: React.ReactNode;
|
|
21
|
+
/** Which side of the trigger the tooltip opens on. Default "top". */
|
|
22
|
+
side?: React.ComponentProps<typeof TooltipPopup>["side"];
|
|
23
|
+
/** Alignment of the tooltip along the trigger. Default "center". */
|
|
24
|
+
align?: React.ComponentProps<typeof TooltipPopup>["align"];
|
|
25
|
+
/** Accessible label for the icon button. Default "More information". */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Hover / focus open delay in ms. Default 150. */
|
|
28
|
+
delay?: number;
|
|
29
|
+
/** Icon size in px. Default 16. */
|
|
30
|
+
iconSize?: number;
|
|
31
|
+
/** Extra classes for the trigger button. */
|
|
32
|
+
className?: string;
|
|
33
|
+
/** Extra classes for the tooltip popup. */
|
|
34
|
+
popupClassName?: string;
|
|
35
|
+
/** Render the little arrow pointing at the icon. Default false. */
|
|
36
|
+
arrow?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A small "?" help affordance: an unobtrusive circular icon that reveals
|
|
41
|
+
* supporting text in a tooltip on hover / focus. Use it next to a label,
|
|
42
|
+
* heading, or field when a short explanation would help but shouldn't take up
|
|
43
|
+
* permanent space.
|
|
44
|
+
*
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <span className="flex items-center gap-1.5">
|
|
47
|
+
* My Events
|
|
48
|
+
* <HelpTip>Every event Country Club has surfaced for you…</HelpTip>
|
|
49
|
+
* </span>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function HelpTip({
|
|
53
|
+
children,
|
|
54
|
+
title,
|
|
55
|
+
side = "top",
|
|
56
|
+
align = "center",
|
|
57
|
+
label = "More information",
|
|
58
|
+
delay = 150,
|
|
59
|
+
iconSize = 16,
|
|
60
|
+
className,
|
|
61
|
+
popupClassName,
|
|
62
|
+
arrow = false,
|
|
63
|
+
}: HelpTipProps) {
|
|
64
|
+
return (
|
|
65
|
+
<TooltipProvider delay={delay}>
|
|
66
|
+
<Tooltip>
|
|
67
|
+
<TooltipTrigger
|
|
68
|
+
aria-label={label}
|
|
69
|
+
className={cn(
|
|
70
|
+
"inline-flex shrink-0 cursor-help items-center justify-center rounded-full text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring",
|
|
71
|
+
className,
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
<CircleHelp aria-hidden size={iconSize} />
|
|
75
|
+
</TooltipTrigger>
|
|
76
|
+
<TooltipPopup
|
|
77
|
+
align={align}
|
|
78
|
+
arrow={arrow}
|
|
79
|
+
className={cn("max-w-64 [--viewport-inline-padding:--spacing(3)]", popupClassName)}
|
|
80
|
+
side={side}
|
|
81
|
+
>
|
|
82
|
+
{title ? (
|
|
83
|
+
<div className="flex flex-col gap-0.5 py-0.5">
|
|
84
|
+
<TooltipTitle>{title}</TooltipTitle>
|
|
85
|
+
<TooltipDescription className="text-popover-foreground">{children}</TooltipDescription>
|
|
86
|
+
</div>
|
|
87
|
+
) : (
|
|
88
|
+
<div className="py-0.5 leading-snug">{children}</div>
|
|
89
|
+
)}
|
|
90
|
+
</TooltipPopup>
|
|
91
|
+
</Tooltip>
|
|
92
|
+
</TooltipProvider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -58,6 +58,7 @@ function SidebarProvider({
|
|
|
58
58
|
defaultOpen = true,
|
|
59
59
|
open: openProp,
|
|
60
60
|
onOpenChange: setOpenProp,
|
|
61
|
+
persistKey,
|
|
61
62
|
className,
|
|
62
63
|
style,
|
|
63
64
|
children,
|
|
@@ -66,6 +67,14 @@ function SidebarProvider({
|
|
|
66
67
|
defaultOpen?: boolean;
|
|
67
68
|
open?: boolean;
|
|
68
69
|
onOpenChange?: (open: boolean) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Remember the expanded/collapsed state in `localStorage` under this key and
|
|
72
|
+
* restore it on the next load (desktop rail only; the mobile sheet is always
|
|
73
|
+
* closed on load). Opt-in — omit to keep the sidebar un-persisted. Ignored in
|
|
74
|
+
* controlled mode (when `open` is provided). The restore runs before first
|
|
75
|
+
* paint, so there's no expand→collapse flash.
|
|
76
|
+
*/
|
|
77
|
+
persistKey?: string;
|
|
69
78
|
}) {
|
|
70
79
|
// Self-contained breakpoint check (md = 48rem). Resolved in a layout effect
|
|
71
80
|
// before the first paint so the sidebar never flashes from mobile to desktop.
|
|
@@ -93,16 +102,38 @@ function SidebarProvider({
|
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
// Persist the sidebar state. document.cookie works in every browser
|
|
96
|
-
// (cookieStore is Chromium-only and throws elsewhere).
|
|
105
|
+
// (cookieStore is Chromium-only and throws elsewhere). When `persistKey`
|
|
106
|
+
// is set we also mirror it to localStorage so a client-only app (no SSR
|
|
107
|
+
// cookie read) can restore it on load.
|
|
97
108
|
try {
|
|
98
109
|
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
110
|
+
if (persistKey) {
|
|
111
|
+
window.localStorage.setItem(persistKey, openState ? "true" : "false");
|
|
112
|
+
}
|
|
99
113
|
} catch {
|
|
100
114
|
// Ignore — persistence is best-effort.
|
|
101
115
|
}
|
|
102
116
|
},
|
|
103
|
-
[setOpenProp, open],
|
|
117
|
+
[setOpenProp, open, persistKey],
|
|
104
118
|
);
|
|
105
119
|
|
|
120
|
+
// Restore the persisted (localStorage) open state on mount — uncontrolled use
|
|
121
|
+
// only. Runs in a layout effect so the restore lands before first paint (no
|
|
122
|
+
// expand→collapse flash). SSR renders `defaultOpen` and the client's first
|
|
123
|
+
// render matches it, so this reconciles afterwards without a hydration
|
|
124
|
+
// mismatch.
|
|
125
|
+
useIsomorphicLayoutEffect(() => {
|
|
126
|
+
if (!persistKey || openProp !== undefined) return;
|
|
127
|
+
try {
|
|
128
|
+
const stored = window.localStorage.getItem(persistKey);
|
|
129
|
+
if (stored === "true" || stored === "false") {
|
|
130
|
+
_setOpen(stored === "true");
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// Ignore — restore is best-effort.
|
|
134
|
+
}
|
|
135
|
+
}, [persistKey, openProp]);
|
|
136
|
+
|
|
106
137
|
// Helper to toggle the sidebar.
|
|
107
138
|
const toggleSidebar = React.useCallback(() => {
|
|
108
139
|
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ export * from "./components/ui/form";
|
|
|
39
39
|
export * from "./components/ui/frame";
|
|
40
40
|
export * from "./components/ui/generic-empty";
|
|
41
41
|
export * from "./components/ui/group";
|
|
42
|
+
export * from "./components/ui/help-tip";
|
|
42
43
|
export * from "./components/ui/horizontal-scroll-fader";
|
|
43
44
|
export * from "./components/ui/input";
|
|
44
45
|
export * from "./components/ui/input-group";
|