@aiready/components 0.1.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.
Files changed (91) hide show
  1. package/README.md +240 -0
  2. package/dist/charts/ForceDirectedGraph.d.ts +40 -0
  3. package/dist/charts/ForceDirectedGraph.js +294 -0
  4. package/dist/charts/ForceDirectedGraph.js.map +1 -0
  5. package/dist/components/badge.d.ts +13 -0
  6. package/dist/components/badge.js +32 -0
  7. package/dist/components/badge.js.map +1 -0
  8. package/dist/components/button.d.ts +14 -0
  9. package/dist/components/button.js +52 -0
  10. package/dist/components/button.js.map +1 -0
  11. package/dist/components/card.d.ts +10 -0
  12. package/dist/components/card.js +66 -0
  13. package/dist/components/card.js.map +1 -0
  14. package/dist/components/checkbox.d.ts +8 -0
  15. package/dist/components/checkbox.js +42 -0
  16. package/dist/components/checkbox.js.map +1 -0
  17. package/dist/components/container.d.ts +8 -0
  18. package/dist/components/container.js +36 -0
  19. package/dist/components/container.js.map +1 -0
  20. package/dist/components/grid.d.ts +9 -0
  21. package/dist/components/grid.js +44 -0
  22. package/dist/components/grid.js.map +1 -0
  23. package/dist/components/input.d.ts +7 -0
  24. package/dist/components/input.js +30 -0
  25. package/dist/components/input.js.map +1 -0
  26. package/dist/components/label.d.ts +10 -0
  27. package/dist/components/label.js +28 -0
  28. package/dist/components/label.js.map +1 -0
  29. package/dist/components/radio-group.d.ts +17 -0
  30. package/dist/components/radio-group.js +64 -0
  31. package/dist/components/radio-group.js.map +1 -0
  32. package/dist/components/select.d.ts +15 -0
  33. package/dist/components/select.js +45 -0
  34. package/dist/components/select.js.map +1 -0
  35. package/dist/components/separator.d.ts +9 -0
  36. package/dist/components/separator.js +30 -0
  37. package/dist/components/separator.js.map +1 -0
  38. package/dist/components/stack.d.ts +11 -0
  39. package/dist/components/stack.js +60 -0
  40. package/dist/components/stack.js.map +1 -0
  41. package/dist/components/switch.d.ts +9 -0
  42. package/dist/components/switch.js +49 -0
  43. package/dist/components/switch.js.map +1 -0
  44. package/dist/components/textarea.d.ts +7 -0
  45. package/dist/components/textarea.js +29 -0
  46. package/dist/components/textarea.js.map +1 -0
  47. package/dist/hooks/useD3.d.ts +6 -0
  48. package/dist/hooks/useD3.js +35 -0
  49. package/dist/hooks/useD3.js.map +1 -0
  50. package/dist/hooks/useDebounce.d.ts +3 -0
  51. package/dist/hooks/useDebounce.js +19 -0
  52. package/dist/hooks/useDebounce.js.map +1 -0
  53. package/dist/hooks/useForceSimulation.d.ts +39 -0
  54. package/dist/hooks/useForceSimulation.js +107 -0
  55. package/dist/hooks/useForceSimulation.js.map +1 -0
  56. package/dist/index.d.ts +27 -0
  57. package/dist/index.js +927 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/utils/cn.d.ts +5 -0
  60. package/dist/utils/cn.js +11 -0
  61. package/dist/utils/cn.js.map +1 -0
  62. package/dist/utils/colors.d.ts +19 -0
  63. package/dist/utils/colors.js +52 -0
  64. package/dist/utils/colors.js.map +1 -0
  65. package/dist/utils/formatters.d.ts +13 -0
  66. package/dist/utils/formatters.js +100 -0
  67. package/dist/utils/formatters.js.map +1 -0
  68. package/package.json +83 -0
  69. package/src/charts/ForceDirectedGraph.tsx +356 -0
  70. package/src/components/badge.tsx +35 -0
  71. package/src/components/button.tsx +53 -0
  72. package/src/components/card.tsx +78 -0
  73. package/src/components/checkbox.tsx +39 -0
  74. package/src/components/container.tsx +31 -0
  75. package/src/components/grid.tsx +40 -0
  76. package/src/components/input.tsx +24 -0
  77. package/src/components/label.tsx +24 -0
  78. package/src/components/radio-group.tsx +71 -0
  79. package/src/components/select.tsx +53 -0
  80. package/src/components/separator.tsx +29 -0
  81. package/src/components/stack.tsx +61 -0
  82. package/src/components/switch.tsx +49 -0
  83. package/src/components/textarea.tsx +23 -0
  84. package/src/hooks/useD3.ts +125 -0
  85. package/src/hooks/useDebounce.ts +44 -0
  86. package/src/hooks/useForceSimulation.ts +328 -0
  87. package/src/index.ts +51 -0
  88. package/src/utils/cn.ts +11 -0
  89. package/src/utils/colors.ts +58 -0
  90. package/src/utils/formatters.ts +161 -0
  91. package/tailwind.config.js +46 -0
