@opensite/ui 0.0.7 → 0.0.8

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 (54) hide show
  1. package/README.md +46 -0
  2. package/dist/components.cjs +430 -26
  3. package/dist/components.cjs.map +1 -1
  4. package/dist/components.d.cts +3 -1
  5. package/dist/components.d.ts +3 -1
  6. package/dist/components.js +425 -23
  7. package/dist/components.js.map +1 -1
  8. package/dist/dynamic-icon.cjs +90 -19
  9. package/dist/dynamic-icon.cjs.map +1 -1
  10. package/dist/dynamic-icon.d.cts +12 -6
  11. package/dist/dynamic-icon.d.ts +12 -6
  12. package/dist/dynamic-icon.js +90 -19
  13. package/dist/dynamic-icon.js.map +1 -1
  14. package/dist/feature-showcase.cjs +123 -66
  15. package/dist/feature-showcase.cjs.map +1 -1
  16. package/dist/feature-showcase.js +119 -62
  17. package/dist/feature-showcase.js.map +1 -1
  18. package/dist/hooks.cjs +207 -0
  19. package/dist/hooks.cjs.map +1 -0
  20. package/dist/hooks.d.cts +2 -0
  21. package/dist/hooks.d.ts +2 -0
  22. package/dist/hooks.js +185 -0
  23. package/dist/hooks.js.map +1 -0
  24. package/dist/index.cjs +432 -26
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +4 -1
  27. package/dist/index.d.ts +4 -1
  28. package/dist/index.js +426 -23
  29. package/dist/index.js.map +1 -1
  30. package/dist/media-hover-ctas.cjs +75 -0
  31. package/dist/media-hover-ctas.cjs.map +1 -0
  32. package/dist/media-hover-ctas.d.cts +83 -0
  33. package/dist/media-hover-ctas.d.ts +83 -0
  34. package/dist/media-hover-ctas.js +73 -0
  35. package/dist/media-hover-ctas.js.map +1 -0
  36. package/dist/pressable.cjs +333 -0
  37. package/dist/pressable.cjs.map +1 -0
  38. package/dist/pressable.d.cts +133 -0
  39. package/dist/pressable.d.ts +133 -0
  40. package/dist/pressable.js +311 -0
  41. package/dist/pressable.js.map +1 -0
  42. package/dist/registry.cjs +240 -66
  43. package/dist/registry.cjs.map +1 -1
  44. package/dist/registry.js +237 -63
  45. package/dist/registry.js.map +1 -1
  46. package/dist/types.d.cts +58 -1
  47. package/dist/types.d.ts +58 -1
  48. package/dist/use-navigation.cjs +206 -0
  49. package/dist/use-navigation.cjs.map +1 -0
  50. package/dist/use-navigation.d.cts +49 -0
  51. package/dist/use-navigation.d.ts +49 -0
  52. package/dist/use-navigation.js +184 -0
  53. package/dist/use-navigation.js.map +1 -0
  54. package/package.json +22 -1
package/dist/registry.cjs CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  var clsx = require('clsx');
4
4
  var tailwindMerge = require('tailwind-merge');
5
- var React3 = require('react');
5
+ var React4 = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
+ var img = require('@page-speed/img');
7
8
  var useEmblaCarousel = require('embla-carousel-react');
8
9
  var reactSlot = require('@radix-ui/react-slot');
9
10
  var classVarianceAuthority = require('class-variance-authority');
@@ -28,7 +29,7 @@ function _interopNamespace(e) {
28
29
  return Object.freeze(n);
29
30
  }
30
31
 
31
- var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
32
+ var React4__namespace = /*#__PURE__*/_interopNamespace(React4);
32
33
  var useEmblaCarousel__default = /*#__PURE__*/_interopDefault(useEmblaCarousel);
33
34
 
34
35
  // lib/utils.ts
@@ -44,7 +45,7 @@ var maxWidthStyles = {
44
45
  "4xl": "max-w-[1536px]",
45
46
  full: "max-w-full"
46
47
  };
