@ponti-studios/ui 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/README.md +27 -0
- package/dist/index.js +2118 -0
- package/dist/index.js.map +1 -0
- package/package.json +130 -0
- package/src/animations.css +437 -0
- package/src/components/data-display/accordion.stories.tsx +57 -0
- package/src/components/data-display/accordion.tsx +65 -0
- package/src/components/data-display/filter-chip.tsx +41 -0
- package/src/components/data-display/index.ts +13 -0
- package/src/components/data-display/table.stories.tsx +75 -0
- package/src/components/data-display/table.tsx +84 -0
- package/src/components/feedback/alert.stories.tsx +51 -0
- package/src/components/feedback/alert.tsx +60 -0
- package/src/components/feedback/empty-state.tsx +66 -0
- package/src/components/feedback/index.ts +6 -0
- package/src/components/feedback/progress.stories.tsx +37 -0
- package/src/components/feedback/progress.tsx +30 -0
- package/src/components/feedback/spinner.stories.tsx +52 -0
- package/src/components/feedback/spinner.tsx +48 -0
- package/src/components/forms/calendar.stories.tsx +30 -0
- package/src/components/forms/calendar.tsx +67 -0
- package/src/components/forms/date-picker.stories.tsx +48 -0
- package/src/components/forms/date-picker.tsx +60 -0
- package/src/components/forms/field.stories.tsx +62 -0
- package/src/components/forms/field.tsx +65 -0
- package/src/components/forms/field.types.ts +7 -0
- package/src/components/forms/index.ts +24 -0
- package/src/components/forms/input.stories.tsx +47 -0
- package/src/components/forms/input.tsx +20 -0
- package/src/components/forms/radio-group.stories.tsx +48 -0
- package/src/components/forms/radio-group.tsx +38 -0
- package/src/components/forms/select.stories.tsx +88 -0
- package/src/components/forms/select.tsx +147 -0
- package/src/components/forms/slider.stories.tsx +67 -0
- package/src/components/forms/slider.tsx +25 -0
- package/src/components/forms/stepper.stories.tsx +111 -0
- package/src/components/forms/stepper.tsx +66 -0
- package/src/components/forms/switch.stories.tsx +144 -0
- package/src/components/forms/switch.tsx +36 -0
- package/src/components/forms/text-field.stories.tsx +87 -0
- package/src/components/forms/text-field.tsx +40 -0
- package/src/components/forms/text-field.types.ts +9 -0
- package/src/components/forms/textarea.stories.tsx +150 -0
- package/src/components/forms/textarea.tsx +18 -0
- package/src/components/layout/index.ts +2 -0
- package/src/components/layout/scroll-area.stories.tsx +83 -0
- package/src/components/layout/scroll-area.tsx +40 -0
- package/src/components/navigation/app-navigation.stories.tsx +81 -0
- package/src/components/navigation/app-navigation.tsx +155 -0
- package/src/components/navigation/index.ts +3 -0
- package/src/components/navigation/tabs.stories.tsx +69 -0
- package/src/components/navigation/tabs.tsx +49 -0
- package/src/components/overlays/alert-dialog.stories.tsx +45 -0
- package/src/components/overlays/alert-dialog.tsx +119 -0
- package/src/components/overlays/command.stories.tsx +65 -0
- package/src/components/overlays/command.tsx +130 -0
- package/src/components/overlays/dialog.stories.tsx +111 -0
- package/src/components/overlays/dialog.tsx +162 -0
- package/src/components/overlays/dropdown-menu.stories.tsx +79 -0
- package/src/components/overlays/dropdown-menu.tsx +214 -0
- package/src/components/overlays/index.ts +65 -0
- package/src/components/overlays/popover.stories.tsx +39 -0
- package/src/components/overlays/popover.tsx +40 -0
- package/src/components/overlays/sheet.stories.tsx +68 -0
- package/src/components/overlays/sheet.tsx +98 -0
- package/src/components/primitives/avatar.stories.tsx +71 -0
- package/src/components/primitives/avatar.tsx +132 -0
- package/src/components/primitives/badge.stories.tsx +33 -0
- package/src/components/primitives/badge.tsx +44 -0
- package/src/components/primitives/button.stories.tsx +113 -0
- package/src/components/primitives/button.tsx +95 -0
- package/src/components/primitives/button.types.ts +10 -0
- package/src/components/primitives/card.stories.tsx +59 -0
- package/src/components/primitives/card.tsx +69 -0
- package/src/components/primitives/index.ts +13 -0
- package/src/components/primitives/label.stories.tsx +47 -0
- package/src/components/primitives/label.tsx +21 -0
- package/src/constants/chart-colors.ts +39 -0
- package/src/env.d.ts +9 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/sort.types.ts +12 -0
- package/src/hooks/use-api-client.ts +122 -0
- package/src/hooks/use-debounced-value.ts +12 -0
- package/src/hooks/use-filter-state.ts +68 -0
- package/src/hooks/use-media-query.ts +65 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.ts +25 -0
- package/src/lib/auth.ts +5 -0
- package/src/lib/clipboard.ts +8 -0
- package/src/lib/create-memory-storage.ts +24 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/utils.ts +6 -0
- package/src/storybook/controls.ts +117 -0
- package/src/storybook/options.ts +52 -0
- package/src/styles.css +1052 -0
- package/src/tokens/color-systems.stories.tsx +133 -0
- package/src/tokens/colors.ts +156 -0
- package/src/tokens/foundations.stories.tsx +151 -0
- package/src/tokens/index.ts +16 -0
- package/src/tokens/layout.stories.tsx +118 -0
- package/src/tokens/motion.ts +51 -0
- package/src/tokens/radii.ts +10 -0
- package/src/tokens/shadows.ts +34 -0
- package/src/tokens/spacing.ts +30 -0
- package/src/tokens/typography.ts +42 -0
- package/src/translations/en.ts +34 -0
- package/src/translations/index.ts +32 -0
- package/src/types/upload.ts +42 -0
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { useApiClient } from "./use-api-client";
|
|
2
|
+
export { useDebouncedValue } from "./use-debounced-value";
|
|
3
|
+
export { useFilterState } from "./use-filter-state";
|
|
4
|
+
export { useMediaQuery } from "./use-media-query";
|
|
5
|
+
export { useIsMobile } from "./use-mobile";
|
|
6
|
+
export type { SortField, SortDirection, SortOption } from "./sort.types";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sort types for use across packages
|
|
3
|
+
* These types are exported from the shared UI package for shared usage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type SortField = string;
|
|
7
|
+
export type SortDirection = "asc" | "desc";
|
|
8
|
+
|
|
9
|
+
export interface SortOption {
|
|
10
|
+
field: SortField;
|
|
11
|
+
direction: SortDirection;
|
|
12
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
|
4
|
+
|
|
5
|
+
type FetchOptions<T> = {
|
|
6
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
7
|
+
body?: T | undefined;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
stream?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ApiState = {
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* React hook for API client that handles fetch requests with authentication
|
|
19
|
+
*/
|
|
20
|
+
export function useApiClient() {
|
|
21
|
+
const [state, setState] = useState<ApiState>({
|
|
22
|
+
isLoading: false,
|
|
23
|
+
error: null,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Base fetch function with authentication
|
|
28
|
+
*/
|
|
29
|
+
const fetchApi = useCallback(
|
|
30
|
+
async <T, S>(endpoint: string, options: FetchOptions<T> = {}): Promise<S> => {
|
|
31
|
+
const { method = "GET", body, headers = {}, stream = false } = options;
|
|
32
|
+
setState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Default headers
|
|
36
|
+
const defaultHeaders: Record<string, string> = {};
|
|
37
|
+
|
|
38
|
+
// Only set Content-Type for non-FormData requests
|
|
39
|
+
if (!(body instanceof FormData)) {
|
|
40
|
+
defaultHeaders["Content-Type"] = "application/json";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const fetchBody: BodyInit | null =
|
|
44
|
+
body instanceof FormData ? body : body ? JSON.stringify(body) : null;
|
|
45
|
+
|
|
46
|
+
const res = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
47
|
+
method,
|
|
48
|
+
headers: {
|
|
49
|
+
...defaultHeaders,
|
|
50
|
+
...headers,
|
|
51
|
+
},
|
|
52
|
+
body: fetchBody,
|
|
53
|
+
credentials: "include",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const error = await res.json().catch(() => ({}));
|
|
58
|
+
throw new Error(error.message || "An error occurred");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (stream) {
|
|
62
|
+
setState((prev) => ({ ...prev, isLoading: false }));
|
|
63
|
+
return res as S;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = (await res.json()) as S;
|
|
67
|
+
setState((prev) => ({ ...prev, isLoading: false }));
|
|
68
|
+
return data;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
setState((prev) => ({
|
|
71
|
+
...prev,
|
|
72
|
+
isLoading: false,
|
|
73
|
+
error: error instanceof Error ? error : new Error("An unknown error occurred"),
|
|
74
|
+
}));
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* API client methods for common operations - memoized to prevent infinite re-renders
|
|
83
|
+
*/
|
|
84
|
+
const api = useMemo(
|
|
85
|
+
() => ({
|
|
86
|
+
get: <body, returnType>(
|
|
87
|
+
endpoint: string,
|
|
88
|
+
options?: Omit<FetchOptions<body>, "method" | "body">,
|
|
89
|
+
) => fetchApi<body, returnType>(endpoint, { ...options, method: "GET" }),
|
|
90
|
+
|
|
91
|
+
post: <T, S>(endpoint: string, data?: T, options?: Omit<FetchOptions<T>, "method">) =>
|
|
92
|
+
fetchApi<T, S>(endpoint, { ...options, method: "POST", body: data || undefined }),
|
|
93
|
+
|
|
94
|
+
postStream: <T>(
|
|
95
|
+
endpoint: string,
|
|
96
|
+
data: T,
|
|
97
|
+
options?: Omit<FetchOptions<T>, "method" | "stream">,
|
|
98
|
+
) =>
|
|
99
|
+
fetchApi<T, Response>(endpoint, { ...options, method: "POST", body: data, stream: true }),
|
|
100
|
+
|
|
101
|
+
postFormData: <S>(
|
|
102
|
+
endpoint: string,
|
|
103
|
+
formData: FormData,
|
|
104
|
+
options?: Omit<FetchOptions<FormData>, "method">,
|
|
105
|
+
) => fetchApi<FormData, S>(endpoint, { ...options, method: "POST", body: formData }),
|
|
106
|
+
|
|
107
|
+
put: <T, S>(endpoint: string, data: T, options?: Omit<FetchOptions<T>, "method">) =>
|
|
108
|
+
fetchApi<T, S>(endpoint, { ...options, method: "PUT", body: data }),
|
|
109
|
+
|
|
110
|
+
delete: <T, S>(endpoint: string, options?: Omit<FetchOptions<T>, "method">) =>
|
|
111
|
+
// We add body because `fetchApi` use `application/json` as default content type
|
|
112
|
+
fetchApi<T, S>(endpoint, { ...options, method: "DELETE", body: {} as T }),
|
|
113
|
+
}),
|
|
114
|
+
[fetchApi],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...api,
|
|
119
|
+
isLoading: state.isLoading,
|
|
120
|
+
error: state.error,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export function useDebouncedValue<T>(value: T, delayMs: number): T {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const timeoutId = setTimeout(() => setDebouncedValue(value), delayMs);
|
|
8
|
+
return () => clearTimeout(timeoutId);
|
|
9
|
+
}, [delayMs, value]);
|
|
10
|
+
|
|
11
|
+
return debouncedValue;
|
|
12
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
interface UseFilterStateOptions<T> {
|
|
4
|
+
initialFilters: T;
|
|
5
|
+
onFiltersChange?: (filters: T) => void;
|
|
6
|
+
debounceMs?: number; // For search-like filters
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useFilterState<T extends Record<string, unknown>>(
|
|
10
|
+
options: UseFilterStateOptions<T>,
|
|
11
|
+
) {
|
|
12
|
+
const { initialFilters, onFiltersChange, debounceMs } = options;
|
|
13
|
+
const [filters, setFiltersState] = useState<T>(initialFilters);
|
|
14
|
+
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
15
|
+
|
|
16
|
+
const setFilters = useCallback(
|
|
17
|
+
(newFilters: T | ((prev: T) => T)) => {
|
|
18
|
+
const updatedFilters = typeof newFilters === "function" ? newFilters(filters) : newFilters;
|
|
19
|
+
setFiltersState(updatedFilters);
|
|
20
|
+
|
|
21
|
+
if (debounceMs && debounceMs > 0) {
|
|
22
|
+
if (debounceTimerRef.current) {
|
|
23
|
+
clearTimeout(debounceTimerRef.current);
|
|
24
|
+
}
|
|
25
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
26
|
+
onFiltersChange?.(updatedFilters);
|
|
27
|
+
}, debounceMs);
|
|
28
|
+
} else {
|
|
29
|
+
onFiltersChange?.(updatedFilters);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
[filters, onFiltersChange, debounceMs],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const updateFilter = useCallback(
|
|
36
|
+
<K extends keyof T>(key: K, value: T[K]) => {
|
|
37
|
+
setFilters((prev) => ({ ...prev, [key]: value }));
|
|
38
|
+
},
|
|
39
|
+
[setFilters],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const clearFilters = useCallback(() => {
|
|
43
|
+
setFiltersState(initialFilters);
|
|
44
|
+
onFiltersChange?.(initialFilters);
|
|
45
|
+
}, [initialFilters, onFiltersChange]);
|
|
46
|
+
|
|
47
|
+
const resetFilters = useCallback(() => {
|
|
48
|
+
setFiltersState(initialFilters);
|
|
49
|
+
onFiltersChange?.(initialFilters);
|
|
50
|
+
}, [initialFilters, onFiltersChange]);
|
|
51
|
+
|
|
52
|
+
// Cleanup debounce timer on unmount
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
return () => {
|
|
55
|
+
if (debounceTimerRef.current) {
|
|
56
|
+
clearTimeout(debounceTimerRef.current);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
filters,
|
|
63
|
+
setFilters,
|
|
64
|
+
updateFilter,
|
|
65
|
+
clearFilters,
|
|
66
|
+
resetFilters,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
interface MediaQueryOptions {
|
|
4
|
+
width?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A utility function to check if a media query matches the current viewport.
|
|
10
|
+
* @param query - The media query string to check.
|
|
11
|
+
* @param options - Optional width and height values for testing
|
|
12
|
+
* @returns A boolean indicating whether the media query matches.
|
|
13
|
+
*/
|
|
14
|
+
function mediaQuery(query: string, options?: MediaQueryOptions) {
|
|
15
|
+
if (options?.width !== undefined) {
|
|
16
|
+
// For test environment where we provide custom dimensions
|
|
17
|
+
const minWidthMatch = query.match(/\(min-width:\s*(\d+)px\)/);
|
|
18
|
+
const maxWidthMatch = query.match(/\(max-width:\s*(\d+)px\)/);
|
|
19
|
+
|
|
20
|
+
if (minWidthMatch && minWidthMatch[1]) {
|
|
21
|
+
return options.width >= Number.parseInt(minWidthMatch[1], 10);
|
|
22
|
+
}
|
|
23
|
+
if (maxWidthMatch && maxWidthMatch[1]) {
|
|
24
|
+
return options.width <= Number.parseInt(maxWidthMatch[1], 10);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const mql = window.matchMedia(query);
|
|
29
|
+
return mql.matches;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Listens for changes to a media query and returns a boolean indicating whether the query matches.
|
|
34
|
+
* @param query - The media query string to listen for.
|
|
35
|
+
* @returns A boolean indicating whether the media query matches.
|
|
36
|
+
*/
|
|
37
|
+
export function useMediaQuery(query: string) {
|
|
38
|
+
const subscribe = useCallback(
|
|
39
|
+
(onStoreChange: () => void) => {
|
|
40
|
+
if (typeof window === "undefined") {
|
|
41
|
+
return () => {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const mql = window.matchMedia(query);
|
|
45
|
+
const onChange = () => {
|
|
46
|
+
onStoreChange();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
mql.addEventListener("change", onChange);
|
|
50
|
+
return () => {
|
|
51
|
+
mql.removeEventListener("change", onChange);
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
[query],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const getSnapshot = useCallback(() => {
|
|
58
|
+
if (typeof window === "undefined") {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return mediaQuery(query);
|
|
62
|
+
}, [query]);
|
|
63
|
+
|
|
64
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => false);
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768;
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
12
|
+
};
|
|
13
|
+
mql.addEventListener("change", onChange);
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
15
|
+
return () => mql.removeEventListener("change", onChange);
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
return !!isMobile;
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { CHART_COLORS, CHART_CSS_VARS } from "./constants/chart-colors";
|
|
2
|
+
export type { SortDirection, SortField, SortOption } from "./hooks/sort.types";
|
|
3
|
+
export { useApiClient } from "./hooks/use-api-client";
|
|
4
|
+
export { useDebouncedValue } from "./hooks/use-debounced-value";
|
|
5
|
+
export { useFilterState } from "./hooks/use-filter-state";
|
|
6
|
+
export { useMediaQuery } from "./hooks/use-media-query";
|
|
7
|
+
export { useIsMobile } from "./hooks/use-mobile";
|
|
8
|
+
export { normalizeOtp, OTP_LENGTH } from "./lib/auth";
|
|
9
|
+
export { copyToClipboard } from "./lib/clipboard";
|
|
10
|
+
export { createMemoryStorage } from "./lib/create-memory-storage";
|
|
11
|
+
export { cn } from "./lib/utils";
|
|
12
|
+
export {
|
|
13
|
+
COLOR_MODE_ATTRIBUTE,
|
|
14
|
+
COLOR_SYSTEM_ATTRIBUTE,
|
|
15
|
+
colors,
|
|
16
|
+
colorSystems,
|
|
17
|
+
colorTokenNames,
|
|
18
|
+
} from "./tokens/colors";
|
|
19
|
+
export type { ColorMode, ColorSystem, ColorToken } from "./tokens/colors";
|
|
20
|
+
export { durations, easingWeb, translateDistances } from "./tokens/motion";
|
|
21
|
+
export { radii } from "./tokens/radii";
|
|
22
|
+
export type { RadiusToken } from "./tokens/radii";
|
|
23
|
+
export { shadowsNative, shadowsWeb } from "./tokens/shadows";
|
|
24
|
+
export { contentWidths, spacing } from "./tokens/spacing";
|
|
25
|
+
export type { ContentWidthToken, SpacingToken } from "./tokens/spacing";
|
package/src/lib/auth.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function createMemoryStorage(): Storage {
|
|
2
|
+
const store = new Map<string, string>();
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
get length() {
|
|
6
|
+
return store.size;
|
|
7
|
+
},
|
|
8
|
+
clear() {
|
|
9
|
+
store.clear();
|
|
10
|
+
},
|
|
11
|
+
getItem(key) {
|
|
12
|
+
return store.get(key) ?? null;
|
|
13
|
+
},
|
|
14
|
+
key(index) {
|
|
15
|
+
return Array.from(store.keys())[index] ?? null;
|
|
16
|
+
},
|
|
17
|
+
removeItem(key) {
|
|
18
|
+
store.delete(key);
|
|
19
|
+
},
|
|
20
|
+
setItem(key, value) {
|
|
21
|
+
store.set(key, value);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
package/src/lib/index.ts
ADDED
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { InputType } from "storybook/internal/csf";
|
|
2
|
+
|
|
3
|
+
type StorybookControlTable = NonNullable<InputType["table"]>;
|
|
4
|
+
|
|
5
|
+
type StorybookControlCondition = NonNullable<InputType["if"]>;
|
|
6
|
+
|
|
7
|
+
type StorybookControl = InputType;
|
|
8
|
+
|
|
9
|
+
type SelectControlConfig = {
|
|
10
|
+
controlType?: "select" | "radio" | "inline-radio" | "check" | "inline-check" | "multi-select";
|
|
11
|
+
defaultValue?: boolean | number | string | null;
|
|
12
|
+
if?: StorybookControlCondition;
|
|
13
|
+
labels?: Record<string, string>;
|
|
14
|
+
mapping?: Record<string, unknown>;
|
|
15
|
+
table?: StorybookControlTable;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const commonControlsExclude = /^(className|style|children|asChild|on[A-Z].*|render[A-Z].*)$/;
|
|
19
|
+
|
|
20
|
+
const hiddenControl = {
|
|
21
|
+
control: false,
|
|
22
|
+
table: {
|
|
23
|
+
disable: true,
|
|
24
|
+
},
|
|
25
|
+
} satisfies StorybookControl;
|
|
26
|
+
|
|
27
|
+
function withDefaultValue(
|
|
28
|
+
control: StorybookControl,
|
|
29
|
+
defaultValue?: boolean | number | string | null,
|
|
30
|
+
): StorybookControl {
|
|
31
|
+
if (defaultValue === undefined) {
|
|
32
|
+
return control;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...control,
|
|
37
|
+
table: {
|
|
38
|
+
...control.table,
|
|
39
|
+
defaultValue: {
|
|
40
|
+
summary: String(defaultValue),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function booleanControl(description: string, defaultValue?: boolean): StorybookControl {
|
|
47
|
+
return withDefaultValue(
|
|
48
|
+
{
|
|
49
|
+
control: "boolean",
|
|
50
|
+
description,
|
|
51
|
+
},
|
|
52
|
+
defaultValue,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function textControl(description: string, defaultValue?: string): StorybookControl {
|
|
57
|
+
return withDefaultValue(
|
|
58
|
+
{
|
|
59
|
+
control: "text",
|
|
60
|
+
description,
|
|
61
|
+
},
|
|
62
|
+
defaultValue,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function numberControl(
|
|
67
|
+
description: string,
|
|
68
|
+
config?: {
|
|
69
|
+
defaultValue?: number;
|
|
70
|
+
max?: number;
|
|
71
|
+
min?: number;
|
|
72
|
+
step?: number;
|
|
73
|
+
},
|
|
74
|
+
): StorybookControl {
|
|
75
|
+
return withDefaultValue(
|
|
76
|
+
{
|
|
77
|
+
control: {
|
|
78
|
+
max: config?.max,
|
|
79
|
+
min: config?.min,
|
|
80
|
+
step: config?.step,
|
|
81
|
+
type: "number",
|
|
82
|
+
},
|
|
83
|
+
description,
|
|
84
|
+
},
|
|
85
|
+
config?.defaultValue,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function selectControl<T extends boolean | number | string | null>(
|
|
90
|
+
options: readonly T[],
|
|
91
|
+
description: string,
|
|
92
|
+
config?: SelectControlConfig,
|
|
93
|
+
): StorybookControl {
|
|
94
|
+
return withDefaultValue(
|
|
95
|
+
{
|
|
96
|
+
control: {
|
|
97
|
+
type: config?.controlType ?? "select",
|
|
98
|
+
},
|
|
99
|
+
description,
|
|
100
|
+
options,
|
|
101
|
+
...(config?.if ? { if: config.if } : {}),
|
|
102
|
+
...(config?.labels ? { labels: config.labels } : {}),
|
|
103
|
+
...(config?.mapping ? { mapping: config.mapping } : {}),
|
|
104
|
+
...(config?.table ? { table: config.table } : {}),
|
|
105
|
+
},
|
|
106
|
+
config?.defaultValue,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
booleanControl,
|
|
112
|
+
commonControlsExclude,
|
|
113
|
+
hiddenControl,
|
|
114
|
+
numberControl,
|
|
115
|
+
selectControl,
|
|
116
|
+
textControl,
|
|
117
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TextFieldType } from "../components/forms/text-field.types";
|
|
2
|
+
|
|
3
|
+
const buttonVariantOptions = [
|
|
4
|
+
"default",
|
|
5
|
+
"destructive",
|
|
6
|
+
"ghost",
|
|
7
|
+
"link",
|
|
8
|
+
"outline",
|
|
9
|
+
"secondary",
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
const inputTypeOptions = ["text", "email", "password", "search", "number", "tel", "url"] as const;
|
|
13
|
+
|
|
14
|
+
const textFieldTypeOptions = [
|
|
15
|
+
"text",
|
|
16
|
+
"email",
|
|
17
|
+
"password",
|
|
18
|
+
"search",
|
|
19
|
+
] satisfies readonly TextFieldType[];
|
|
20
|
+
|
|
21
|
+
const checkboxStateOptions = [true, false, "indeterminate"] as const;
|
|
22
|
+
|
|
23
|
+
const switchSizeOptions = ["default", "sm"] as const;
|
|
24
|
+
const drawerDirectionOptions = ["top", "bottom", "left", "right"] as const;
|
|
25
|
+
const loadingSizeOptions = ["sm", "md", "lg", "xl", "2xl", "3xl"] as const;
|
|
26
|
+
const codeBlockLanguageOptions = [
|
|
27
|
+
"typescript",
|
|
28
|
+
"tsx",
|
|
29
|
+
"javascript",
|
|
30
|
+
"jsx",
|
|
31
|
+
"python",
|
|
32
|
+
"bash",
|
|
33
|
+
"sql",
|
|
34
|
+
"json",
|
|
35
|
+
"html",
|
|
36
|
+
"css",
|
|
37
|
+
"yaml",
|
|
38
|
+
"dockerfile",
|
|
39
|
+
"markdown",
|
|
40
|
+
"graphql",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
buttonVariantOptions,
|
|
45
|
+
checkboxStateOptions,
|
|
46
|
+
codeBlockLanguageOptions,
|
|
47
|
+
drawerDirectionOptions,
|
|
48
|
+
inputTypeOptions,
|
|
49
|
+
loadingSizeOptions,
|
|
50
|
+
switchSizeOptions,
|
|
51
|
+
textFieldTypeOptions,
|
|
52
|
+
};
|