@kushagradhawan/kookie-ui 0.1.14 → 0.1.16

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 (50) hide show
  1. package/components.css +1324 -131
  2. package/dist/cjs/components/dropdown-menu.js +1 -1
  3. package/dist/cjs/components/dropdown-menu.js.map +2 -2
  4. package/dist/cjs/components/icons.d.ts +2 -1
  5. package/dist/cjs/components/icons.d.ts.map +1 -1
  6. package/dist/cjs/components/icons.js +1 -1
  7. package/dist/cjs/components/icons.js.map +3 -3
  8. package/dist/cjs/components/image.d.ts +23 -2
  9. package/dist/cjs/components/image.d.ts.map +1 -1
  10. package/dist/cjs/components/image.js +1 -1
  11. package/dist/cjs/components/image.js.map +3 -3
  12. package/dist/cjs/components/sidebar.d.ts +117 -42
  13. package/dist/cjs/components/sidebar.d.ts.map +1 -1
  14. package/dist/cjs/components/sidebar.js +1 -1
  15. package/dist/cjs/components/sidebar.js.map +3 -3
  16. package/dist/cjs/components/sidebar.props.d.ts +17 -10
  17. package/dist/cjs/components/sidebar.props.d.ts.map +1 -1
  18. package/dist/cjs/components/sidebar.props.js +1 -1
  19. package/dist/cjs/components/sidebar.props.js.map +3 -3
  20. package/dist/esm/components/dropdown-menu.js +1 -1
  21. package/dist/esm/components/dropdown-menu.js.map +3 -3
  22. package/dist/esm/components/icons.d.ts +2 -1
  23. package/dist/esm/components/icons.d.ts.map +1 -1
  24. package/dist/esm/components/icons.js +1 -1
  25. package/dist/esm/components/icons.js.map +3 -3
  26. package/dist/esm/components/image.d.ts +23 -2
  27. package/dist/esm/components/image.d.ts.map +1 -1
  28. package/dist/esm/components/image.js +1 -1
  29. package/dist/esm/components/image.js.map +3 -3
  30. package/dist/esm/components/sidebar.d.ts +117 -42
  31. package/dist/esm/components/sidebar.d.ts.map +1 -1
  32. package/dist/esm/components/sidebar.js +1 -1
  33. package/dist/esm/components/sidebar.js.map +3 -3
  34. package/dist/esm/components/sidebar.props.d.ts +17 -10
  35. package/dist/esm/components/sidebar.props.d.ts.map +1 -1
  36. package/dist/esm/components/sidebar.props.js +1 -1
  37. package/dist/esm/components/sidebar.props.js.map +3 -3
  38. package/package.json +1 -1
  39. package/src/components/_internal/base-button.css +2 -9
  40. package/src/components/_internal/base-menu.css +2 -2
  41. package/src/components/button.css +2 -2
  42. package/src/components/dropdown-menu.tsx +2 -2
  43. package/src/components/icon-button.css +2 -2
  44. package/src/components/icons.tsx +18 -1
  45. package/src/components/image.css +5 -0
  46. package/src/components/image.tsx +173 -11
  47. package/src/components/sidebar.css +850 -54
  48. package/src/components/sidebar.props.tsx +15 -11
  49. package/src/components/sidebar.tsx +500 -367
  50. package/styles.css +1324 -131
@@ -8,6 +8,7 @@ import { extractProps } from '../helpers/extract-props.js';
8
8
  import { marginPropDefs } from '../props/margin.props.js';
9
9
  import { widthPropDefs } from '../props/width.props.js';
10
10
  import { heightPropDefs } from '../props/height.props.js';
11
+ import { Skeleton } from './skeleton.js';
11
12
 
12
13
  import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props.js';
13
14
  import type { MarginProps } from '../props/margin.props.js';
@@ -18,6 +19,27 @@ import type { GetPropDefTypes } from '../props/prop-def.js';
18
19
  type ImageElement = React.ElementRef<'img'>;
19
20
  type ImageOwnProps = GetPropDefTypes<typeof imagePropDefs> & {
20
21
  loading?: 'eager' | 'lazy';
22
+ /**
23
+ * Placeholder image URL to show while the main image is loading.
24
+ * Can be a low-quality/blurred version of the main image.
25
+ */
26
+ placeholder?: string;
27
+ /**
28
+ * Shows a skeleton placeholder while loading instead of a blank space.
29
+ */
30
+ showSkeleton?: boolean;
31
+ /**
32
+ * Whether the image should fade in when loaded. Set to false for background images or when you want immediate visibility.
33
+ */
34
+ fadeIn?: boolean;
35
+ /**
36
+ * Callback fired when the image successfully loads.
37
+ */
38
+ onLoad?: (event: React.SyntheticEvent<HTMLImageElement>) => void;
39
+ /**
40
+ * Callback fired when the image fails to load.
41
+ */
42
+ onError?: (event: React.SyntheticEvent<HTMLImageElement>) => void;
21
43
  };