47
- var Container = React3__namespace.default.forwardRef(
48
+ var Container = React4__namespace.default.forwardRef(
48
49
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
49
50
  const Component = as;
50
51
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -78,7 +79,7 @@ var spacingStyles = {
78
79
  lg: "py-20 md:py-32",
79
80
  xl: "py-24 md:py-40"
80
81
  };
81
- var Section = React3__namespace.default.forwardRef(
82
+ var Section = React4__namespace.default.forwardRef(
82
83
  ({
83
84
  id,
84
85
  title,
@@ -154,54 +155,66 @@ function AlternatingBlocks({
154
155
  }
155
156
  );
156
157
  }
157
- var ArrowLeft = ({
158
- size = 24,
159
- className,
160
- strokeWidth = 2,
161
- ...props
162
- }) => {
163
- return /* @__PURE__ */ jsxRuntime.jsx(
164
- "svg",
165
- {
166
- xmlns: "http://www.w3.org/2000/svg",
167
- width: size,
168
- height: size,
169
- viewBox: "0 0 24 24",
170
- fill: "none",
171
- stroke: "currentColor",
172
- strokeWidth,
173
- strokeLinecap: "round",
174
- strokeLinejoin: "round",
175
- className,
176
- ...props,
177
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m12 19l-7-7l7-7m7 7H5" })
178
- }
179
- );
180
- };
181
- var ArrowRight = ({
182
- size = 24,
183
- className,
184
- strokeWidth = 2,
185
- ...props
186
- }) => {
187
- return /* @__PURE__ */ jsxRuntime.jsx(
188
- "svg",
158
+ function MediaHoverCtas(props) {
159
+ const { sectionClassName, gridClassName, items, optixFlowConfig } = props;
160
+ const resolvedItems = items ?? [];
161
+ return /* @__PURE__ */ jsxRuntime.jsx("section", { className: cn("py-32", sectionClassName), children: /* @__PURE__ */ jsxRuntime.jsx(
162
+ "div",
189
163
  {
190
- xmlns: "http://www.w3.org/2000/svg",
191
- width: size,
192
- height: size,
193
- viewBox: "0 0 24 24",
194
- fill: "none",
195
- stroke: "currentColor",
196
- strokeWidth,
197
- strokeLinecap: "round",
198
- strokeLinejoin: "round",
199
- className,
200
- ...props,
201
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h14m-7-7l7 7l-7 7" })
164
+ className: cn(
165
+ "grid min-h-100 grid-cols-1 gap-1 lg:grid-cols-2",
166
+ gridClassName
167
+ ),
168
+ children: resolvedItems.map((item, index) => {
169
+ const CardComponent = item.cardHref ? "a" : "div";
170
+ const hasHoverImage = Boolean(item.onHoverImgSrc);
171
+ const applyHoverBackground = Boolean(
172
+ item.onHoverBackgroundColor && !hasHoverImage
173
+ );
174
+ const cardStyle = item.initialBackgroundColor || applyHoverBackground ? {
175
+ ...item.initialBackgroundColor ? { "--media-hover-cta-bg": item.initialBackgroundColor } : {},
176
+ ...applyHoverBackground ? {
177
+ "--media-hover-cta-hover-bg": item.onHoverBackgroundColor
178
+ } : {}
179
+ } : void 0;
180
+ const baseBackgroundClassName = item.initialBackgroundColor ? "bg-[var(--media-hover-cta-bg)]" : "bg-muted-foreground/10";
181
+ const hoverBackgroundClassName = applyHoverBackground ? "group-hover:bg-[var(--media-hover-cta-hover-bg)]" : "";
182
+ const hoverImageAltText = item.altText ?? "";
183
+ return /* @__PURE__ */ jsxRuntime.jsxs(
184
+ CardComponent,
185
+ {
186
+ ...item.cardHref ? { href: item.cardHref } : {},
187
+ className: cn(
188
+ "group relative flex min-h-100 cursor-pointer items-center overflow-hidden justify-start p-10 transition-colors duration-500",
189
+ index % 2 === 0 ? "md:justify-center" : "md:justify-start md:pl-24",
190
+ baseBackgroundClassName,
191
+ hoverBackgroundClassName
192
+ ),
193
+ style: cardStyle,
194
+ children: [
195
+ item.onHoverImgSrc ? /* @__PURE__ */ jsxRuntime.jsx(
196
+ img.Img,
197
+ {
198
+ src: item.onHoverImgSrc,
199
+ alt: hoverImageAltText,
200
+ "aria-hidden": item.altText ? void 0 : true,
201
+ className: cn(
202
+ "absolute top-0 left-0 z-[-1] h-full w-full object-cover opacity-0 transition-opacity duration-500 group-hover:opacity-100",
203
+ item.imgHoverClassName
204
+ ),
205
+ loading: "lazy",
206
+ optixFlowConfig
207
+ }
208
+ ) : null,
209
+ item.content
210
+ ]
211
+ },
212
+ index
213
+ );
214
+ })
202
215
  }
203
- );
204
- };
216
+ ) });
217
+ }
205
218
  var buttonVariants = classVarianceAuthority.cva(
206
219
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-button text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
207
220
  {
@@ -249,9 +262,114 @@ function Button({
249
262
  }
250
263
  );
251
264
  }
252
- var CarouselContext = React3__namespace.createContext(null);
265
+ var svgCache = /* @__PURE__ */ new Map();
266
+ function DynamicIcon({
267
+ name,
268
+ size = 28,
269
+ color,
270
+ className,
271
+ alt
272
+ }) {
273
+ const [svgContent, setSvgContent] = React4__namespace.useState(null);
274
+ const [isLoading, setIsLoading] = React4__namespace.useState(true);
275
+ const [error, setError] = React4__namespace.useState(null);
276
+ const { url, iconName } = React4__namespace.useMemo(() => {
277
+ const separator = name.includes("/") ? "/" : ":";
278
+ const [prefix, iconName2] = name.split(separator);
279
+ const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}`;
280
+ return {
281
+ url: baseUrl,
282
+ iconName: iconName2
283
+ };
284
+ }, [name, size]);
285
+ React4__namespace.useEffect(() => {
286
+ let isMounted = true;
287
+ const fetchSvg = async () => {
288
+ const cached = svgCache.get(url);
289
+ if (cached) {
290
+ if (isMounted) {
291
+ setSvgContent(cached);
292
+ setIsLoading(false);
293
+ }
294
+ return;
295
+ }
296
+ try {
297
+ setIsLoading(true);
298
+ setError(null);
299
+ const response = await fetch(url);
300
+ if (!response.ok) {
301
+ throw new Error(`Failed to fetch icon: ${response.status}`);
302
+ }
303
+ let svg = await response.text();
304
+ svg = processSvgForCurrentColor(svg);
305
+ svgCache.set(url, svg);
306
+ if (isMounted) {
307
+ setSvgContent(svg);
308
+ setIsLoading(false);
309
+ }
310
+ } catch (err) {
311
+ if (isMounted) {
312
+ setError(err instanceof Error ? err.message : "Failed to load icon");
313
+ setIsLoading(false);
314
+ }
315
+ }
316
+ };
317
+ fetchSvg();
318
+ return () => {
319
+ isMounted = false;
320
+ };
321
+ }, [url]);
322
+ if (isLoading) {
323
+ return /* @__PURE__ */ jsxRuntime.jsx(
324
+ "span",
325
+ {
326
+ className: cn("inline-block", className),
327
+ style: { width: size, height: size },
328
+ "aria-hidden": "true"
329
+ }
330
+ );
331
+ }
332
+ if (error || !svgContent) {
333
+ return /* @__PURE__ */ jsxRuntime.jsx(
334
+ "span",
335
+ {
336
+ className: cn("inline-block", className),
337
+ style: { width: size, height: size },
338
+ role: "img",
339
+ "aria-label": alt || iconName
340
+ }
341
+ );
342
+ }
343
+ return /* @__PURE__ */ jsxRuntime.jsx(
344
+ "span",
345
+ {
346
+ className: cn("inline-flex items-center justify-center", className),
347
+ style: {
348
+ width: size,
349
+ height: size,
350
+ color: color || "inherit"
351
+ },
352
+ role: "img",
353
+ "aria-label": alt || iconName,
354
+ dangerouslySetInnerHTML: { __html: svgContent }
355
+ }
356
+ );
357
+ }
358
+ function processSvgForCurrentColor(svg) {
359
+ let processed = svg;
360
+ processed = processed.replace(
361
+ /stroke=["'](#000000|#000|black)["']/gi,
362
+ 'stroke="currentColor"'
363
+ );
364
+ processed = processed.replace(
365
+ /fill=["'](#000000|#000|black)["']/gi,
366
+ 'fill="currentColor"'
367
+ );
368
+ return processed;
369
+ }
370
+ var CarouselContext = React4__namespace.createContext(null);
253
371
  function useCarousel() {
254
- const context = React3__namespace.useContext(CarouselContext);
372
+ const context = React4__namespace.useContext(CarouselContext);
255
373
  if (!context) {
256
374
  throw new Error("useCarousel must be used within a <Carousel />");
257
375
  }
@@ -273,20 +391,20 @@ function Carousel({
273
391
  },
274
392
  plugins
275
393
  );
276
- const [canScrollPrev, setCanScrollPrev] = React3__namespace.useState(false);
277
- const [canScrollNext, setCanScrollNext] = React3__namespace.useState(false);
278
- const onSelect = React3__namespace.useCallback((api2) => {
394
+ const [canScrollPrev, setCanScrollPrev] = React4__namespace.useState(false);
395
+ const [canScrollNext, setCanScrollNext] = React4__namespace.useState(false);
396
+ const onSelect = React4__namespace.useCallback((api2) => {
279
397
  if (!api2) return;
280
398
  setCanScrollPrev(api2.canScrollPrev());
281
399
  setCanScrollNext(api2.canScrollNext());
282
400
  }, []);
283
- const scrollPrev = React3__namespace.useCallback(() => {
401
+ const scrollPrev = React4__namespace.useCallback(() => {
284
402
  api?.scrollPrev();
285
403
  }, [api]);
286
- const scrollNext = React3__namespace.useCallback(() => {
404
+ const scrollNext = React4__namespace.useCallback(() => {
287
405
  api?.scrollNext();
288
406
  }, [api]);
289
- const handleKeyDown = React3__namespace.useCallback(
407
+ const handleKeyDown = React4__namespace.useCallback(
290
408
  (event) => {
291
409
  if (event.key === "ArrowLeft") {
292
410
  event.preventDefault();
@@ -298,11 +416,11 @@ function Carousel({
298
416
  },
299
417
  [scrollPrev, scrollNext]
300
418
  );
301
- React3__namespace.useEffect(() => {
419
+ React4__namespace.useEffect(() => {
302
420
  if (!api || !setApi) return;
303
421
  setApi(api);
304
422
  }, [api, setApi]);
305
- React3__namespace.useEffect(() => {
423
+ React4__namespace.useEffect(() => {
306
424
  if (!api) return;
307
425
  onSelect(api);
308
426
  api.on("reInit", onSelect);
@@ -400,7 +518,7 @@ function CarouselPrevious({
400
518
  onClick: scrollPrev,
401
519
  ...props,
402
520
  children: [
403
- /* @__PURE__ */ jsxRuntime.jsx(ArrowLeft, {}),
521
+ /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/arrow-left" }),
404
522
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Previous slide" })
405
523
  ]
406
524
  }
@@ -428,7 +546,7 @@ function CarouselNext({
428
546
  onClick: scrollNext,
429
547
  ...props,
430
548
  children: [
431
- /* @__PURE__ */ jsxRuntime.jsx(ArrowRight, {}),
549
+ /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/arrow-right" }),
432
550
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Next slide" })
433
551
  ]
434
552
  }
@@ -447,12 +565,12 @@ function FeatureShowcase({
447
565
  stretchMediaOnMobile = true
448
566
  }) {
449
567
  const baseArrowClassName = "bottom-4 top-auto size-12 translate-y-0 rounded-full border border-current bg-transparent text-current shadow-sm focus:ring-current focus:ring-offset-2 focus:ring-offset-transparent hover:bg-current/10 md:bottom-6";
450
- const [mobileSlideHeight, setMobileSlideHeight] = React3.useState(
568
+ const [mobileSlideHeight, setMobileSlideHeight] = React4.useState(
451
569
  null
452
570
  );
453
- const slideRefs = React3.useRef([]);
571
+ const slideRefs = React4.useRef([]);
454
572
  const mediaWrapperClassName = equalizeOnMobile && stretchMediaOnMobile ? "flex-1 min-h-0 md:flex-none" : "";
455
- React3.useEffect(() => {
573
+ React4.useEffect(() => {
456
574
  if (!equalizeOnMobile) {
457
575
  setMobileSlideHeight(null);
458
576
  return;
@@ -582,6 +700,60 @@ var BLOCK_REGISTRY = {
582
700
  mediaLeft: true
583
701
  }
584
702
  ]}
703
+ />
704
+ `.trim()
705
+ },
706
+ "media-hover-ctas": {
707
+ id: "media-hover-ctas",
708
+ name: "Media Hover CTAs",
709
+ description: "Display CTA cards that reveal background imagery or color on hover. Ideal for mission/vision tiles, service highlights, or campaign prompts.",
710
+ semanticTags: [
711
+ "cta",
712
+ "call-to-action",
713
+ "hover",
714
+ "media",
715
+ "cards",
716
+ "grid",
717
+ "image-hover",
718
+ "tiles",
719
+ "mission",
720
+ "vision"
721
+ ],
722
+ category: "cta",
723
+ component: MediaHoverCtas,
724
+ props: "MediaHoverCtasProps",
725
+ exampleUsage: `
726
+ <MediaHoverCtas
727
+ items={[
728
+ {
729
+ content: (
730
+ <div className="flex max-w-sm flex-col gap-4">
731
+ <span className="text-sm font-semibold uppercase tracking-wide text-primary">
732
+ Our Mission
733
+ </span>
734
+ <p className="text-muted-foreground">
735
+ Deliver remarkable experiences with thoughtful design.
736
+ </p>
737
+ </div>
738
+ ),
739
+ onHoverImgSrc: "/images/mission.jpg",
740
+ altText: "Our Mission"
741
+ },
742
+ {
743
+ content: (
744
+ <div className="flex max-w-sm flex-col gap-4">
745
+ <span className="text-sm font-semibold uppercase tracking-wide text-primary">
746
+ Our Vision
747
+ </span>
748
+ <p className="text-muted-foreground">
749
+ Build the future of adaptive customer experiences.
750
+ </p>
751
+ </div>
752
+ ),
753
+ initialBackgroundColor: "var(--brand-100)",
754
+ onHoverBackgroundColor: "var(--brand-900)"
755
+ }
756
+ ]}
585
757
  />
586
758
  `.trim()
587
759
  },
@@ -669,7 +841,9 @@ function getAllCategories() {
669
841
  function searchBlocks(query) {
670
842
  const lowercaseQuery = query.toLowerCase();
671
843
  return Object.values(BLOCK_REGISTRY).filter(
672
- (block) => block.name.toLowerCase().includes(lowercaseQuery) || block.description.toLowerCase().includes(lowercaseQuery) || block.semanticTags.some((tag) => tag.toLowerCase().includes(lowercaseQuery))
844
+ (block) => block.name.toLowerCase().includes(lowercaseQuery) || block.description.toLowerCase().includes(lowercaseQuery) || block.semanticTags.some(
845
+ (tag) => tag.toLowerCase().includes(lowercaseQuery)
846
+ )
673
847
  );
674
848
  }
675
849