barua-ui 0.2.3 → 0.3.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.
@@ -232,6 +232,34 @@
232
232
  opacity: 0;
233
233
  transition: opacity var(--b-duration-slow) var(--b-ease-out);
234
234
  }
235
+ /* ---- Blur-up ------------------------------------------------------------
236
+ *
237
+ * A shimmer says "something is coming". A blurred copy of the actual picture
238
+ * says "this is what is coming", which is a different and better promise —
239
+ * the layout is right, the colours are right, and the photograph resolves
240
+ * rather than appearing.
241
+ *
242
+ * The placeholder is a thumbnail small enough to inline: a 32px-wide JPEG is
243
+ * around a kilobyte, so it costs nothing and arrives with the markup. Give
244
+ * it as a data URI on --b-img-placeholder and mark the host with
245
+ * data-placeholder, which is what tells the shimmer to stand down.
246
+ */
247
+ .b-async-img[data-placeholder]::before { display: none; }
248
+
249
+ .b-async-img[data-placeholder]::after {
250
+ content: "";
251
+ position: absolute;
252
+ inset: 0;
253
+ background: var(--b-img-placeholder) center / cover no-repeat;
254
+ /* Scaled up because a blur samples past the edges and would otherwise
255
+ leave a soft border all the way round. */
256
+ filter: blur(14px);
257
+ transform: scale(1.1);
258
+ transition: opacity var(--b-duration-slow) var(--b-ease-out);
259
+ pointer-events: none;
260
+ }
261
+ .b-async-img[data-placeholder].is-loaded::after { opacity: 0; }
262
+
235
263
  .b-async-img.is-loaded::before { display: none; }
236
264
  .b-async-img.is-loaded img { opacity: 1; }
237
265
 
package/css/utilities.css CHANGED
@@ -470,12 +470,101 @@
470
470
  }
471
471
 
472
472
  /* ---- Symbol effects (SF Symbols bounce / pulse) -------------------------- */
473
+ /*
474
+ * The roster SF Symbols established, because a glyph that answers a press is
475
+ * the cheapest feedback an interface has. Each is one-shot unless it is
476
+ * describing an ongoing state — bounce acknowledges, breathe waits.
477
+ * prefers-reduced-motion is handled once, globally, in base.css.
478
+ */
473
479
  .b-icon--bounce svg, .b-icon--bounce { animation: b-icon-bounce 0.5s var(--b-ease-bounce); }
474
480
  .b-icon--pulse svg, .b-icon--pulse { animation: b-pulse 1.6s var(--b-ease-standard) infinite; }
481
+ .b-icon--scale svg, .b-icon--scale { animation: b-icon-scale 0.32s var(--b-ease-standard); }
482
+ .b-icon--wiggle svg, .b-icon--wiggle { animation: b-icon-wiggle 0.5s var(--b-ease-standard); }
483
+ .b-icon--rotate svg, .b-icon--rotate { animation: b-icon-rotate 1.1s linear infinite; }
484
+ .b-icon--breathe svg, .b-icon--breathe { animation: b-icon-breathe 3s var(--b-ease-standard) infinite; }
485
+ .b-icon--replace svg, .b-icon--replace { animation: b-icon-replace 0.3s var(--b-ease-standard); }
486
+
475
487
  @keyframes b-icon-bounce {
476
488
  30% { translate: 0 -3px; scale: 1.15; }
477
489
  60% { translate: 0 1px; scale: 0.96; }
478
490
  }