@@ -0,0 +1,71 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface RadioOption {
5
+ value: string;
6
+ label: string;
7
+ disabled?: boolean;
8
+ }
9
+
10
+ export interface RadioGroupProps
11
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
12
+ options: RadioOption[];
13
+ value?: string;
14
+ onChange?: (value: string) => void;
15
+ name: string;
16
+ orientation?: 'horizontal' | 'vertical';
17
+ }
18
+
19
+ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
20
+ (
21
+ {
22
+ className,
23
+ options,
24
+ value,
25
+ onChange,
26
+ name,
27
+ orientation = 'vertical',
28
+ ...props
29
+ },
30
+ ref
31
+ ) => {
32
+ return (
33
+ <div
34
+ ref={ref}
35
+ className={cn(
36
+ 'flex',
37
+ orientation === 'vertical' ? 'flex-col gap-2' : 'flex-row gap-4',
38
+ className
39
+ )}
40
+ {...props}
41
+ >
42
+ {options.map((option) => {
43
+ const id = `${name}-${option.value}`;
44
+ return (
45
+ <div key={option.value} className="flex items-center">
46
+ <input
47
+ type="radio"
48
+ id={id}
49
+ name={name}
50
+ value={option.value}
51
+ checked={value === option.value}
52
+ disabled={option.disabled}
53
+ onChange={(e) => onChange?.(e.target.value)}
54
+ className="h-4 w-4 border-gray-300 text-primary focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
55
+ />
56
+ <label
57
+ htmlFor={id}
58
+ className="ml-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
59
+ >
60
+ {option.label}
61
+ </label>
62
+ </div>
63
+ );
64
+ })}
65
+ </div>
66
+ );
67
+ }
68
+ );
69
+ RadioGroup.displayName = 'RadioGroup';
70
+
71
+ export { RadioGroup };
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface SelectOption {
5
+ value: string;
6
+ label: string;
7
+ disabled?: boolean;
8
+ }
9
+
10
+ export interface SelectProps
11
+ extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'onChange'> {
12
+ options: SelectOption[];
13
+ placeholder?: string;
14
+ onChange?: (value: string) => void;
15
+ }
16
+
17
+ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
18
+ ({ className, options, placeholder, onChange, ...props }, ref) => {
19
+ const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
20
+ onChange?.(e.target.value);
21
+ };
22
+
23
+ return (
24
+ <select
25
+ ref={ref}
26
+ className={cn(
27
+ 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
28
+ className
29
+ )}
30
+ onChange={handleChange}
31
+ {...props}
32
+ >
33
+ {placeholder && (
34
+ <option value="" disabled>
35
+ {placeholder}
36
+ </option>
37
+ )}
38
+ {options.map((option) => (
39
+ <option
40
+ key={option.value}
41
+ value={option.value}
42
+ disabled={option.disabled}
43
+ >
44
+ {option.label}
45
+ </option>
46
+ ))}
47
+ </select>
48
+ );
49
+ }
50
+ );
51
+ Select.displayName = 'Select';
52
+
53
+ export { Select };
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ orientation?: 'horizontal' | 'vertical';
6
+ decorative?: boolean;
7
+ }
8
+
9
+ const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
10
+ (
11
+ { className, orientation = 'horizontal', decorative = true, ...props },
12
+ ref
13
+ ) => (
14
+ <div
15
+ ref={ref}
16
+ role={decorative ? 'none' : 'separator'}
17
+ aria-orientation={orientation}
18
+ className={cn(
19
+ 'shrink-0 bg-border',
20
+ orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ );
27
+ Separator.displayName = 'Separator';
28
+
29
+ export { Separator };
@@ -0,0 +1,61 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface StackProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ direction?: 'horizontal' | 'vertical';
6
+ spacing?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
7
+ align?: 'start' | 'center' | 'end' | 'stretch';
8
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around';
9
+ }
10
+
11
+ const Stack = React.forwardRef<HTMLDivElement, StackProps>(
12
+ (
13
+ {
14
+ className,
15
+ direction = 'vertical',
16
+ spacing = 'md',
17
+ align = 'stretch',
18
+ justify = 'start',
19
+ ...props
20
+ },
21
+ ref
22
+ ) => {
23
+ return (
24
+ <div
25
+ ref={ref}
26
+ className={cn(
27
+ 'flex',
28
+ {
29
+ 'flex-col': direction === 'vertical',
30
+ 'flex-row': direction === 'horizontal',
31
+ },
32
+ {
33
+ 'gap-1': spacing === 'xs',
34
+ 'gap-2': spacing === 'sm',
35
+ 'gap-4': spacing === 'md',
36
+ 'gap-6': spacing === 'lg',
37
+ 'gap-8': spacing === 'xl',
38
+ },
39
+ {
40
+ 'items-start': align === 'start',
41
+ 'items-center': align === 'center',
42
+ 'items-end': align === 'end',
43
+ 'items-stretch': align === 'stretch',
44
+ },
45
+ {
46
+ 'justify-start': justify === 'start',
47
+ 'justify-center': justify === 'center',
48
+ 'justify-end': justify === 'end',
49
+ 'justify-between': justify === 'between',
50
+ 'justify-around': justify === 'around',
51
+ },
52
+ className
53
+ )}
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+ );
59
+ Stack.displayName = 'Stack';
60
+
61
+ export { Stack };
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface SwitchProps
5
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
6
+ label?: string;
7
+ onCheckedChange?: (checked: boolean) => void;
8
+ }
9
+
10
+ const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
11
+ ({ className, label, id, checked, onCheckedChange, onChange, ...props }, ref) => {
12
+ const switchId = id || React.useId();
13
+
14
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
15
+ onChange?.(e);
16
+ onCheckedChange?.(e.target.checked);
17
+ };
18
+
19
+ return (
20
+ <div className="flex items-center">
21
+ <label htmlFor={switchId} className="relative inline-flex cursor-pointer items-center">
22
+ <input
23
+ type="checkbox"
24
+ id={switchId}
25
+ ref={ref}
26
+ checked={checked}
27
+ onChange={handleChange}
28
+ className="peer sr-only"
29
+ {...props}
30
+ />
31
+ <div
32
+ className={cn(
33
+ 'peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[""] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-ring peer-focus:ring-offset-2 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
34
+ className
35
+ )}
36
+ />
37
+ </label>
38
+ {label && (
39
+ <span className="ml-3 text-sm font-medium text-foreground">
40
+ {label}
41
+ </span>
42
+ )}
43
+ </div>
44
+ );
45
+ }
46
+ );
47
+ Switch.displayName = 'Switch';
48
+
49
+ export { Switch };
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface TextareaProps
5
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
13
+ className
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+ );
21
+ Textarea.displayName = 'Textarea';
22
+
23
+ export { Textarea };
@@ -0,0 +1,125 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import * as d3 from 'd3';
3
+
4
+ /**
5
+ * Hook for managing D3 selections with React lifecycle
6
+ * Provides a ref to the SVG/container element and runs a render function when dependencies change
7
+ *
8
+ * @param renderFn - Function that receives the D3 selection and performs rendering
9
+ * @param dependencies - Array of dependencies that trigger re-render
10
+ * @returns Ref to attach to the SVG/container element
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function BarChart({ data }: { data: number[] }) {
15
+ * const ref = useD3(
16
+ * (svg) => {
17
+ * const width = 600;
18
+ * const height = 400;
19
+ * const margin = { top: 20, right: 20, bottom: 30, left: 40 };
20
+ *
21
+ * // Clear previous content
22
+ * svg.selectAll('*').remove();
23
+ *
24
+ * // Set up scales
25
+ * const x = d3.scaleBand()
26
+ * .domain(data.map((_, i) => i.toString()))
27
+ * .range([margin.left, width - margin.right])
28
+ * .padding(0.1);
29
+ *
30
+ * const y = d3.scaleLinear()
31
+ * .domain([0, d3.max(data) || 0])
32
+ * .range([height - margin.bottom, margin.top]);
33
+ *
34
+ * // Draw bars
35
+ * svg.selectAll('rect')
36
+ * .data(data)
37
+ * .join('rect')
38
+ * .attr('x', (_, i) => x(i.toString())!)
39
+ * .attr('y', d => y(d))
40
+ * .attr('width', x.bandwidth())
41
+ * .attr('height', d => y(0) - y(d))
42
+ * .attr('fill', 'steelblue');
43
+ * },
44
+ * [data]
45
+ * );
46
+ *
47
+ * return <svg ref={ref} width={600} height={400} />;
48
+ * }
49
+ * ```
50
+ */
51
+ export function useD3<T extends SVGSVGElement | HTMLDivElement>(
52
+ renderFn: (selection: d3.Selection<T, unknown, null, undefined>) => void,
53
+ dependencies: React.DependencyList = []
54
+ ): React.RefObject<T | null> {
55
+ const ref = useRef<T | null>(null);
56
+
57
+ useEffect(() => {
58
+ if (ref.current) {
59
+ const selection = d3.select(ref.current);
60
+ renderFn(selection);
61
+ }
62
+ // eslint-disable-next-line react-hooks/exhaustive-deps
63
+ }, dependencies);
64
+
65
+ return ref;
66
+ }
67
+
68
+ /**
69
+ * Hook for managing D3 selections with automatic resize handling
70
+ * Similar to useD3 but also triggers re-render on window resize
71
+ *
72
+ * @param renderFn - Function that receives the D3 selection and performs rendering
73
+ * @param dependencies - Array of dependencies that trigger re-render
74
+ * @returns Ref to attach to the SVG/container element
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * function ResponsiveChart({ data }: { data: number[] }) {
79
+ * const ref = useD3WithResize(
80
+ * (svg) => {
81
+ * const container = svg.node();
82
+ * const width = container?.clientWidth || 600;
83
+ * const height = container?.clientHeight || 400;
84
+ *
85
+ * // Render with responsive dimensions
86
+ * // ...
87
+ * },
88
+ * [data]
89
+ * );
90
+ *
91
+ * return <svg ref={ref} style={{ width: '100%', height: '400px' }} />;
92
+ * }
93
+ * ```
94
+ */
95
+ export function useD3WithResize<T extends SVGSVGElement | HTMLDivElement>(
96
+ renderFn: (selection: d3.Selection<T, unknown, null, undefined>) => void,
97
+ dependencies: React.DependencyList = []
98
+ ): React.RefObject<T | null> {
99
+ const ref = useRef<T | null>(null);
100
+
101
+ useEffect(() => {
102
+ if (!ref.current) return;
103
+
104
+ const selection = d3.select(ref.current);
105
+ const render = () => renderFn(selection);
106
+
107
+ // Initial render
108
+ render();
109
+
110
+ // Set up resize observer
111
+ const resizeObserver = new ResizeObserver(() => {
112
+ render();
113
+ });
114
+
115
+ resizeObserver.observe(ref.current);
116
+
117
+ // Cleanup
118
+ return () => {
119
+ resizeObserver.disconnect();
120
+ };
121
+ // eslint-disable-next-line react-hooks/exhaustive-deps
122
+ }, dependencies);
123
+
124
+ return ref;
125
+ }
@@ -0,0 +1,44 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ /**
4
+ * Debounce a value with a specified delay
5
+ * Useful for search inputs, filters, and other frequently changing values
6
+ *
7
+ * @param value - The value to debounce
8
+ * @param delay - Delay in milliseconds (default: 300ms)
9
+ * @returns The debounced value
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * function SearchInput() {
14
+ * const [searchTerm, setSearchTerm] = useState('');
15
+ * const debouncedSearchTerm = useDebounce(searchTerm, 500);
16
+ *
17
+ * useEffect(() => {
18
+ * // This will only run when user stops typing for 500ms
19
+ * if (debouncedSearchTerm) {
20
+ * performSearch(debouncedSearchTerm);
21
+ * }
22
+ * }, [debouncedSearchTerm]);
23
+ *
24
+ * return <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />;
25
+ * }
26
+ * ```
27
+ */
28
+ export function useDebounce<T>(value: T, delay: number = 300): T {
29
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
30
+
31
+ useEffect(() => {
32
+ // Set up the timeout to update debounced value after delay
33
+ const timer = setTimeout(() => {
34
+ setDebouncedValue(value);
35
+ }, delay);
36
+
37
+ // Clean up the timeout if value changes or component unmounts
38
+ return () => {
39
+ clearTimeout(timer);
40
+ };
41
+ }, [value, delay]);
42
+
43
+ return debouncedValue;
44
+ }