torch-glare 2.0.0 → 2.1.1

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.
@@ -0,0 +1,341 @@
1
+ import { ComponentProps, forwardRef, HTMLAttributes, useCallback, useEffect, useRef, useState } from 'react';
2
+ import { getDaysInMonth } from 'date-fns';
3
+ import { Popover, PopoverContent, PopoverTrigger } from './Popover';
4
+ import { InputField } from './InputField';
5
+
6
+ interface PickerValue {
7
+ [key: string]: string | number;
8
+ }
9
+
10
+ function getDayArray(year: number, month: number): string[] {
11
+ const dayCount = getDaysInMonth(new Date(year, month - 1));
12
+ return Array.from({ length: dayCount }, (_, i) => String(i + 1).padStart(2, '0'));
13
+ }
14
+
15
+ interface IosDatePickerProps extends Omit<ComponentProps<typeof InputField>, 'onChange'> {
16
+ onChange?: (e: Date) => void;
17
+ theme?: "dark" | "light" | "default";
18
+ }
19
+
20
+ export const IosDatePicker = forwardRef<HTMLInputElement, IosDatePickerProps>(
21
+ ({ theme = "dark", onChange, ...props }, forwardedRef) => {
22
+ const today = new Date();
23
+
24
+ const [year, setYear] = useState(String(today.getFullYear()));
25
+ const [month, setMonth] = useState(String(today.getMonth() + 1).padStart(2, '0'));
26
+ const [day, setDay] = useState(String(today.getDate()).padStart(2, '0'));
27
+
28
+ const currentYear = new Date().getFullYear();
29
+ const years = Array.from({ length: 150 }, (_, i) => `${currentYear - 100 + i}`);
30
+ const months = Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, '0'));
31
+ const days = getDayArray(Number(year), Number(month));
32
+ const monthsNames = [
33
+ "January", "February", "March", "April", "May", "June",
34
+ "July", "August", "September", "October", "November", "December",
35
+ ];
36
+
37
+ // Update handlers for each state separately
38
+ const handleYearChange = (value: string) => {
39
+ setYear(value);
40
+ const newDays = getDayArray(Number(value), Number(month));
41
+ if (!newDays.includes(day)) {
42
+ setDay(newDays[newDays.length - 1]);
43
+ }
44
+ triggerOnChange(value, month, day);
45
+ };
46
+
47
+ const handleMonthChange = (value: string) => {
48
+ setMonth(value);
49
+ const newDays = getDayArray(Number(year), Number(value));
50
+ if (!newDays.includes(day)) {
51
+ setDay(newDays[newDays.length - 1]);
52
+ }
53
+ triggerOnChange(year, value, day);
54
+ };
55
+
56
+ const handleDayChange = (value: string) => {
57
+ setDay(value);
58
+ triggerOnChange(year, month, value);
59
+ };
60
+
61
+ // Function to trigger onChange callback
62
+ const triggerOnChange = (y: string, m: string, d: string) => {
63
+ if (onChange) {
64
+ onChange(new Date(`${y}-${m}-${d}`));
65
+ }
66
+ };
67
+
68
+ return (
69
+ <Popover>
70
+ <PopoverTrigger data-theme={theme} className='w-full flex-1'>
71
+ <InputField
72
+ theme={theme}
73
+ {...props}
74
+ ref={forwardedRef}
75
+ value={`${year}/${month}/${day}`}
76
+ readOnly
77
+ />
78
+ </PopoverTrigger>
79
+ <PopoverContent data-theme={theme} dir="ltr" variant={props.variant} className="overflow-hidden w-[285px] flex justify-center items-center p-[6px] pt-[30px]">
80
+ <div className="flex justify-evenly items-center w-full absolute top-0 py-[6px]">
81
+ <p className="text-content-system-global-secondary typography-headers-medium-regular">Year</p>
82
+ <div className="flex justify-center items-center self-center">
83
+ <span className="h-[13px] w-[1px] bg-border-system-global-secondary rounded-[3px]"></span>
84
+ <p className="text-content-system-global-secondary typography-headers-medium-regular px-[18px]">Month</p>
85
+ <span className="h-[13px] w-[1px] bg-border-system-global-secondary rounded-[3px]"></span>
86
+ </div>
87
+ <p className="text-content-system-global-secondary typography-headers-medium-regular">Day</p>
88
+ </div>
89
+ <div className='absolute inset-0 w-full h-full flex justify-center items-center z-0 p-[6px]'>
90
+ <div className='w-full h-[42px] rounded-[8px] bg-background-system-body-tertiary mt-[23px]'></div>
91
+ </div>
92
+
93
+ <div
94
+ className="relative flex w-full h-[300px] max-w-full mx-auto text-white"
95
+ style={{
96
+ maskImage: 'linear-gradient(to top, transparent, transparent 10%, white 50%, white 19%, transparent 75%, transparent)',
97
+ WebkitMaskImage: 'linear-gradient(to top, transparent, transparent 10%, white 50%, white 19%, transparent 75%, transparent)',
98
+ }}
99
+ >
100
+ <IosPickerItem
101
+ onValueSelect={handleYearChange}
102
+ slideData={years}
103
+ perspective="left"
104
+ selectedValue={year}
105
+ >
106
+ {years.map((value) => (
107
+ <SliderItem key={value}>{value}</SliderItem>
108
+ ))}
109
+ </IosPickerItem>
110
+ <IosPickerItem
111
+ onValueSelect={handleMonthChange}
112
+ slideData={months}
113
+ perspective="left"
114
+ selectedValue={month}
115
+ >
116
+ {months.map((value) => (
117
+ <SliderItem key={value}>
118
+ {`${monthsNames[Number(value) - 1].substring(0, 3)}-${value}`}
119
+ </SliderItem>
120
+ ))}
121
+ </IosPickerItem>
122
+ <IosPickerItem
123
+ onValueSelect={handleDayChange}
124
+ slideData={getDayArray(Number(year), Number(month))}
125
+ perspective="right"
126
+ selectedValue={day}
127
+ >
128
+ {getDayArray(Number(year), Number(month)).map((value) => (
129
+ <SliderItem key={`${value}-days`}>{value}</SliderItem>
130
+ ))}
131
+ </IosPickerItem>
132
+ </div>
133
+ </PopoverContent>
134
+ </Popover>
135
+ );
136
+ }
137
+ );
138
+
139
+ // using with react hook form lib
140
+ /*
141
+ <form onSubmit={handleSubmit(onSubmit)}>
142
+ <Controller
143
+ name="date"
144
+ control={control}
145
+ render={({ field }) => (
146
+ <SlideDatePicker
147
+ {...field}
148
+ onChange={(value: Date) => field.onChange(value)}
149
+ />
150
+ )}
151
+ />
152
+ <button>submit</button>
153
+ </form>
154
+ */
155
+
156
+
157
+
158
+ import { EmblaCarouselType } from 'embla-carousel'
159
+ import useEmblaCarousel from 'embla-carousel-react'
160
+
161
+
162
+ const CIRCLE_DEGREES = 360;
163
+ const WHEEL_ITEM_SIZE = 32;
164
+ const WHEEL_ITEM_COUNT = 18;
165
+ const WHEEL_ITEMS_IN_VIEW = 4;
166
+ const WHEEL_ITEM_RADIUS = CIRCLE_DEGREES / WHEEL_ITEM_COUNT;
167
+ const IN_VIEW_DEGREES = WHEEL_ITEM_RADIUS * WHEEL_ITEMS_IN_VIEW;
168
+ const WHEEL_RADIUS = Math.round(
169
+ WHEEL_ITEM_SIZE / 2 / Math.tan(Math.PI / WHEEL_ITEM_COUNT)
170
+ );
171
+
172
+ const isInView = (wheelLocation: number, slidePosition: number): boolean =>
173
+ Math.abs(wheelLocation - slidePosition) < IN_VIEW_DEGREES;
174
+
175
+ interface PropType extends HTMLAttributes<HTMLDivElement> {
176
+ loop?: boolean;
177
+ slideData: string[];
178
+ selectedValue: any
179
+ perspective: 'left' | 'right';
180
+ onValueSelect?: (value: string) => void; // Callback to pass the selected value to the parent
181
+ }
182
+
183
+ const IosPickerItem: React.FC<PropType> = ({
184
+ onValueSelect,
185
+ slideData,
186
+ perspective,
187
+ selectedValue,
188
+ loop = false,
189
+ ...props
190
+ }) => {
191
+ const [emblaRef, emblaApi] = useEmblaCarousel({
192
+ loop,
193
+ axis: 'y',
194
+ dragFree: true,
195
+ containScroll: false,
196
+ watchSlides: false,
197
+ });
198
+
199
+ const rootNodeRef = useRef<HTMLDivElement>(null);
200
+ const [totalRadius, setTotalRadius] = useState(slideData.length * WHEEL_ITEM_RADIUS);
201
+ const [rotationOffset, setRotationOffset] = useState(loop ? 0 : WHEEL_ITEM_RADIUS);
202
+
203
+ // Update totalRadius and rotationOffset when data or loop changes
204
+ useEffect(() => {
205
+ setTotalRadius(slideData.length * WHEEL_ITEM_RADIUS);
206
+ setRotationOffset(loop ? 0 : WHEEL_ITEM_RADIUS);
207
+ }, [slideData, loop]);
208
+
209
+ const inactivateEmblaTransform = useCallback(
210
+ (emblaApi: EmblaCarouselType) => {
211
+ if (!emblaApi) return;
212
+ const { translate, slideLooper } = emblaApi.internalEngine();
213
+ translate.clear();
214
+ translate.toggleActive(false);
215
+ slideLooper.loopPoints.forEach(({ translate }) => {
216
+ translate.clear();
217
+ translate.toggleActive(false);
218
+ });
219
+ },
220
+ []
221
+ );
222
+
223
+ console.log(slideData.length)
224
+
225
+
226
+ const rotateWheel = useCallback(
227
+ (emblaApi: EmblaCarouselType) => {
228
+ const rotation = slideData.length * WHEEL_ITEM_RADIUS - rotationOffset;
229
+ const wheelRotation = rotation * emblaApi.scrollProgress();
230
+ emblaApi.containerNode().style.transform = `translateZ(${0}px) rotateX(${wheelRotation}deg)`;
231
+ emblaApi.slideNodes().forEach((_, index) => {
232
+ setSlideStyles(emblaApi, index, loop, slideData.length, totalRadius);
233
+ });
234
+ },
235
+ [slideData, rotationOffset, totalRadius, loop]
236
+ );
237
+
238
+ const setSlideStyles = useCallback(
239
+ (
240
+ emblaApi: EmblaCarouselType,
241
+ index: number,
242
+ loop: boolean,
243
+ dataLength: number,
244
+ totalRadius: number
245
+ ) => {
246
+ const slideNode = emblaApi.slideNodes()[index];
247
+ const wheelLocation = emblaApi.scrollProgress() * totalRadius;
248
+ const positionDefault = emblaApi.scrollSnapList()[index] * totalRadius;
249
+ const positionLoopStart = positionDefault + totalRadius;
250
+ const positionLoopEnd = positionDefault - totalRadius;
251
+
252
+ let inView = false;
253
+ let angle = index * -WHEEL_ITEM_RADIUS;
254
+
255
+ if (isInView(wheelLocation, positionDefault)) {
256
+ inView = true;
257
+ }
258
+
259
+ if (loop && isInView(wheelLocation, positionLoopEnd)) {
260
+ inView = true;
261
+ angle = -CIRCLE_DEGREES + (dataLength - index) * WHEEL_ITEM_RADIUS;
262
+ }
263
+
264
+ if (loop && isInView(wheelLocation, positionLoopStart)) {
265
+ inView = true;
266
+ angle = -(totalRadius % CIRCLE_DEGREES) - index * WHEEL_ITEM_RADIUS;
267
+ }
268
+
269
+ if (inView) {
270
+ slideNode.style.opacity = '1';
271
+ slideNode.style.transform = `translateY(-${index * 100}%) rotateX(${angle}deg) translateZ(${WHEEL_RADIUS}px)`;
272
+ } else {
273
+ slideNode.style.opacity = '0';
274
+ slideNode.style.transform = 'none';
275
+ }
276
+ },
277
+ [loop, totalRadius]
278
+ );
279
+
280
+ const handleSelection = useCallback(
281
+ (emblaApi: EmblaCarouselType) => {
282
+ const selectedIndex = emblaApi.selectedScrollSnap();
283
+ const selectedValue = slideData[selectedIndex];
284
+ if (onValueSelect) {
285
+ onValueSelect(selectedValue); // Pass the selected value to the parent
286
+ }
287
+ },
288
+ [slideData]
289
+ );
290
+
291
+ useEffect(() => {
292
+ if (!emblaApi) return;
293
+
294
+ emblaApi.on('pointerUp', (emblaApi) => {
295
+ const { scrollTo, target, location } = emblaApi.internalEngine();
296
+ const diffToTarget = target.get() - location.get();
297
+ const factor = Math.abs(diffToTarget) < WHEEL_ITEM_SIZE / 2.5 ? 10 : 0.1;
298
+ const distance = diffToTarget * factor;
299
+ scrollTo.distance(distance, true);
300
+ });
301
+
302
+ emblaApi.on('scroll', rotateWheel);
303
+ emblaApi.on('select', handleSelection); // Listen for slide selection changes
304
+
305
+ emblaApi.on('reInit', (emblaApi) => {
306
+ inactivateEmblaTransform(emblaApi);
307
+ rotateWheel(emblaApi);
308
+ });
309
+
310
+ inactivateEmblaTransform(emblaApi);
311
+ rotateWheel(emblaApi);
312
+ }, [emblaApi]);
313
+
314
+ return (
315
+ <div {...props} className="flex items-center justify-center w-[90px] h-full text-[1.8rem]">
316
+ <div className="min-w-full h-full flex items-center justify-center overflow-hidden touch-pan-x" ref={rootNodeRef}>
317
+ <div
318
+ className={`w-[75px] h-[32px] perspective-[3200px] select-none ${perspective === 'left'
319
+ ? '[perspective-origin:calc(50%+130px)]'
320
+ : '[perspective-origin:calc(50%-130px)]'
321
+ }`}
322
+ ref={emblaRef}
323
+ >
324
+ <div className="flex flex-col w-full h-full [transform-style:preserve-3d] will-change-transform scroll-smooth">
325
+ {props.children}
326
+ </div>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ );
331
+ };
332
+
333
+ interface SliderItemType extends HTMLAttributes<HTMLDivElement> { }
334
+ const SliderItem = (props: SliderItemType) => {
335
+ return (
336
+ <div className="w-full h-full text-[19px] text-center flex items-center justify-center [backface-visibility:hidden] opacity-0" {...props}>
337
+ {props.children}
338
+ </div>
339
+ );
340
+ };
341
+
package/dist/bin/index.js CHANGED
File without changes
@@ -34,8 +34,8 @@ import { useState } from 'react'
34
34
  export function BasicBadgeField() {
35
35
  const [tags] = useState([
36
36
  { id: '1', name: 'React', variant: 'blue' },
37
- { id: '2', name: 'TypeScript', variant: 'bluePurple' },
38
- { id: '3', name: 'Next.js', variant: 'navy' },
37
+ { id: '2', name: 'TypeScript', variant: 'purple' },
38
+ { id: '3', name: 'Next.js', variant: 'slate' },
39
39
  ])
40
40
 
41
41
  return (
@@ -60,12 +60,12 @@ export function TechnologySelector() {
60
60
 
61
61
  const allTags: Tag[] = [
62
62
  { id: '1', name: 'React', variant: 'blue' },
63
- { id: '2', name: 'TypeScript', variant: 'bluePurple' },
64
- { id: '3', name: 'Next.js', variant: 'navy' },
65
- { id: '4', name: 'Tailwind CSS', variant: 'cocktailGreen' },
63
+ { id: '2', name: 'TypeScript', variant: 'purple' },
64
+ { id: '3', name: 'Next.js', variant: 'slate' },
65
+ { id: '4', name: 'Tailwind CSS', variant: 'green' },
66
66
  { id: '5', name: 'Node.js', variant: 'green' },
67
67
  { id: '6', name: 'PostgreSQL', variant: 'blue' },
68
- { id: '7', name: 'MongoDB', variant: 'greenLight' },
68
+ { id: '7', name: 'MongoDB', variant: 'green' },
69
69
  { id: '8', name: 'GraphQL', variant: 'purple' },
70
70
  { id: '9', name: 'REST API', variant: 'gray' },
71
71
  { id: '10', name: 'Docker', variant: 'blue' },
@@ -105,9 +105,9 @@ export function CategoryFilter() {
105
105
  { id: 'development', name: 'Development', variant: 'blue' },
106
106
  { id: 'marketing', name: 'Marketing', variant: 'yellow' },
107
107
  { id: 'sales', name: 'Sales', variant: 'green' },
108
- { id: 'support', name: 'Support', variant: 'redOrange' },
108
+ { id: 'support', name: 'Support', variant: 'orange' },
109
109
  { id: 'hr', name: 'Human Resources', variant: 'rose' },
110
- { id: 'finance', name: 'Finance', variant: 'navy' },
110
+ { id: 'finance', name: 'Finance', variant: 'slate' },
111
111
  ]
112
112
 
113
113
  const [selectedCategories, setSelectedCategories] = useState<Tag[]>([])
@@ -152,13 +152,13 @@ export function SkillsSelector() {
152
152
  { id: 'ts', name: 'TypeScript', variant: 'blue' },
153
153
  { id: 'react', name: 'React', variant: 'blue' },
154
154
  { id: 'vue', name: 'Vue.js', variant: 'green' },
155
- { id: 'angular', name: 'Angular', variant: 'redLight' },
156
- { id: 'node', name: 'Node.js', variant: 'greenLight' },
155
+ { id: 'angular', name: 'Angular', variant: 'red' },
156
+ { id: 'node', name: 'Node.js', variant: 'green' },
157
157
  { id: 'python', name: 'Python', variant: 'blue' },
158
- { id: 'java', name: 'Java', variant: 'redOrange' },
159
- { id: 'go', name: 'Go', variant: 'cocktailGreen' },
160
- { id: 'rust', name: 'Rust', variant: 'redOrange' },
161
- { id: 'sql', name: 'SQL', variant: 'navy' },
158
+ { id: 'java', name: 'Java', variant: 'orange' },
159
+ { id: 'go', name: 'Go', variant: 'green' },
160
+ { id: 'rust', name: 'Rust', variant: 'orange' },
161
+ { id: 'sql', name: 'SQL', variant: 'slate' },
162
162
  { id: 'nosql', name: 'NoSQL', variant: 'green' },
163
163
  { id: 'aws', name: 'AWS', variant: 'yellow' },
164
164
  { id: 'azure', name: 'Azure', variant: 'blue' },
@@ -213,16 +213,16 @@ Tag management for projects with custom variants.
213
213
  ```tsx
214
214
  export function ProjectTags() {
215
215
  const projectTags: Tag[] = [
216
- { id: '1', name: 'High Priority', variant: 'redLight' },
216
+ { id: '1', name: 'High Priority', variant: 'red' },
217
217
  { id: '2', name: 'In Progress', variant: 'blue' },
218
218
  { id: '3', name: 'Completed', variant: 'green' },
219
219
  { id: '4', name: 'On Hold', variant: 'yellow' },
220
220
  { id: '5', name: 'Needs Review', variant: 'purple' },
221
- { id: '6', name: 'Client Approval', variant: 'bluePurple' },
221
+ { id: '6', name: 'Client Approval', variant: 'purple' },
222
222
  { id: '7', name: 'Internal', variant: 'gray' },
223
- { id: '8', name: 'External', variant: 'navy' },
224
- { id: '9', name: 'Urgent', variant: 'redOrange' },
225
- { id: '10', name: 'Can Wait', variant: 'greenLight' },
223
+ { id: '8', name: 'External', variant: 'slate' },
224
+ { id: '9', name: 'Urgent', variant: 'orange' },
225
+ { id: '10', name: 'Can Wait', variant: 'green' },
226
226
  ]
227
227
 
228
228
  const [projectData, setProjectData] = useState({
@@ -310,7 +310,7 @@ export function BadgeFieldWithIcon() {
310
310
  const tags: Tag[] = [
311
311
  { id: '1', name: 'JavaScript', variant: 'yellow' },
312
312
  { id: '2', name: 'Python', variant: 'blue' },
313
- { id: '3', name: 'Ruby', variant: 'redLight' },
313
+ { id: '3', name: 'Ruby', variant: 'red' },
314
314
  ]
315
315
 
316
316
  return (
@@ -385,7 +385,7 @@ export function EmailRecipients() {
385
385
  { id: '1', name: 'john@example.com', variant: 'blue' },
386
386
  { id: '2', name: 'jane@example.com', variant: 'green' },
387
387
  { id: '3', name: 'team@example.com', variant: 'purple' },
388
- { id: '4', name: 'support@example.com', variant: 'navy' },
388
+ { id: '4', name: 'support@example.com', variant: 'slate' },
389
389
  ]
390
390
 
391
391
  const [recipients, setRecipients] = useState<Tag[]>([])