@opensite/ui 0.0.6 → 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 +434 -22
  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 +429 -16
  7. package/dist/components.js.map +1 -1
  8. package/dist/dynamic-icon.cjs +115 -13
  9. package/dist/dynamic-icon.cjs.map +1 -1
  10. package/dist/dynamic-icon.d.cts +12 -7
  11. package/dist/dynamic-icon.d.ts +12 -7
  12. package/dist/dynamic-icon.js +95 -13
  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 +436 -22
  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 +430 -16
  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.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
- import * as React3 from 'react';
4
- import React3__default, { useState, useRef, useEffect } from 'react';
3
+ import * as React4 from 'react';
4
+ import React4__default, { useState, useRef, useEffect } from 'react';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
+ import { Img } from '@page-speed/img';
6
7
  import useEmblaCarousel from 'embla-carousel-react';
7
8
  import { Slot } from '@radix-ui/react-slot';
8
9
  import { cva } from 'class-variance-authority';
@@ -20,7 +21,7 @@ var maxWidthStyles = {
20
21
  "4xl": "max-w-[1536px]",
21
22
  full: "max-w-full"
22
23
  };
23
- var Container = React3__default.forwardRef(
24
+ var Container = React4__default.forwardRef(
24
25
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
25
26
  const Component = as;
26
27
  return /* @__PURE__ */ jsx(
@@ -54,7 +55,7 @@ var spacingStyles = {
54
55
  lg: "py-20 md:py-32",
55
56
  xl: "py-24 md:py-40"
56
57
  };
57
- var Section = React3__default.forwardRef(
58
+ var Section = React4__default.forwardRef(
58
59
  ({
59
60
  id,
60
61
  title,
@@ -130,54 +131,66 @@ function AlternatingBlocks({
130
131
  }
131
132
  );
132
133
  }
133
- var ArrowLeft = ({
134
- size = 24,
135
- className,
136
- strokeWidth = 2,
137
- ...props
138
- }) => {
139
- return /* @__PURE__ */ jsx(
140
- "svg",
141
- {
142
- xmlns: "http://www.w3.org/2000/svg",
143
- width: size,
144
- height: size,
145
- viewBox: "0 0 24 24",
146
- fill: "none",
147
- stroke: "currentColor",
148
- strokeWidth,
149
- strokeLinecap: "round",
150
- strokeLinejoin: "round",
151
- className,
152
- ...props,
153
- children: /* @__PURE__ */ jsx("path", { d: "m12 19l-7-7l7-7m7 7H5" })
154
- }
155
- );
156
- };
157
- var ArrowRight = ({
158
- size = 24,
159
- className,
160
- strokeWidth = 2,
161
- ...props
162
- }) => {
163
- return /* @__PURE__ */ jsx(
164
- "svg",
134
+ function MediaHoverCtas(props) {
135
+ const { sectionClassName, gridClassName, items, optixFlowConfig } = props;
136
+ const resolvedItems = items ?? [];
137
+ return /* @__PURE__ */ jsx("section", { className: cn("py-32", sectionClassName), children: /* @__PURE__ */ jsx(
138
+ "div",
165
139
  {
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__ */ jsx("path", { d: "M5 12h14m-7-7l7 7l-7 7" })
140
+ className: cn(
141
+ "grid min-h-100 grid-cols-1 gap-1 lg:grid-cols-2",
142
+ gridClassName
143
+ ),
144
+ children: resolvedItems.map((item, index) => {
145
+ const CardComponent = item.cardHref ? "a" : "div";
146
+ const hasHoverImage = Boolean(item.onHoverImgSrc);
147
+ const applyHoverBackground = Boolean(
148
+ item.onHoverBackgroundColor && !hasHoverImage
149
+ );
150
+ const cardStyle = item.initialBackgroundColor || applyHoverBackground ? {
151
+ ...item.initialBackgroundColor ? { "--media-hover-cta-bg": item.initialBackgroundColor } : {},
152
+ ...applyHoverBackground ? {
153
+ "--media-hover-cta-hover-bg": item.onHoverBackgroundColor
154
+ } : {}
155
+ } : void 0;
156
+ const baseBackgroundClassName = item.initialBackgroundColor ? "bg-[var(--media-hover-cta-bg)]" : "bg-muted-foreground/10";
157
+ const hoverBackgroundClassName = applyHoverBackground ? "group-hover:bg-[var(--media-hover-cta-hover-bg)]" : "";
158
+ const hoverImageAltText = item.altText ?? "";
159
+ return /* @__PURE__ */ jsxs(
160
+ CardComponent,
161
+ {
162
+ ...item.cardHref ? { href: item.cardHref } : {},
163
+ className: cn(
164
+ "group relative flex min-h-100 cursor-pointer items-center overflow-hidden justify-start p-10 transition-colors duration-500",
165
+ index % 2 === 0 ? "md:justify-center" : "md:justify-start md:pl-24",
166
+ baseBackgroundClassName,
167
+ hoverBackgroundClassName
168
+ ),
169
+ style: cardStyle,
170
+ children: [
171
+ item.onHoverImgSrc ? /* @__PURE__ */ jsx(
172
+ Img,
173
+ {
174
+ src: item.onHoverImgSrc,
175
+ alt: hoverImageAltText,
176
+ "aria-hidden": item.altText ? void 0 : true,
177
+ className: cn(
178
+ "absolute top-0 left-0 z-[-1] h-full w-full object-cover opacity-0 transition-opacity duration-500 group-hover:opacity-100",
179
+ item.imgHoverClassName
180
+ ),
181
+ loading: "lazy",
182
+ optixFlowConfig
183
+ }
184
+ ) : null,
185
+ item.content
186
+ ]
187
+ },
188
+ index
189
+ );
190
+ })
178
191
  }
179
- );
180
- };
192
+ ) });
193
+ }
181
194
  var buttonVariants = cva(
182
195
  "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",
183
196
  {
@@ -225,9 +238,114 @@ function Button({
225
238
  }
226
239
  );
227
240
  }
228
- var CarouselContext = React3.createContext(null);
241
+ var svgCache = /* @__PURE__ */ new Map();
242
+ function DynamicIcon({
243
+ name,
244
+ size = 28,
245
+ color,
246
+ className,
247
+ alt
248
+ }) {
249
+ const [svgContent, setSvgContent] = React4.useState(null);
250
+ const [isLoading, setIsLoading] = React4.useState(true);
251
+ const [error, setError] = React4.useState(null);
252
+ const { url, iconName } = React4.useMemo(() => {
253
+ const separator = name.includes("/") ? "/" : ":";
254
+ const [prefix, iconName2] = name.split(separator);
255
+ const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}`;
256
+ return {
257
+ url: baseUrl,
258
+ iconName: iconName2
259
+ };
260
+ }, [name, size]);
261
+ React4.useEffect(() => {
262
+ let isMounted = true;
263
+ const fetchSvg = async () => {
264
+ const cached = svgCache.get(url);
265
+ if (cached) {
266
+ if (isMounted) {
267
+ setSvgContent(cached);
268
+ setIsLoading(false);
269
+ }
270
+ return;
271
+ }
272
+ try {
273
+ setIsLoading(true);
274
+ setError(null);
275
+ const response = await fetch(url);
276
+ if (!response.ok) {
277
+ throw new Error(`Failed to fetch icon: ${response.status}`);
278
+ }
279
+ let svg = await response.text();
280
+ svg = processSvgForCurrentColor(svg);
281
+ svgCache.set(url, svg);
282
+ if (isMounted) {
283
+ setSvgContent(svg);
284
+ setIsLoading(false);
285
+ }
286
+ } catch (err) {
287
+ if (isMounted) {
288
+ setError(err instanceof Error ? err.message : "Failed to load icon");
289
+ setIsLoading(false);
290
+ }
291
+ }
292
+ };
293
+ fetchSvg();
294
+ return () => {
295
+ isMounted = false;
296
+ };
297
+ }, [url]);
298
+ if (isLoading) {
299
+ return /* @__PURE__ */ jsx(
300
+ "span",
301
+ {
302
+ className: cn("inline-block", className),
303
+ style: { width: size, height: size },
304
+ "aria-hidden": "true"
305
+ }
306
+ );
307
+ }
308
+ if (error || !svgContent) {
309
+ return /* @__PURE__ */ jsx(
310
+ "span",
311
+ {
312
+ className: cn("inline-block", className),
313
+ style: { width: size, height: size },
314
+ role: "img",
315
+ "aria-label": alt || iconName
316
+ }
317
+ );
318
+ }
319
+ return /* @__PURE__ */ jsx(
320
+ "span",
321
+ {
322
+ className: cn("inline-flex items-center justify-center", className),
323
+ style: {
324
+ width: size,
325
+ height: size,
326
+ color: color || "inherit"
327
+ },
328
+ role: "img",
329
+ "aria-label": alt || iconName,
330
+ dangerouslySetInnerHTML: { __html: svgContent }
331
+ }
332
+ );
333
+ }
334
+ function processSvgForCurrentColor(svg) {
335
+ let processed = svg;
336
+ processed = processed.replace(
337
+ /stroke=["'](#000000|#000|black)["']/gi,
338
+ 'stroke="currentColor"'
339
+ );
340
+ processed = processed.replace(
341
+ /fill=["'](#000000|#000|black)["']/gi,
342
+ 'fill="currentColor"'
343
+ );
344
+ return processed;
345
+ }
346
+ var CarouselContext = React4.createContext(null);
229
347
  function useCarousel() {
230
- const context = React3.useContext(CarouselContext);
348
+ const context = React4.useContext(CarouselContext);
231
349
  if (!context) {
232
350
  throw new Error("useCarousel must be used within a <Carousel />");
233
351
  }
@@ -249,20 +367,20 @@ function Carousel({
249
367
  },
250
368
  plugins
251
369
  );
252
- const [canScrollPrev, setCanScrollPrev] = React3.useState(false);
253
- const [canScrollNext, setCanScrollNext] = React3.useState(false);
254
- const onSelect = React3.useCallback((api2) => {
370
+ const [canScrollPrev, setCanScrollPrev] = React4.useState(false);
371
+ const [canScrollNext, setCanScrollNext] = React4.useState(false);
372
+ const onSelect = React4.useCallback((api2) => {
255
373
  if (!api2) return;
256
374
  setCanScrollPrev(api2.canScrollPrev());
257
375
  setCanScrollNext(api2.canScrollNext());
258
376
  }, []);
259
- const scrollPrev = React3.useCallback(() => {
377
+ const scrollPrev = React4.useCallback(() => {
260
378
  api?.scrollPrev();
261
379
  }, [api]);
262
- const scrollNext = React3.useCallback(() => {
380
+ const scrollNext = React4.useCallback(() => {
263
381
  api?.scrollNext();
264
382
  }, [api]);
265
- const handleKeyDown = React3.useCallback(
383
+ const handleKeyDown = React4.useCallback(
266
384
  (event) => {
267
385
  if (event.key === "ArrowLeft") {
268
386
  event.preventDefault();
@@ -274,11 +392,11 @@ function Carousel({
274
392
  },
275
393
  [scrollPrev, scrollNext]
276
394
  );
277
- React3.useEffect(() => {
395
+ React4.useEffect(() => {
278
396
  if (!api || !setApi) return;
279
397
  setApi(api);
280
398
  }, [api, setApi]);
281
- React3.useEffect(() => {
399
+ React4.useEffect(() => {
282
400
  if (!api) return;
283
401
  onSelect(api);
284
402
  api.on("reInit", onSelect);
@@ -376,7 +494,7 @@ function CarouselPrevious({
376
494
  onClick: scrollPrev,
377
495
  ...props,
378
496
  children: [
379
- /* @__PURE__ */ jsx(ArrowLeft, {}),
497
+ /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/arrow-left" }),
380
498
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Previous slide" })
381
499
  ]
382
500
  }
@@ -404,7 +522,7 @@ function CarouselNext({
404
522
  onClick: scrollNext,
405
523
  ...props,
406
524
  children: [
407
- /* @__PURE__ */ jsx(ArrowRight, {}),
525
+ /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/arrow-right" }),
408
526
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Next slide" })
409
527
  ]
410
528
  }
@@ -558,6 +676,60 @@ var BLOCK_REGISTRY = {
558
676
  mediaLeft: true
559
677
  }
560
678
  ]}
679
+ />
680
+ `.trim()
681
+ },
682
+ "media-hover-ctas": {
683
+ id: "media-hover-ctas",
684
+ name: "Media Hover CTAs",
685
+ description: "Display CTA cards that reveal background imagery or color on hover. Ideal for mission/vision tiles, service highlights, or campaign prompts.",
686
+ semanticTags: [
687
+ "cta",
688
+ "call-to-action",
689
+ "hover",
690
+ "media",
691
+ "cards",
692
+ "grid",
693
+ "image-hover",
694
+ "tiles",
695
+ "mission",
696
+ "vision"
697
+ ],
698
+ category: "cta",
699
+ component: MediaHoverCtas,
700
+ props: "MediaHoverCtasProps",
701
+ exampleUsage: `
702
+ <MediaHoverCtas
703
+ items={[
704
+ {
705
+ content: (
706
+ <div className="flex max-w-sm flex-col gap-4">
707
+ <span className="text-sm font-semibold uppercase tracking-wide text-primary">
708
+ Our Mission
709
+ </span>
710
+ <p className="text-muted-foreground">
711
+ Deliver remarkable experiences with thoughtful design.
712
+ </p>
713
+ </div>
714
+ ),
715
+ onHoverImgSrc: "/images/mission.jpg",
716
+ altText: "Our Mission"
717
+ },
718
+ {
719
+ content: (
720
+ <div className="flex max-w-sm flex-col gap-4">
721
+ <span className="text-sm font-semibold uppercase tracking-wide text-primary">
722
+ Our Vision
723
+ </span>
724
+ <p className="text-muted-foreground">
725
+ Build the future of adaptive customer experiences.
726
+ </p>
727
+ </div>
728
+ ),
729
+ initialBackgroundColor: "var(--brand-100)",
730
+ onHoverBackgroundColor: "var(--brand-900)"
731
+ }
732
+ ]}
561
733
  />
562
734
  `.trim()
563
735
  },
@@ -645,7 +817,9 @@ function getAllCategories() {
645
817
  function searchBlocks(query) {
646
818
  const lowercaseQuery = query.toLowerCase();
647
819
  return Object.values(BLOCK_REGISTRY).filter(
648
- (block) => block.name.toLowerCase().includes(lowercaseQuery) || block.description.toLowerCase().includes(lowercaseQuery) || block.semanticTags.some((tag) => tag.toLowerCase().includes(lowercaseQuery))
820
+ (block) => block.name.toLowerCase().includes(lowercaseQuery) || block.description.toLowerCase().includes(lowercaseQuery) || block.semanticTags.some(
821
+ (tag) => tag.toLowerCase().includes(lowercaseQuery)
822
+ )
649
823
  );
650
824
  }
651
825