491
+ @keyframes b-icon-scale {
492
+ 50% { scale: 1.18; }
493
+ }
494
+ /* Small angles: a glyph that swings far reads as broken rather than lively. */
495
+ @keyframes b-icon-wiggle {
496
+ 20% { rotate: -7deg; }
497
+ 45% { rotate: 6deg; }
498
+ 70% { rotate: -3deg; }
499
+ }
500
+ @keyframes b-icon-rotate {
501
+ to { rotate: 360deg; }
502
+ }
503
+ @keyframes b-icon-breathe {
504
+ 50% { scale: 1.06; opacity: 0.75; }
505
+ }
506
+ /* The swap SwiftUI does between two glyphs: the old one leaves, the new
507
+ arrives. Put the class on the icon at the moment its `name` changes. */
508
+ @keyframes b-icon-replace {
509
+ 0% { scale: 0.6; opacity: 0; }
510
+ 100% { scale: 1; opacity: 1; }
511
+ }
512
+
513
+ /* ---- Scroll-driven reveal ------------------------------------------------
514
+ *
515
+ * The browser drives this from the element's own position in the scrollport,
516
+ * so there is no observer, no scroll listener and no main-thread work: the
517
+ * animation is tied to a timeline rather than to time.
518
+ *
519
+ * Everything stays visible where the timeline is unsupported. An entrance
520
+ * animation that fails closed hides the page. */
521
+ @supports (animation-timeline: view()) {
522
+ .b-reveal {
523
+ animation: b-reveal-in linear both;
524
+ animation-timeline: view();
525
+ /* Finish while the element is still coming up the screen, rather than
526
+ when it is centred — otherwise a reader watches it fade in. */
527
+ animation-range: entry 10% cover 32%;
528
+ }
529
+ .b-reveal--fade { animation-name: b-reveal-fade; }
530
+ .b-reveal--scale { animation-name: b-reveal-scale; }
531
+ .b-reveal--start { animation-name: b-reveal-start; }
532
+ .b-reveal--end { animation-name: b-reveal-end; }
533
+ }
534
+ @keyframes b-reveal-in {
535
+ from { opacity: 0; translate: 0 1.5rem; }
536
+ to { opacity: 1; translate: 0 0; }
537
+ }
538
+ @keyframes b-reveal-fade {
539
+ from { opacity: 0; }
540
+ to { opacity: 1; }
541
+ }
542
+ @keyframes b-reveal-scale {
543
+ from { opacity: 0; scale: 0.94; }
544
+ to { opacity: 1; scale: 1; }
545
+ }
546
+ @keyframes b-reveal-start {
547
+ from { opacity: 0; translate: -1.5rem 0; }
548
+ to { opacity: 1; translate: 0 0; }
549
+ }
550
+ @keyframes b-reveal-end {
551
+ from { opacity: 0; translate: 1.5rem 0; }
552
+ to { opacity: 1; translate: 0 0; }
553
+ }
554
+
555
+ /* ---- Text that shrinks to fit its container -----------------------------
556
+ *
557
+ * SwiftUI's minimumScaleFactor has no CSS equivalent, but a container query
558
+ * unit does the same job honestly: the size is a fraction of the container's
559
+ * width, clamped so it never becomes unreadable or absurd. Needs a container
560
+ * ancestor — .b-fit-text establishes its own. */
561
+ .b-fit-text {
562
+ container-type: inline-size;
563
+ }
564
+ .b-fit-text > * {
565
+ font-size: clamp(var(--b-fit-min, 0.85rem), var(--b-fit-scale, 9cqi), var(--b-fit-max, 3rem));
566
+ white-space: nowrap;
567
+ }
479
568
 
480
569
  /* ---- Safe area (mobile) ------------------------------------------------ */