22
44
 
23
45
  interface ImageProps
@@ -28,13 +50,13 @@ interface ImageProps
28
50
  ImageOwnProps {
29
51
  /**
30
52
  * The alt attribute provides alternative information for an image if a user for some reason cannot view it.
31
- * Required for accessibility when not using asChild.
53
+ * Required for accessibility when not using asChild. Use empty string for decorative images.
32
54
  */
33
- alt?: string;
55
+ alt: string;
34
56
  }
35
57
 
36
58
  const Image = React.forwardRef<ImageElement, ImageProps>((props, forwardedRef) => {
37
- const { variant = 'surface', fit: _fit = 'cover', children } = props;
59
+ const { variant = 'surface', children } = props;
38
60
  const {
39
61
  asChild,
40
62
  className,
@@ -43,16 +65,108 @@ const Image = React.forwardRef<ImageElement, ImageProps>((props, forwardedRef) =
43
65
  loading = 'lazy',
44
66
  alt,
45
67
  src,
68
+ placeholder,
69
+ showSkeleton = false,
70
+ fadeIn = true,
71
+ onLoad: userOnLoad,
72
+ onError: userOnError,
46
73
  children: _children, // Extract children to exclude from imgProps
47
74
  ...imgProps
48
75
  } = extractProps(props, imagePropDefs, marginPropDefs, widthPropDefs, heightPropDefs);
49
76
 
77
+ // Loading state management
78
+ const [imageLoaded, setImageLoaded] = React.useState(false);
79
+ const [imageError, setImageError] = React.useState(false);
80
+ const [showPlaceholder, setShowPlaceholder] = React.useState(!!placeholder);
81
+
82
+ // Ref to check if image is already loaded (for cached images)
83
+ const imgRef = React.useRef<HTMLImageElement>(null);
84
+
85
+ // Handle image load - moved to top to avoid conditional hook call
86
+ const handleLoad = React.useCallback((event: React.SyntheticEvent<HTMLImageElement>) => {
87
+ setImageLoaded(true);
88
+ setImageError(false);
89
+ setShowPlaceholder(false);
90
+ userOnLoad?.(event);
91
+ }, [userOnLoad]);
92
+
93
+ // Handle image error - moved to top to avoid conditional hook call
94
+ const handleError = React.useCallback((event: React.SyntheticEvent<HTMLImageElement>) => {
95
+ setImageLoaded(false);
96
+ setImageError(true);
97
+ setShowPlaceholder(false);
98
+ userOnError?.(event);
99
+ }, [userOnError]);
100
+
101
+ // Check if image is already loaded (for cached images)
102
+ React.useEffect(() => {
103
+ const img = imgRef.current;
104
+ if (img && img.complete && img.naturalWidth > 0) {
105
+ setImageLoaded(true);
106
+ setImageError(false);
107
+ setShowPlaceholder(false);
108
+ }
109
+ }, [src]);
110
+
111
+ // Validate required props
112
+ if (!src) {
113
+ console.warn('Image component: src prop is required');
114
+ return null;
115
+ }
116
+
117
+ if (!asChild && alt === undefined) {
118
+ console.warn('Image component: alt prop is required for accessibility when not using asChild');
119
+ }
120
+
121
+ // Create skeleton placeholder
122
+ const skeletonElement = showSkeleton && !imageLoaded && !imageError ? (
123
+ <Skeleton
124
+ style={{
125
+ ...style,
126
+ width: '100%',
127
+ height: '100px', // Default height, can be overridden by style
128
+ borderRadius: radius ? `var(--radius-${radius})` : undefined,
129
+ }}
130
+ className={className}
131
+ />
132
+ ) : null;
133
+
134
+ // Create placeholder image element
135
+ const placeholderElement = placeholder && showPlaceholder ? (
136
+ <img
137
+ data-radius={radius}
138
+ style={{
139
+ ...style,
140
+ position: 'absolute',
141
+ top: 0,
142
+ left: 0,
143
+ width: '100%',
144
+ height: '100%',
145
+ filter: 'blur(4px)',
146
+ opacity: 0.7,
147
+ transition: 'opacity 0.3s ease-out',
148
+ }}
149
+ className={classNames(
150
+ 'rt-reset',
151
+ 'rt-Image',
152
+ 'rt-Image--placeholder',
153
+ className,
154
+ )}
155
+ alt=""
156
+ src={placeholder}
157
+ />
158
+ ) : null;
159
+
50
160
  // Create the standard img element
51
161
  const imgElement = (
52
162
  <img
53
163
  data-radius={radius}
54
164
  loading={loading}
55
- style={style}
165
+ style={{
166
+ ...style,
167
+ opacity: fadeIn ? (imageLoaded ? 1 : 0) : 1,
168
+ transition: fadeIn ? 'opacity 0.3s ease-out' : 'none',
169
+ }}
56
170
  className={classNames(
57
171
  'rt-reset',
58
172
  'rt-Image',
@@ -61,11 +175,29 @@ const Image = React.forwardRef<ImageElement, ImageProps>((props, forwardedRef) =
61
175
  )}
62
176
  alt={alt}
63
177
  src={src}
178
+ onLoad={handleLoad}
179
+ onError={handleError}
64
180
  {...imgProps}
65
- ref={forwardedRef}
181
+ ref={(node) => {
182
+ imgRef.current = node;
183
+ if (typeof forwardedRef === 'function') {
184
+ forwardedRef(node);
185
+ } else if (forwardedRef) {
186
+ forwardedRef.current = node;
187
+ }
188
+ }}
66
189
  />
67
190
  );
68
191
 
192
+ // Wrapper for images with placeholders
193
+ const imageWithPlaceholder = (placeholder || showSkeleton) ? (
194
+ <div style={{ position: 'relative', display: 'inline-block' }}>
195
+ {skeletonElement}
196
+ {placeholderElement}
197
+ {imgElement}
198
+ </div>
199
+ ) : imgElement;
200
+
69
201
  // Handle asChild - inject img into the child element
70
202
  if (asChild && children) {
71
203
  const child = React.Children.only(children) as React.ReactElement<any>;
@@ -113,12 +245,27 @@ const Image = React.forwardRef<ImageElement, ImageProps>((props, forwardedRef) =
113
245
  <img
114
246
  data-radius={radius}
115
247
  loading={loading}
116
- style={{ ...style, position: 'relative', zIndex: 1 }}
248
+ style={{
249
+ ...style,
250
+ position: 'relative',
251
+ zIndex: 1,
252
+ opacity: fadeIn ? (imageLoaded ? 1 : 0) : 1,
253
+ transition: fadeIn ? 'opacity 0.3s ease-out' : 'none',
254
+ }}
117
255
  className={classNames('rt-reset', 'rt-Image', 'rt-Image--blur', className)}
118
256
  alt={alt}
119
257
  src={src}
258
+ onLoad={handleLoad}
259
+ onError={handleError}
120
260
  {...imgProps}
121
- ref={forwardedRef}
261
+ ref={(node) => {
262
+ imgRef.current = node;
263
+ if (typeof forwardedRef === 'function') {
264
+ forwardedRef(node);
265
+ } else if (forwardedRef) {
266
+ forwardedRef.current = node;
267
+ }
268
+ }}
122
269
  />
123
270
  </>
124
271
  ),
@@ -137,7 +284,7 @@ const Image = React.forwardRef<ImageElement, ImageProps>((props, forwardedRef) =
137
284
  cursor: 'pointer', // Ensure interactive cursor
138
285
  ...child.props?.style, // Allow user overrides
139
286
  },
140
- children: imgElement,
287
+ children: imageWithPlaceholder,
141
288
  });
142
289
  }
143
290
  }
@@ -171,18 +318,33 @@ const Image = React.forwardRef<ImageElement, ImageProps>((props, forwardedRef) =
171
318
  <img
172
319
  data-radius={radius}
173
320
  loading={loading}
174
- style={{ ...style, position: 'relative', zIndex: 1 }}
321
+ style={{
322
+ ...style,
323
+ position: 'relative',
324
+ zIndex: 1,
325
+ opacity: fadeIn ? (imageLoaded ? 1 : 0) : 1,
326
+ transition: fadeIn ? 'opacity 0.3s ease-out' : 'none',
327
+ }}
175
328
  className={classNames('rt-reset', 'rt-Image', 'rt-Image--blur', className)}
176
329
  alt={alt}
177
330
  src={src}
331
+ onLoad={handleLoad}
332
+ onError={handleError}
178
333
  {...imgProps}
179
- ref={forwardedRef}
334
+ ref={(node) => {
335
+ imgRef.current = node;
336
+ if (typeof forwardedRef === 'function') {
337
+ forwardedRef(node);
338
+ } else if (forwardedRef) {
339
+ forwardedRef.current = node;
340
+ }
341
+ }}
180
342
  />
181
343
  </div>
182
344
  );
183
345
  }
184
346
 
185
- return imgElement;
347
+ return imageWithPlaceholder;
186
348
  });
187
349
 
188
350
  Image.displayName = 'Image';