@hypoth-ui/cli 0.1.0 → 0.1.2
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/{add-V5PW73GC.js → add-SZWOYJL5.js} +24 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/_shared/hooks/use-responsive-classes.ts +92 -0
- package/templates/_shared/hooks/use-stable-id.ts +87 -0
- package/templates/_shared/primitives/portal.tsx +70 -0
- package/templates/_shared/primitives/responsive.ts +269 -0
- package/templates/_shared/primitives/slot.tsx +103 -0
- package/templates/_shared/types/events.ts +21 -0
- package/templates/_shared/types/polymorphic.ts +32 -0
- package/templates/_shared/utils/create-context.ts +22 -0
- package/templates/_shared/utils/merge-props.ts +88 -0
- package/templates/avatar/avatar-group.tsx +1 -1
- package/templates/avatar/avatar.tsx +1 -1
- package/templates/badge/index.tsx +1 -1
- package/templates/button/button.tsx +1 -1
- package/templates/combobox/combobox-context.ts +1 -1
- package/templates/date-picker/date-picker-context.ts +1 -1
- package/templates/date-picker/date-picker-trigger.tsx +1 -1
- package/templates/dialog/dialog-close.tsx +1 -1
- package/templates/dialog/dialog-context.ts +1 -1
- package/templates/dialog/dialog-root.tsx +1 -1
- package/templates/dialog/dialog-trigger.tsx +1 -1
- package/templates/file-upload/file-upload-context.ts +1 -1
- package/templates/icon/icon.tsx +1 -1
- package/templates/layout/flow.tsx +1 -1
- package/templates/layout/grid.tsx +1 -1
- package/templates/link/link.tsx +4 -4
- package/templates/menu/menu-context.ts +1 -1
- package/templates/menu/menu-root.tsx +1 -1
- package/templates/menu/menu-trigger.tsx +1 -1
- package/templates/number-input/number-input-context.ts +1 -1
- package/templates/pin-input/pin-input-context.ts +1 -1
- package/templates/progress/index.tsx +1 -1
- package/templates/select/select-context.ts +1 -1
- package/templates/select/select-root.tsx +1 -1
- package/templates/select/select-trigger.tsx +1 -1
- package/templates/skeleton/index.tsx +1 -1
- package/templates/slider/slider-context.ts +1 -1
- package/templates/spinner/spinner.tsx +1 -1
- package/templates/tag/index.tsx +1 -1
- package/templates/text/text.tsx +3 -3
- package/templates/textarea/textarea.tsx +1 -1
- package/templates/time-picker/time-picker-context.ts +1 -1
- package/templates/toast/provider.tsx +1 -1
|
@@ -21,7 +21,7 @@ import * as p from "@clack/prompts";
|
|
|
21
21
|
import pc from "picocolors";
|
|
22
22
|
|
|
23
23
|
// src/utils/copy.ts
|
|
24
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
25
25
|
import { dirname, join } from "path";
|
|
26
26
|
async function copyComponentFiles(component, config, options = {}) {
|
|
27
27
|
const { cwd = process.cwd(), overwrite = false, sourceDir } = options;
|
|
@@ -32,6 +32,7 @@ async function copyComponentFiles(component, config, options = {}) {
|
|
|
32
32
|
skipped: [],
|
|
33
33
|
errors: []
|
|
34
34
|
};
|
|
35
|
+
copySharedFiles(targetDir, overwrite);
|
|
35
36
|
for (const file of files) {
|
|
36
37
|
const targetPath = join(targetDir, component.name, file.target);
|
|
37
38
|
if (existsSync(targetPath) && !overwrite) {
|
|
@@ -91,6 +92,28 @@ function getBundledTemplatePath(componentName, filePath) {
|
|
|
91
92
|
const __dirname2 = new URL(".", import.meta.url).pathname;
|
|
92
93
|
return join(__dirname2, "../../templates", componentName, filePath);
|
|
93
94
|
}
|
|
95
|
+
function copySharedFiles(targetDir, overwrite) {
|
|
96
|
+
const __dirname2 = new URL(".", import.meta.url).pathname;
|
|
97
|
+
const sharedSourceDir = join(__dirname2, "../../templates/_shared");
|
|
98
|
+
if (!existsSync(sharedSourceDir)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
function copyDir(srcDir, destDir) {
|
|
102
|
+
if (!existsSync(destDir)) {
|
|
103
|
+
mkdirSync(destDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
106
|
+
const srcPath = join(srcDir, entry.name);
|
|
107
|
+
const destPath = join(destDir, entry.name);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
copyDir(srcPath, destPath);
|
|
110
|
+
} else if (!existsSync(destPath) || overwrite) {
|
|
111
|
+
writeFileSync(destPath, readFileSync(srcPath, "utf-8"), "utf-8");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
copyDir(sharedSourceDir, join(targetDir, "_shared"));
|
|
116
|
+
}
|
|
94
117
|
function transformImports(content, config, fileType) {
|
|
95
118
|
if (fileType !== "ts" && fileType !== "tsx") {
|
|
96
119
|
return content;
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ program.command("init").description("Initialize hypoth-ui in your project").opti
|
|
|
10
10
|
await initCommand(options);
|
|
11
11
|
});
|
|
12
12
|
program.command("add").description("Add components to your project").argument("[components...]", "Component names to add").option("-c, --copy", "Copy component source files instead of installing package").option("-o, --overwrite", "Overwrite existing components").option("-a, --all", "Add all available components").action(async (components, options) => {
|
|
13
|
-
const { addCommand } = await import("./add-
|
|
13
|
+
const { addCommand } = await import("./add-SZWOYJL5.js");
|
|
14
14
|
await addCommand(components, options);
|
|
15
15
|
});
|
|
16
16
|
program.command("list").description("List available components").option("-j, --json", "Output as JSON").action(async (options) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Responsive Classes Hook
|
|
3
|
+
*
|
|
4
|
+
* Converts React responsive object syntax to CSS classes.
|
|
5
|
+
* React syntax: { base: "column", md: "row" }
|
|
6
|
+
* Output: ["ds-flow--md:dir-row"]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useMemo } from "react";
|
|
10
|
+
|
|
11
|
+
type BreakpointToken = "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
|
|
12
|
+
|
|
13
|
+
export type ResponsiveValue<T> = T | Partial<Record<"base" | BreakpointToken, T>>;
|
|
14
|
+
|
|
15
|
+
function isResponsiveObject<T>(
|
|
16
|
+
value: ResponsiveValue<T>
|
|
17
|
+
): value is Partial<Record<"base" | BreakpointToken, T>> {
|
|
18
|
+
return typeof value === "object" && value !== null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a responsive value into base value and classes.
|
|
23
|
+
*/
|
|
24
|
+
export function parseResponsiveValue<T extends string>(
|
|
25
|
+
component: string,
|
|
26
|
+
property: string,
|
|
27
|
+
value: ResponsiveValue<T>
|
|
28
|
+
): { baseValue: T | undefined; classes: string[] } {
|
|
29
|
+
if (!isResponsiveObject(value)) {
|
|
30
|
+
return { baseValue: value, classes: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const classes: string[] = [];
|
|
34
|
+
let baseValue: T | undefined;
|
|
35
|
+
|
|
36
|
+
for (const [key, val] of Object.entries(value)) {
|
|
37
|
+
if (val === undefined) continue;
|
|
38
|
+
|
|
39
|
+
if (key === "base") {
|
|
40
|
+
baseValue = val as T;
|
|
41
|
+
} else {
|
|
42
|
+
classes.push(`ds-${component}--${key}:${property}-${val}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { baseValue, classes };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hook to generate responsive CSS classes from React props.
|
|
51
|
+
*/
|
|
52
|
+
export function useResponsiveClasses<T extends Record<string, ResponsiveValue<string> | undefined>>(
|
|
53
|
+
component: string,
|
|
54
|
+
props: T,
|
|
55
|
+
propertyMap: Record<keyof T, string>
|
|
56
|
+
): { baseValues: Partial<Record<keyof T, string>>; classes: string[] } {
|
|
57
|
+
return useMemo(() => {
|
|
58
|
+
const baseValues: Partial<Record<keyof T, string>> = {};
|
|
59
|
+
const allClasses: string[] = [];
|
|
60
|
+
|
|
61
|
+
for (const [propKey, propValue] of Object.entries(props)) {
|
|
62
|
+
if (propValue === undefined) continue;
|
|
63
|
+
|
|
64
|
+
const shortName = propertyMap[propKey as keyof T];
|
|
65
|
+
if (!shortName) continue;
|
|
66
|
+
|
|
67
|
+
const { baseValue, classes } = parseResponsiveValue(
|
|
68
|
+
component,
|
|
69
|
+
shortName,
|
|
70
|
+
propValue as ResponsiveValue<string>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (baseValue !== undefined) {
|
|
74
|
+
baseValues[propKey as keyof T] = baseValue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
allClasses.push(...classes);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { baseValues, classes: allClasses };
|
|
81
|
+
}, [component, props, propertyMap]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the base value from a responsive value.
|
|
86
|
+
*/
|
|
87
|
+
export function getBaseValue<T>(value: ResponsiveValue<T>): T | undefined {
|
|
88
|
+
if (!isResponsiveObject(value)) {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
return value.base;
|
|
92
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR-Safe ID Generation Hooks
|
|
3
|
+
*
|
|
4
|
+
* Provides React 18 useId()-based hooks for stable ID generation
|
|
5
|
+
* that works correctly with SSR and hydration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useId, useMemo } from "react";
|
|
9
|
+
|
|
10
|
+
export interface UseStableIdOptions {
|
|
11
|
+
/** Custom ID to use instead of auto-generated one. */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** Prefix for the generated ID. @default "ds" */
|
|
14
|
+
prefix?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseStableIdsOptions<T extends string> extends UseStableIdOptions {
|
|
18
|
+
/** Parts to generate related IDs for. */
|
|
19
|
+
parts: readonly T[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type StableIds<T extends string> = {
|
|
23
|
+
/** The base ID */
|
|
24
|
+
baseId: string;
|
|
25
|
+
} & Record<T, string>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate a stable, SSR-safe ID for a component.
|
|
29
|
+
* Uses React 18's useId() under the hood.
|
|
30
|
+
*/
|
|
31
|
+
export function useStableId(options: UseStableIdOptions = {}): string {
|
|
32
|
+
const { id: customId, prefix = "ds" } = options;
|
|
33
|
+
|
|
34
|
+
const reactId = useId();
|
|
35
|
+
|
|
36
|
+
const stableId = useMemo(() => {
|
|
37
|
+
if (customId) {
|
|
38
|
+
return customId;
|
|
39
|
+
}
|
|
40
|
+
const cleanId = reactId.replace(/:/g, "");
|
|
41
|
+
return `${prefix}-${cleanId}`;
|
|
42
|
+
}, [customId, prefix, reactId]);
|
|
43
|
+
|
|
44
|
+
return stableId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate multiple related stable IDs for a component with multiple ARIA relationships.
|
|
49
|
+
*/
|
|
50
|
+
export function useStableIds<T extends string>(
|
|
51
|
+
options: UseStableIdsOptions<T>
|
|
52
|
+
): StableIds<T> {
|
|
53
|
+
const { id: customId, prefix = "ds", parts } = options;
|
|
54
|
+
|
|
55
|
+
const baseId = useStableId({ id: customId, prefix });
|
|
56
|
+
|
|
57
|
+
const ids = useMemo(() => {
|
|
58
|
+
const result: Record<string, string> = { baseId };
|
|
59
|
+
for (const part of parts) {
|
|
60
|
+
result[part] = `${baseId}-${part}`;
|
|
61
|
+
}
|
|
62
|
+
return result as StableIds<T>;
|
|
63
|
+
}, [baseId, parts]);
|
|
64
|
+
|
|
65
|
+
return ids;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create a scoped ID generator function from a base ID.
|
|
70
|
+
*/
|
|
71
|
+
export function useScopedIdGenerator(baseId: string): (suffix: string) => string {
|
|
72
|
+
return useMemo(() => {
|
|
73
|
+
return (suffix: string) => `${baseId}-${suffix}`;
|
|
74
|
+
}, [baseId]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Hook to conditionally include an ID attribute.
|
|
79
|
+
*/
|
|
80
|
+
export function useConditionalId(id: string | undefined): string | undefined {
|
|
81
|
+
return useMemo(() => {
|
|
82
|
+
if (!id || id.trim() === "") {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
return id;
|
|
86
|
+
}, [id]);
|
|
87
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portal React Component
|
|
5
|
+
*
|
|
6
|
+
* Renders children into a DOM node outside the parent component hierarchy.
|
|
7
|
+
* Useful for modals, tooltips, dropdowns, and other overlay patterns.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type ReactNode, useEffect, useState } from "react";
|
|
11
|
+
import { createPortal } from "react-dom";
|
|
12
|
+
|
|
13
|
+
export interface PortalProps {
|
|
14
|
+
/** Content to render in the portal */
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
/** Container element to render into (defaults to document.body) */
|
|
17
|
+
container?: Element | null;
|
|
18
|
+
/** CSS selector for container element (alternative to container prop) */
|
|
19
|
+
containerSelector?: string;
|
|
20
|
+
/** Whether the portal is disabled (renders children in place) */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the portal container element
|
|
26
|
+
*/
|
|
27
|
+
function getContainer(container?: Element | null, containerSelector?: string): Element | null {
|
|
28
|
+
if (typeof document === "undefined") return null;
|
|
29
|
+
|
|
30
|
+
if (container) return container;
|
|
31
|
+
|
|
32
|
+
if (containerSelector) {
|
|
33
|
+
return document.querySelector(containerSelector);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return document.body;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Portal component for rendering content outside the normal React tree.
|
|
41
|
+
*/
|
|
42
|
+
export function Portal({
|
|
43
|
+
children,
|
|
44
|
+
container,
|
|
45
|
+
containerSelector,
|
|
46
|
+
disabled = false,
|
|
47
|
+
}: PortalProps): ReactNode {
|
|
48
|
+
const [mounted, setMounted] = useState(false);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setMounted(true);
|
|
52
|
+
return () => setMounted(false);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
// If disabled or not mounted (SSR), render children in place
|
|
56
|
+
if (disabled || !mounted) {
|
|
57
|
+
return <>{children}</>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const portalContainer = getContainer(container, containerSelector);
|
|
61
|
+
|
|
62
|
+
// If no container found, render in place
|
|
63
|
+
if (!portalContainer) {
|
|
64
|
+
return <>{children}</>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return createPortal(children, portalContainer);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Portal.displayName = "Portal";
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides types and utilities for breakpoint-aware component props.
|
|
5
|
+
* Supports responsive object syntax for size and other props.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Breakpoint names matching the design token system
|
|
12
|
+
*/
|
|
13
|
+
export type Breakpoint = "base" | "sm" | "md" | "lg" | "xl" | "2xl";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Breakpoint values in pixels (min-width)
|
|
17
|
+
*/
|
|
18
|
+
export const BREAKPOINTS: Record<Breakpoint, number> = {
|
|
19
|
+
base: 0,
|
|
20
|
+
sm: 640,
|
|
21
|
+
md: 768,
|
|
22
|
+
lg: 1024,
|
|
23
|
+
xl: 1280,
|
|
24
|
+
"2xl": 1536,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Breakpoint media query strings
|
|
29
|
+
*/
|
|
30
|
+
export const BREAKPOINT_QUERIES: Record<Breakpoint, string> = {
|
|
31
|
+
base: "",
|
|
32
|
+
sm: "(min-width: 640px)",
|
|
33
|
+
md: "(min-width: 768px)",
|
|
34
|
+
lg: "(min-width: 1024px)",
|
|
35
|
+
xl: "(min-width: 1280px)",
|
|
36
|
+
"2xl": "(min-width: 1536px)",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ordered list of breakpoints for cascade
|
|
41
|
+
*/
|
|
42
|
+
export const BREAKPOINT_ORDER: Breakpoint[] = [
|
|
43
|
+
"base",
|
|
44
|
+
"sm",
|
|
45
|
+
"md",
|
|
46
|
+
"lg",
|
|
47
|
+
"xl",
|
|
48
|
+
"2xl",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Responsive prop value type
|
|
53
|
+
*
|
|
54
|
+
* Can be either:
|
|
55
|
+
* - A single value (applies to all breakpoints)
|
|
56
|
+
* - An object with breakpoint keys (applies at each breakpoint)
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* // Single value
|
|
61
|
+
* <Button size="md" />
|
|
62
|
+
*
|
|
63
|
+
* // Responsive object
|
|
64
|
+
* <Button size={{ base: "sm", md: "md", lg: "lg" }} />
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export type ResponsiveProp<T> = T | Partial<Record<Breakpoint, T>>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a value is a responsive object
|
|
71
|
+
*/
|
|
72
|
+
export function isResponsiveObject<T>(
|
|
73
|
+
value: ResponsiveProp<T>
|
|
74
|
+
): value is Partial<Record<Breakpoint, T>> {
|
|
75
|
+
return (
|
|
76
|
+
typeof value === "object" &&
|
|
77
|
+
value !== null &&
|
|
78
|
+
!Array.isArray(value) &&
|
|
79
|
+
BREAKPOINT_ORDER.some((bp) => bp in (value as object))
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the value for a specific breakpoint, with cascade fallback
|
|
85
|
+
*/
|
|
86
|
+
export function getValueAtBreakpoint<T>(
|
|
87
|
+
value: ResponsiveProp<T>,
|
|
88
|
+
breakpoint: Breakpoint
|
|
89
|
+
): T | undefined {
|
|
90
|
+
if (!isResponsiveObject(value)) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const targetIndex = BREAKPOINT_ORDER.indexOf(breakpoint);
|
|
95
|
+
|
|
96
|
+
for (let i = targetIndex; i >= 0; i--) {
|
|
97
|
+
const bp = BREAKPOINT_ORDER[i];
|
|
98
|
+
if (bp !== undefined && bp in value) {
|
|
99
|
+
return value[bp];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the base value from a responsive prop
|
|
108
|
+
*/
|
|
109
|
+
export function getBaseValue<T>(value: ResponsiveProp<T>): T | undefined {
|
|
110
|
+
return getValueAtBreakpoint(value, "base");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolve a responsive prop to a flat value
|
|
115
|
+
* Uses the base value or first defined value
|
|
116
|
+
*/
|
|
117
|
+
export function resolveResponsiveValue<T>(
|
|
118
|
+
value: ResponsiveProp<T>,
|
|
119
|
+
defaultValue: T
|
|
120
|
+
): T {
|
|
121
|
+
if (!isResponsiveObject(value)) {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const bp of BREAKPOINT_ORDER) {
|
|
126
|
+
if (bp in value && value[bp] !== undefined) {
|
|
127
|
+
return value[bp] as T;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return defaultValue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Generate CSS custom properties for responsive values
|
|
136
|
+
*/
|
|
137
|
+
export function generateResponsiveCssVars<T extends string>(
|
|
138
|
+
propName: string,
|
|
139
|
+
value: ResponsiveProp<T>
|
|
140
|
+
): Record<string, string> {
|
|
141
|
+
const vars: Record<string, string> = {};
|
|
142
|
+
|
|
143
|
+
if (!isResponsiveObject(value)) {
|
|
144
|
+
vars[`--${propName}`] = value;
|
|
145
|
+
return vars;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const bp of BREAKPOINT_ORDER) {
|
|
149
|
+
if (bp in value && value[bp] !== undefined) {
|
|
150
|
+
if (bp === "base") {
|
|
151
|
+
vars[`--${propName}`] = value[bp] as string;
|
|
152
|
+
} else {
|
|
153
|
+
vars[`--${propName}-${bp}`] = value[bp] as string;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return vars;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generate data attributes for responsive values (for CSS targeting)
|
|
163
|
+
*/
|
|
164
|
+
export function generateResponsiveDataAttr<T extends string>(
|
|
165
|
+
value: ResponsiveProp<T>
|
|
166
|
+
): string {
|
|
167
|
+
if (!isResponsiveObject(value)) {
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const parts: string[] = [];
|
|
172
|
+
|
|
173
|
+
for (const bp of BREAKPOINT_ORDER) {
|
|
174
|
+
if (bp in value && value[bp] !== undefined) {
|
|
175
|
+
if (bp === "base") {
|
|
176
|
+
parts.push(value[bp] as string);
|
|
177
|
+
} else {
|
|
178
|
+
parts.push(`${bp}:${value[bp]}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return parts.join(" ");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Hook to get current breakpoint (client-side only)
|
|
188
|
+
*/
|
|
189
|
+
export function useBreakpoint(): Breakpoint {
|
|
190
|
+
if (typeof window === "undefined") {
|
|
191
|
+
return "base";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const width = window.innerWidth;
|
|
195
|
+
|
|
196
|
+
if (width >= BREAKPOINTS["2xl"]) return "2xl";
|
|
197
|
+
if (width >= BREAKPOINTS.xl) return "xl";
|
|
198
|
+
if (width >= BREAKPOINTS.lg) return "lg";
|
|
199
|
+
if (width >= BREAKPOINTS.md) return "md";
|
|
200
|
+
if (width >= BREAKPOINTS.sm) return "sm";
|
|
201
|
+
return "base";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Hook to check if a breakpoint is active
|
|
206
|
+
*/
|
|
207
|
+
export function useBreakpointValue<T>(
|
|
208
|
+
breakpoint: Breakpoint,
|
|
209
|
+
below: T,
|
|
210
|
+
atOrAbove: T
|
|
211
|
+
): T {
|
|
212
|
+
const currentBreakpoint = useBreakpoint();
|
|
213
|
+
const currentIndex = BREAKPOINT_ORDER.indexOf(currentBreakpoint);
|
|
214
|
+
const targetIndex = BREAKPOINT_ORDER.indexOf(breakpoint);
|
|
215
|
+
|
|
216
|
+
return currentIndex >= targetIndex ? atOrAbove : below;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get responsive value for current breakpoint
|
|
221
|
+
*/
|
|
222
|
+
export function useResponsiveValue<T>(
|
|
223
|
+
value: ResponsiveProp<T>,
|
|
224
|
+
defaultValue: T
|
|
225
|
+
): T {
|
|
226
|
+
const breakpoint = useBreakpoint();
|
|
227
|
+
const resolved = getValueAtBreakpoint(value, breakpoint);
|
|
228
|
+
return resolved ?? defaultValue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Type helper for component props that support responsive values
|
|
233
|
+
*/
|
|
234
|
+
export type WithResponsive<T, K extends keyof T> = Omit<T, K> & {
|
|
235
|
+
[P in K]?: ResponsiveProp<NonNullable<T[P]>>;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* CSS generation utilities for responsive styles
|
|
240
|
+
*/
|
|
241
|
+
export const responsiveCss = {
|
|
242
|
+
/**
|
|
243
|
+
* Generate media query CSS for responsive prop
|
|
244
|
+
*/
|
|
245
|
+
generateMediaQueries<T extends string>(
|
|
246
|
+
_propName: string,
|
|
247
|
+
value: ResponsiveProp<T>,
|
|
248
|
+
cssGenerator: (val: T) => string
|
|
249
|
+
): string {
|
|
250
|
+
if (!isResponsiveObject(value)) {
|
|
251
|
+
return cssGenerator(value);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const rules: string[] = [];
|
|
255
|
+
|
|
256
|
+
for (const bp of BREAKPOINT_ORDER) {
|
|
257
|
+
if (bp in value && value[bp] !== undefined) {
|
|
258
|
+
const css = cssGenerator(value[bp] as T);
|
|
259
|
+
if (bp === "base") {
|
|
260
|
+
rules.push(css);
|
|
261
|
+
} else {
|
|
262
|
+
rules.push(`@media ${BREAKPOINT_QUERIES[bp]} { ${css} }`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return rules.join("\n");
|
|
268
|
+
},
|
|
269
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
type HTMLAttributes,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
type Ref,
|
|
6
|
+
cloneElement,
|
|
7
|
+
forwardRef,
|
|
8
|
+
isValidElement,
|
|
9
|
+
useEffect,
|
|
10
|
+
useRef,
|
|
11
|
+
} from "react";
|
|
12
|
+
import { mergeProps } from "../utils/merge-props.js";
|
|
13
|
+
|
|
14
|
+
export interface SlotProps extends HTMLAttributes<HTMLElement> {
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Merges two refs into a single ref callback.
|
|
20
|
+
*/
|
|
21
|
+
function mergeRefs<T>(...refs: (Ref<T> | undefined)[]): (instance: T | null) => void {
|
|
22
|
+
return (instance: T | null) => {
|
|
23
|
+
for (const ref of refs) {
|
|
24
|
+
if (typeof ref === "function") {
|
|
25
|
+
ref(instance);
|
|
26
|
+
} else if (ref && typeof ref === "object") {
|
|
27
|
+
(ref as { current: T | null }).current = instance;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Slot renders its child element with merged props.
|
|
35
|
+
* Used internally by asChild implementations.
|
|
36
|
+
*
|
|
37
|
+
* - Validates that children is a single React element
|
|
38
|
+
* - Merges props (className concatenated, styles merged, events composed)
|
|
39
|
+
* - Forwards refs to the child element
|
|
40
|
+
*/
|
|
41
|
+
export const Slot = forwardRef<HTMLElement, SlotProps>(
|
|
42
|
+
({ children, ...slotProps }, forwardedRef) => {
|
|
43
|
+
const childrenArray = Children.toArray(children);
|
|
44
|
+
const internalRef = useRef<HTMLElement | null>(null);
|
|
45
|
+
|
|
46
|
+
// Filter out null/undefined/boolean children
|
|
47
|
+
const validChildren = childrenArray.filter(
|
|
48
|
+
(child) => child !== null && child !== undefined && typeof child !== "boolean"
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (validChildren.length === 0) {
|
|
52
|
+
throw new Error("Slot expects a single React element child");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (validChildren.length > 1) {
|
|
56
|
+
throw new Error("Slot expects a single React element child, received multiple children");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const child = validChildren[0];
|
|
60
|
+
|
|
61
|
+
if (!isValidElement(child)) {
|
|
62
|
+
throw new Error(`Slot expects a single React element child, received ${typeof child}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check if child is a custom component (not an intrinsic element)
|
|
66
|
+
const isCustomComponent = typeof child.type === "function" || typeof child.type === "object";
|
|
67
|
+
|
|
68
|
+
// Warn in development if the child component doesn't forward refs
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (
|
|
71
|
+
process.env.NODE_ENV !== "production" &&
|
|
72
|
+
isCustomComponent &&
|
|
73
|
+
internalRef.current === null
|
|
74
|
+
) {
|
|
75
|
+
const componentName =
|
|
76
|
+
typeof child.type === "function"
|
|
77
|
+
? (child.type as { displayName?: string; name?: string }).displayName ||
|
|
78
|
+
(child.type as { name?: string }).name ||
|
|
79
|
+
"Component"
|
|
80
|
+
: "Component";
|
|
81
|
+
console.warn(
|
|
82
|
+
`[Slot] The child component "${componentName}" doesn't forward refs. When using asChild, ensure the child component is wrapped with React.forwardRef() and forwards the ref to its underlying DOM element. This is required for proper focus management and accessibility.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}, [isCustomComponent, child.type]);
|
|
86
|
+
|
|
87
|
+
// Get child's ref if it exists
|
|
88
|
+
const childRef = (child as { ref?: Ref<HTMLElement> }).ref;
|
|
89
|
+
|
|
90
|
+
// Merge props and refs
|
|
91
|
+
const mergedProps = mergeProps(
|
|
92
|
+
slotProps as Record<string, unknown>,
|
|
93
|
+
child.props as Record<string, unknown>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return cloneElement(child, {
|
|
97
|
+
...mergedProps,
|
|
98
|
+
ref: mergeRefs(forwardedRef, childRef, internalRef),
|
|
99
|
+
} as Record<string, unknown>);
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
Slot.displayName = "Slot";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event type definitions for React wrapper components.
|
|
3
|
+
* These types match the custom events emitted by Web Components.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DsNavigateEventDetail {
|
|
7
|
+
/** The target URL */
|
|
8
|
+
href: string;
|
|
9
|
+
/** Whether the link opens in a new tab */
|
|
10
|
+
external: boolean;
|
|
11
|
+
/** The original DOM event that triggered navigation */
|
|
12
|
+
originalEvent: MouseEvent | KeyboardEvent;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DsInputEventDetail {
|
|
16
|
+
value: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type NavigateEventHandler = (event: CustomEvent<DsNavigateEventDetail>) => void;
|
|
20
|
+
|
|
21
|
+
export type InputValueHandler = (value: string, event: CustomEvent<DsInputEventDetail>) => void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polymorphic type utilities for asChild pattern.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type SpacingValue = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16 | 20 | 24;
|
|
6
|
+
|
|
7
|
+
export type DisplayValue =
|
|
8
|
+
| "block"
|
|
9
|
+
| "inline"
|
|
10
|
+
| "inline-block"
|
|
11
|
+
| "flex"
|
|
12
|
+
| "inline-flex"
|
|
13
|
+
| "grid"
|
|
14
|
+
| "inline-grid"
|
|
15
|
+
| "none";
|
|
16
|
+
|
|
17
|
+
export type FlexDirection = "row" | "row-reverse" | "column" | "column-reverse";
|
|
18
|
+
|
|
19
|
+
export type AlignValue = "start" | "end" | "center" | "stretch" | "baseline";
|
|
20
|
+
|
|
21
|
+
export type JustifyValue = "start" | "end" | "center" | "between" | "around" | "evenly";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base props for components supporting asChild pattern.
|
|
25
|
+
*/
|
|
26
|
+
export interface AsChildProps {
|
|
27
|
+
/**
|
|
28
|
+
* When true, the component renders its child element with merged props
|
|
29
|
+
* instead of its default element.
|
|
30
|
+
*/
|
|
31
|
+
asChild?: boolean;
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Provider, createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a compound component context with a built-in hook.
|
|
5
|
+
* Throws a helpful error when used outside the provider.
|
|
6
|
+
*/
|
|
7
|
+
export function createCompoundContext<T>(
|
|
8
|
+
displayName: string
|
|
9
|
+
): [Provider<T>, (consumerName: string) => T] {
|
|
10
|
+
const Context = createContext<T | null>(null);
|
|
11
|
+
Context.displayName = displayName;
|
|
12
|
+
|
|
13
|
+
function useCompoundContext(consumerName: string): T {
|
|
14
|
+
const context = useContext(Context);
|
|
15
|
+
if (context === null) {
|
|
16
|
+
throw new Error(`<${consumerName}> must be used within <${displayName}.Root>`);
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return [Context.Provider as Provider<T>, useCompoundContext];
|
|
22
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { CSSProperties, SyntheticEvent } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Composes two event handlers, calling child handler first.
|
|
5
|
+
* If child handler calls preventDefault(), parent handler is skipped.
|
|
6
|
+
*/
|
|
7
|
+
export function composeEventHandlers<E extends SyntheticEvent | Event>(
|
|
8
|
+
parentHandler?: (event: E) => void,
|
|
9
|
+
childHandler?: (event: E) => void
|
|
10
|
+
): (event: E) => void {
|
|
11
|
+
return (event: E) => {
|
|
12
|
+
childHandler?.(event);
|
|
13
|
+
if (
|
|
14
|
+
!("defaultPrevented" in event) ||
|
|
15
|
+
!(event as { defaultPrevented: boolean }).defaultPrevented
|
|
16
|
+
) {
|
|
17
|
+
parentHandler?.(event);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Merges two className strings, filtering out falsy values.
|
|
24
|
+
*/
|
|
25
|
+
export function mergeClassNames(...classNames: (string | undefined | null | false)[]): string {
|
|
26
|
+
return classNames.filter(Boolean).join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Merges two style objects, with child styles taking precedence.
|
|
31
|
+
*/
|
|
32
|
+
export function mergeStyles(
|
|
33
|
+
parentStyle?: CSSProperties,
|
|
34
|
+
childStyle?: CSSProperties
|
|
35
|
+
): CSSProperties | undefined {
|
|
36
|
+
if (!parentStyle && !childStyle) return undefined;
|
|
37
|
+
if (!parentStyle) return childStyle;
|
|
38
|
+
if (!childStyle) return parentStyle;
|
|
39
|
+
return { ...parentStyle, ...childStyle };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a prop name is an event handler (starts with 'on' followed by uppercase).
|
|
44
|
+
*/
|
|
45
|
+
function isEventHandler(propName: string): boolean {
|
|
46
|
+
return /^on[A-Z]/.test(propName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Merges slot props with child props.
|
|
51
|
+
* - className: concatenated
|
|
52
|
+
* - style: merged (child wins conflicts)
|
|
53
|
+
* - event handlers: composed (both called, child first)
|
|
54
|
+
* - other props: child wins
|
|
55
|
+
*/
|
|
56
|
+
export function mergeProps<T extends Record<string, unknown>>(slotProps: T, childProps: T): T {
|
|
57
|
+
const result = { ...slotProps } as T;
|
|
58
|
+
|
|
59
|
+
for (const key in childProps) {
|
|
60
|
+
const slotValue = slotProps[key];
|
|
61
|
+
const childValue = childProps[key];
|
|
62
|
+
|
|
63
|
+
if (key === "className") {
|
|
64
|
+
result[key] = mergeClassNames(
|
|
65
|
+
slotValue as string | undefined,
|
|
66
|
+
childValue as string | undefined
|
|
67
|
+
) as T[typeof key];
|
|
68
|
+
} else if (key === "style") {
|
|
69
|
+
result[key] = mergeStyles(
|
|
70
|
+
slotValue as CSSProperties | undefined,
|
|
71
|
+
childValue as CSSProperties | undefined
|
|
72
|
+
) as T[typeof key];
|
|
73
|
+
} else if (
|
|
74
|
+
isEventHandler(key) &&
|
|
75
|
+
typeof slotValue === "function" &&
|
|
76
|
+
typeof childValue === "function"
|
|
77
|
+
) {
|
|
78
|
+
result[key] = composeEventHandlers(
|
|
79
|
+
slotValue as (event: Event) => void,
|
|
80
|
+
childValue as (event: Event) => void
|
|
81
|
+
) as T[typeof key];
|
|
82
|
+
} else if (childValue !== undefined) {
|
|
83
|
+
result[key] = childValue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
generateResponsiveDataAttr,
|
|
8
8
|
isResponsiveObject,
|
|
9
9
|
resolveResponsiveValue,
|
|
10
|
-
} from "
|
|
10
|
+
} from "../_shared/primitives/responsive.js";
|
|
11
11
|
import type { AvatarSize } from "./avatar.js";
|
|
12
12
|
|
|
13
13
|
export interface AvatarGroupProps extends HTMLAttributes<HTMLElement> {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
generateResponsiveDataAttr,
|
|
8
8
|
isResponsiveObject,
|
|
9
9
|
resolveResponsiveValue,
|
|
10
|
-
} from "
|
|
10
|
+
} from "../_shared/primitives/responsive.js";
|
|
11
11
|
|
|
12
12
|
export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
|
13
13
|
export type AvatarShape = "circle" | "square";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
useEffect,
|
|
13
13
|
useMemo,
|
|
14
14
|
} from "react";
|
|
15
|
-
import { Slot } from "
|
|
15
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
16
16
|
|
|
17
17
|
export interface ButtonProps
|
|
18
18
|
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "disabled"> {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ComboboxBehavior, Option } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface ComboboxContextValue<Multi extends boolean = false> {
|
|
9
9
|
/** Combobox behavior instance */
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { DatePickerBehavior, DateRange } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export type DatePickerMode = "single" | "range";
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { type ButtonHTMLAttributes, type ReactNode, forwardRef, useCallback, useRef } from "react";
|
|
6
|
-
import { Slot } from "
|
|
6
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
7
7
|
import { useDatePickerContext } from "./date-picker-context.js";
|
|
8
8
|
|
|
9
9
|
export interface DatePickerTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { type ButtonHTMLAttributes, type ReactNode, forwardRef, useCallback } from "react";
|
|
6
|
-
import { Slot } from "
|
|
6
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
7
7
|
import { useDialogContext } from "./dialog-context.js";
|
|
8
8
|
|
|
9
9
|
export interface DialogCloseProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { DialogBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface DialogContextValue {
|
|
9
9
|
/** Dialog behavior instance */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type DialogRole, createDialogBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
6
|
import { type ReactNode, useCallback, useMemo, useState } from "react";
|
|
7
|
-
import { useStableId } from "
|
|
7
|
+
import { useStableId } from "../_shared/hooks/use-stable-id.js";
|
|
8
8
|
import { DialogProvider } from "./dialog-context.js";
|
|
9
9
|
|
|
10
10
|
export interface DialogRootProps {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
useEffect,
|
|
11
11
|
useRef,
|
|
12
12
|
} from "react";
|
|
13
|
-
import { Slot } from "
|
|
13
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
14
14
|
import { useDialogContext } from "./dialog-context.js";
|
|
15
15
|
|
|
16
16
|
export interface DialogTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { FileInfo, FileUploadBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface FileUploadContextValue {
|
|
9
9
|
/** FileUpload behavior instance */
|
package/templates/icon/icon.tsx
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type React from "react";
|
|
8
8
|
import { createElement, forwardRef, useMemo } from "react";
|
|
9
|
-
import { type ResponsiveValue, parseResponsiveValue } from "
|
|
9
|
+
import { type ResponsiveValue, parseResponsiveValue } from "../_shared/hooks/use-responsive-classes.js";
|
|
10
10
|
|
|
11
11
|
type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse";
|
|
12
12
|
type FlexAlign = "start" | "center" | "end" | "stretch" | "baseline";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type React from "react";
|
|
8
8
|
import { createElement, forwardRef, useMemo } from "react";
|
|
9
|
-
import { type ResponsiveValue, parseResponsiveValue } from "
|
|
9
|
+
import { type ResponsiveValue, parseResponsiveValue } from "../_shared/hooks/use-responsive-classes.js";
|
|
10
10
|
|
|
11
11
|
type GridColumns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | "auto-fit" | "auto-fill";
|
|
12
12
|
type SpacingToken = "none" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
|
package/templates/link/link.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { AnchorHTMLAttributes, ReactNode } from "react";
|
|
2
2
|
import { createElement, forwardRef, useCallback, useEffect, useRef } from "react";
|
|
3
|
-
import { Slot } from "../primitives/slot.js";
|
|
4
|
-
import type { DsNavigateEventDetail, NavigateEventHandler } from "../types/events.js";
|
|
5
|
-
import type { AsChildProps } from "../types/polymorphic.js";
|
|
6
|
-
import { mergeClassNames } from "../utils/merge-props.js";
|
|
3
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
4
|
+
import type { DsNavigateEventDetail, NavigateEventHandler } from "../_shared/types/events.js";
|
|
5
|
+
import type { AsChildProps } from "../_shared/types/polymorphic.js";
|
|
6
|
+
import { mergeClassNames } from "../_shared/utils/merge-props.js";
|
|
7
7
|
|
|
8
8
|
export type LinkVariant = "default" | "muted" | "underline";
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { MenuBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface MenuContextValue {
|
|
9
9
|
/** Menu behavior instance */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Placement, createMenuBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
6
|
import { type ReactNode, useCallback, useMemo, useState } from "react";
|
|
7
|
-
import { useStableId } from "
|
|
7
|
+
import { useStableId } from "../_shared/hooks/use-stable-id.js";
|
|
8
8
|
import { MenuProvider } from "./menu-context.js";
|
|
9
9
|
|
|
10
10
|
export interface MenuRootProps {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
useEffect,
|
|
11
11
|
useRef,
|
|
12
12
|
} from "react";
|
|
13
|
-
import { Slot } from "
|
|
13
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
14
14
|
import { useMenuContext } from "./menu-context.js";
|
|
15
15
|
|
|
16
16
|
export interface MenuTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { NumberInputBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export type NumberInputFormat = "decimal" | "currency" | "percent";
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { PinInputBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface PinInputContextValue {
|
|
9
9
|
/** PinInput behavior instance */
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
generateResponsiveDataAttr,
|
|
8
8
|
isResponsiveObject,
|
|
9
9
|
resolveResponsiveValue,
|
|
10
|
-
} from "
|
|
10
|
+
} from "../_shared/primitives/responsive.js";
|
|
11
11
|
|
|
12
12
|
export type ProgressVariant = "linear" | "circular";
|
|
13
13
|
export type ProgressSize = "sm" | "md" | "lg";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { SelectBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface SelectContextValue {
|
|
9
9
|
/** Select behavior instance */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Placement, createSelectBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
6
|
import { type ReactNode, useCallback, useMemo, useState } from "react";
|
|
7
|
-
import { useStableId } from "
|
|
7
|
+
import { useStableId } from "../_shared/hooks/use-stable-id.js";
|
|
8
8
|
import { SelectProvider } from "./select-context.js";
|
|
9
9
|
|
|
10
10
|
export interface SelectRootProps {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { type ButtonHTMLAttributes, type ReactNode, forwardRef, useCallback, useRef } from "react";
|
|
6
|
-
import { Slot } from "
|
|
6
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
7
7
|
import { useSelectContext } from "./select-context.js";
|
|
8
8
|
|
|
9
9
|
export interface SelectTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
generateResponsiveDataAttr,
|
|
8
8
|
isResponsiveObject,
|
|
9
9
|
resolveResponsiveValue,
|
|
10
|
-
} from "
|
|
10
|
+
} from "../_shared/primitives/responsive.js";
|
|
11
11
|
|
|
12
12
|
export type SkeletonVariant = "text" | "circular" | "rectangular" | "rounded";
|
|
13
13
|
export type SkeletonSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { SliderBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface SliderContextValue {
|
|
9
9
|
/** Slider behavior instance */
|
package/templates/tag/index.tsx
CHANGED
package/templates/text/text.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { createElement, forwardRef } from "react";
|
|
3
|
-
import { Slot } from "../primitives/slot.js";
|
|
4
|
-
import type { AsChildProps } from "../types/polymorphic.js";
|
|
5
|
-
import { mergeClassNames } from "../utils/merge-props.js";
|
|
3
|
+
import { Slot } from "../_shared/primitives/slot.js";
|
|
4
|
+
import type { AsChildProps } from "../_shared/types/polymorphic.js";
|
|
5
|
+
import { mergeClassNames } from "../_shared/utils/merge-props.js";
|
|
6
6
|
|
|
7
7
|
export type TextSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
|
8
8
|
export type TextWeight = "normal" | "medium" | "semibold" | "bold";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { TimePickerBehavior, TimeSegment, TimeValue } from "@hypoth-ui/primitives-dom";
|
|
6
|
-
import { createCompoundContext } from "
|
|
6
|
+
import { createCompoundContext } from "../_shared/utils/create-context.js";
|
|
7
7
|
|
|
8
8
|
export interface TimePickerContextValue {
|
|
9
9
|
/** TimePicker behavior instance */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { type ReactNode, createContext, useCallback, useMemo, useState } from "react";
|
|
10
|
-
import { Portal } from "
|
|
10
|
+
import { Portal } from "../_shared/primitives/portal.js";
|
|
11
11
|
import { ToastItem } from "./toast.js";
|
|
12
12
|
|
|
13
13
|
export type ToastVariant = "info" | "success" | "warning" | "error";
|