481
570
  .b-safe-area {
package/dist/index.cjs CHANGED
@@ -97,6 +97,8 @@ __export(index_exports, {
97
97
  CommandList: () => CommandList,
98
98
  CommandPalette: () => CommandPalette,
99
99
  Container: () => Container,
100
+ ContextMenuTarget: () => ContextMenuTarget,
101
+ CountUp: () => CountUp,
100
102
  Dashboard: () => Dashboard,
101
103
  DataGrid: () => DataGrid,
102
104
  DatePicker: () => DatePicker,
@@ -127,6 +129,7 @@ __export(index_exports, {
127
129
  FileTile: () => FileTile,
128
130
  FilterBar: () => FilterBar,
129
131
  FilterChip: () => FilterChip,
132
+ FitText: () => FitText,
130
133
  Footnote: () => Footnote,
131
134
  Footnotes: () => Footnotes,
132
135
  Form: () => Form,
@@ -211,6 +214,7 @@ __export(index_exports, {
211
214
  Resizable: () => Resizable,
212
215
  ResizeHandle: () => ResizeHandle,
213
216
  Result: () => Result,
217
+ Reveal: () => Reveal,
214
218
  SafeArea: () => SafeArea,
215
219
  ScatterChart: () => ScatterChart,
216
220
  ScrollArea: () => ScrollArea,
@@ -2391,7 +2395,32 @@ var OnboardingFeature = (0, import_react19.forwardRef)(
2391
2395
  var import_react20 = require("react");
2392
2396
  var import_jsx_runtime20 = require("react/jsx-runtime");
2393
2397
  var MediaPlaceholder = block("div", "b-media-placeholder", "MediaPlaceholder");
2394
- var AsyncImage = block("span", "b-async-img", "AsyncImage");
2398
+ var AsyncImage = (0, import_react20.forwardRef)(function AsyncImage2({ src, alt, placeholder, aspect, className, style, ...rest }, ref) {
2399
+ const [loaded, setLoaded] = (0, import_react20.useState)(false);
2400
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2401
+ "span",
2402
+ {
2403
+ className: cn("b-async-img", loaded && "is-loaded", className),
2404
+ "data-placeholder": placeholder ? "" : void 0,
2405
+ style: {
2406
+ ...aspect ? { aspectRatio: aspect } : null,
2407
+ ...placeholder ? { ["--b-img-placeholder"]: `url("${placeholder}")` } : null,
2408
+ ...style
2409
+ },
2410
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2411
+ "img",
2412
+ {
2413
+ ref,
2414
+ src,
2415
+ alt,
2416
+ onLoad: () => setLoaded(true),
2417
+ onError: () => setLoaded(true),
2418
+ ...rest
2419
+ }
2420
+ )
2421
+ }
2422
+ );
2423
+ });
2395
2424
  var Web = block("div", "b-web", "Web");
2396
2425
  var Video = block("video", "b-video", "Video");
2397
2426
  var Audio = block("div", "b-audio", "Audio");
@@ -2466,6 +2495,90 @@ var CommandCenterTile = block("div", "b-cc__tile", "CommandCenterTile");
2466
2495
  var CommandCenterLabel = block("div", "b-cc__label", "CommandCenterLabel");
2467
2496
  var CommandCenterStatus = block("div", "b-cc__status", "CommandCenterStatus");
2468
2497
  var ImageLens = block("div", "b-lens", "ImageLens");
2498
+
2499
+ // src/motion.tsx
2500
+ var import_react21 = require("react");
2501
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2502
+ var Reveal = (0, import_react21.forwardRef)(function Reveal2({ as: Tag2 = "div", variant = "up", className, children, ...rest }, ref) {
2503
+ const Component = Tag2;
2504
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2505
+ Component,
2506
+ {
2507
+ ref,
2508
+ className: cn("b-reveal", variant !== "up" && `b-reveal--${variant}`, className),
2509
+ ...rest,
2510
+ children
2511
+ }
2512
+ );
2513
+ });
2514
+ var FitText = (0, import_react21.forwardRef)(function FitText2({ min, max, scale, className, style, children, ...rest }, ref) {
2515
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2516
+ "div",
2517
+ {
2518
+ ref,
2519
+ className: cn("b-fit-text", className),
2520
+ style: {
2521
+ ...min ? { ["--b-fit-min"]: min } : null,
2522
+ ...max ? { ["--b-fit-max"]: max } : null,
2523
+ ...scale ? { ["--b-fit-scale"]: scale } : null,
2524
+ ...style
2525
+ },
2526
+ ...rest,
2527
+ children
2528
+ }
2529
+ );
2530
+ });
2531
+ var CountUp = (0, import_react21.forwardRef)(function CountUp2({ to, decimals = 0, duration = 700, prefix = "", suffix = "", grouped = true, className, ...rest }, ref) {
2532
+ const node = (0, import_react21.useRef)(null);
2533
+ const from = (0, import_react21.useRef)(0);
2534
+ (0, import_react21.useEffect)(() => {
2535
+ const el = node.current;
2536
+ if (!el) return;
2537
+ const start = from.current;
2538
+ const target = to;
2539
+ from.current = to;
2540
+ const render = (value) => {
2541
+ const text = grouped ? value.toLocaleString(void 0, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : value.toFixed(decimals);
2542
+ el.textContent = `${prefix}${text}${suffix}`;
2543
+ };
2544
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
2545
+ render(target);
2546
+ return;
2547
+ }
2548
+ let frame = 0;
2549
+ const began = performance.now();
2550
+ const step = (now) => {
2551
+ const p = Math.min(1, (now - began) / duration);
2552
+ render(start + (target - start) * (1 - Math.pow(1 - p, 3)));
2553
+ if (p < 1) frame = requestAnimationFrame(step);
2554
+ };
2555
+ frame = requestAnimationFrame(step);
2556
+ return () => cancelAnimationFrame(frame);
2557
+ }, [to, decimals, duration, prefix, suffix, grouped]);
2558
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2559
+ "span",
2560
+ {
2561
+ ref: (el) => {
2562
+ node.current = el;
2563
+ if (typeof ref === "function") ref(el);
2564
+ else if (ref) ref.current = el;
2565
+ },
2566
+ className: cn("b-tabular-nums", className),
2567
+ ...rest,
2568
+ children: [
2569
+ prefix,
2570
+ 0 .toFixed(decimals),
2571
+ suffix
2572
+ ]
2573
+ }
2574
+ );
2575
+ });
2576
+ var ContextMenuTarget = (0, import_react21.forwardRef)(
2577
+ function ContextMenuTarget2({ as: Tag2 = "div", menu, children, ...rest }, ref) {
2578
+ const Component = Tag2;
2579
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Component, { ref, "data-b-contextmenu": menu, ...rest, children });
2580
+ }
2581
+ );
2469
2582
  // Annotate the CommonJS export names for ESM import in node:
2470
2583
  0 && (module.exports = {
2471
2584
  Accordion,
@@ -2544,6 +2657,8 @@ var ImageLens = block("div", "b-lens", "ImageLens");
2544
2657
  CommandList,
2545
2658
  CommandPalette,
2546
2659
  Container,
2660
+ ContextMenuTarget,
2661
+ CountUp,
2547
2662
  Dashboard,
2548
2663
  DataGrid,
2549
2664
  DatePicker,
@@ -2574,6 +2689,7 @@ var ImageLens = block("div", "b-lens", "ImageLens");
2574
2689
  FileTile,
2575
2690
  FilterBar,
2576
2691
  FilterChip,
2692
+ FitText,
2577
2693
  Footnote,
2578
2694
  Footnotes,
2579
2695
  Form,
@@ -2658,6 +2774,7 @@ var ImageLens = block("div", "b-lens", "ImageLens");
2658
2774
  Resizable,
2659
2775
  ResizeHandle,
2660
2776
  Result,
2777
+ Reveal,
2661
2778
  SafeArea,
2662
2779
  ScatterChart,
2663
2780
  ScrollArea,