@ceebee/ui 0.3.3 → 0.5.0

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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, AnchorHTMLAttributes, ReactElement } from 'react';
3
3
 
4
4
  /** Joins class names, dropping falsy values. No merge logic: the library never emits
5
5
  * conflicting utility classes, so the "last wins" problem tailwind-merge solves does not exist. */
@@ -45,7 +45,7 @@ interface StackProps {
45
45
  children?: ReactNode;
46
46
  }
47
47
  /** Flex layout whose gap can only be a spacing token — the reason the library has no raw padding. */
48
- declare function Stack({ direction, gap, align, justify, wrap, className, children, }: StackProps): react.JSX.Element;
48
+ declare function Flex({ direction, gap, align, justify, wrap, className, children, }: StackProps): react.JSX.Element;
49
49
  interface GridProps {
50
50
  /** Column count at the widest breakpoint; the grid collapses on its own below it. */
51
51
  columns?: 1 | 2 | 3 | 4 | 6 | 12;
@@ -81,6 +81,54 @@ interface HeadingProps {
81
81
  /** Level and size are separate on purpose: document structure is not a font size. */
82
82
  declare function Heading({ level, size, className, children }: HeadingProps): react.JSX.Element;
83
83
 
84
+ interface LinkButtonProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'color'> {
85
+ variant?: 'solid' | 'soft' | 'outline' | 'ghost';
86
+ tone?: Tone;
87
+ size?: Size;
88
+ iconStart?: ReactNode;
89
+ iconEnd?: ReactNode;
90
+ children?: ReactNode;
91
+ /**
92
+ * Render something else in the anchor's place — a router's own Link, which has
93
+ * to stay itself or client-side navigation is lost. It receives the classes
94
+ * and the tone; everything else it keeps. The same `render` seam Base UI uses
95
+ * elsewhere in this library.
96
+ */
97
+ render?: ReactElement;
98
+ }
99
+ /**
100
+ * A link wearing a button's clothes.
101
+ *
102
+ * Not a `Button` with an `href`. A link navigates and a button acts, and the
103
+ * difference is not decoration: a link can be opened in a new tab, copied,
104
+ * dragged to a bookmark bar and reached by a screen reader's list of links,
105
+ * while a button can be disabled and submit a form. Those are different
106
+ * contracts, so they are different components (ADR 0014).
107
+ *
108
+ * What they share is the look, and that is shared literally — the same
109
+ * `cb-button` classes rather than a second set that would agree today and drift
110
+ * apart later. It is server-safe: an anchor needs no hook, no handler and no
111
+ * motion, so it stays a Server Component (ADR 0004). The press animation is
112
+ * Button's alone, which is honest: a link does not depress, it goes somewhere.
113
+ */
114
+ declare function LinkButton({ variant, tone, size, iconStart, iconEnd, className, children, render, ...rest }: LinkButtonProps): react.JSX.Element;
115
+
116
+ interface DividerProps {
117
+ /** A word or two sitting in the rule, for a section break that names itself. */
118
+ children?: ReactNode;
119
+ orientation?: 'horizontal' | 'vertical';
120
+ className?: string;
121
+ }
122
+ /**
123
+ * A rule between things.
124
+ *
125
+ * `<hr>` when it separates content, because that is what the element means and
126
+ * a screen reader announces it. With a label it becomes a `<div>` carrying the
127
+ * rule in its borders instead — an `<hr>` may not contain anything, and a
128
+ * separator that says which section is starting is worth more than the element.
129
+ */
130
+ declare function Divider({ children, orientation, className }: DividerProps): react.JSX.Element;
131
+
84
132
  interface BadgeProps {
85
133
  children: ReactNode;
86
134
  tone?: Tone;
@@ -102,7 +150,7 @@ interface EmptyStateProps {
102
150
  variant?: 'first-run' | 'search' | 'error';
103
151
  className?: string;
104
152
  }
105
- declare function EmptyState({ title, description, icon, actions, variant, className }: EmptyStateProps): react.JSX.Element;
153
+ declare function Empty({ title, description, icon, actions, variant, className }: EmptyStateProps): react.JSX.Element;
106
154
 
107
155
  interface SpinnerProps {
108
156
  size?: 'sm' | 'md' | 'lg';
@@ -112,7 +160,7 @@ interface SpinnerProps {
112
160
  className?: string;
113
161
  }
114
162
  /** Server-safe: a CSS animation, no state. It stops turning under reduced motion. */
115
- declare function Spinner({ size, tone, label, className }: SpinnerProps): react.JSX.Element;
163
+ declare function Spin({ size, tone, label, className }: SpinnerProps): react.JSX.Element;
116
164
  interface ProgressBarProps {
117
165
  /** Omit for an indeterminate bar — "something is happening, duration unknown". */
118
166
  value?: number;
@@ -371,7 +419,7 @@ interface BreadcrumbsProps {
371
419
  * Server-safe. The last crumb is the current page: it is text with `aria-current`, not a link,
372
420
  * because a link to where you already are is a dead end for keyboard and screen reader users.
373
421
  */
374
- declare function Breadcrumbs({ items, maxItems, className }: BreadcrumbsProps): react.JSX.Element;
422
+ declare function Breadcrumb({ items, maxItems, className }: BreadcrumbsProps): react.JSX.Element;
375
423
 
376
424
  interface Step {
377
425
  label: ReactNode;
@@ -388,7 +436,7 @@ interface StepperProps {
388
436
  * Progress through an ordered sequence — the thing Tabs must not be used for. Server-safe:
389
437
  * it displays position, it does not navigate.
390
438
  */
391
- declare function Stepper({ steps, current, orientation, className }: StepperProps): react.JSX.Element;
439
+ declare function Steps({ steps, current, orientation, className }: StepperProps): react.JSX.Element;
392
440
 
393
441
  interface StatCardProps {
394
442
  label: string;
@@ -406,15 +454,15 @@ interface StatCardProps {
406
454
  className?: string;
407
455
  }
408
456
  /** The tinted metric tile the reference board repeats on every dashboard. */
409
- declare function StatCard({ label, value, delta, caption, hue, icon, visual, className }: StatCardProps): react.JSX.Element;
410
- declare namespace StatCard {
457
+ declare function Statistic({ label, value, delta, caption, hue, icon, visual, className }: StatCardProps): react.JSX.Element;
458
+ declare namespace Statistic {
411
459
  var Skeleton: typeof StatCardSkeleton;
412
460
  }
413
461
  interface StatCardSkeletonProps {
414
462
  withVisual?: boolean;
415
463
  className?: string;
416
464
  }
417
- /** Built from StatCard's own spacing, so the tile does not resize when data arrives (ADR 0009). */
465
+ /** Built from Statistic's own spacing, so the tile does not resize when data arrives (ADR 0009). */
418
466
  declare function StatCardSkeleton({ withVisual, className }: StatCardSkeletonProps): react.JSX.Element;
419
467
 
420
- export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, BarMini, type BarMiniProps, Breadcrumbs, type BreadcrumbsProps, Container, type ContainerProps, type Crumb, type DecorHue, Donut, type DonutArc, type DonutProps, type DonutSlice, EmptyState, type EmptyStateProps, type GlassStyle, Grid, type GridProps, Heading, type HeadingProps, Leaderboard, type LeaderboardEntry, type LeaderboardProps, type LeaderboardSkeletonProps, ProgressBar, type ProgressBarProps, ProgressRing, type ProgressRingProps, type RingGeometry, type Size, Skeleton, type SkeletonProps, type SkeletonTextProps, Sparkline, type SparklineGeometry, type SparklineProps, Spinner, type SpinnerProps, Stack, type StackProps, StatCard, type StatCardProps, type StatCardSkeletonProps, type Step, Stepper, type StepperProps, Surface, type SurfaceProps, type SurfaceVariant, Text, type TextProps, Timeline, type TimelineEntry, type TimelineProps, type TimelineSkeletonProps, type Tone, cn, donutArcs, hueForName, initialsOf, ringGeometry, sparklineGeometry };
468
+ export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, BarMini, type BarMiniProps, Breadcrumb, type BreadcrumbsProps, Container, type ContainerProps, type Crumb, type DecorHue, Divider, type DividerProps, Donut, type DonutArc, type DonutProps, type DonutSlice, Empty, type EmptyStateProps, Flex, type GlassStyle, Grid, type GridProps, Heading, type HeadingProps, Leaderboard, type LeaderboardEntry, type LeaderboardProps, type LeaderboardSkeletonProps, LinkButton, type LinkButtonProps, ProgressBar, type ProgressBarProps, ProgressRing, type ProgressRingProps, type RingGeometry, type Size, Skeleton, type SkeletonProps, type SkeletonTextProps, Sparkline, type SparklineGeometry, type SparklineProps, Spin, type SpinnerProps, type StackProps, type StatCardProps, type StatCardSkeletonProps, Statistic, type Step, type StepperProps, Steps, Surface, type SurfaceProps, type SurfaceVariant, Text, type TextProps, Timeline, type TimelineEntry, type TimelineProps, type TimelineSkeletonProps, type Tone, cn, donutArcs, hueForName, initialsOf, ringGeometry, sparklineGeometry };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { isValidElement, cloneElement } from 'react';
2
3
  import { ChevronRight, Check } from 'lucide-react';
3
4
 
4
5
  // src/lib/cn.ts
@@ -38,7 +39,7 @@ function Surface({
38
39
  }
39
40
  );
40
41
  }
41
- function Stack({
42
+ function Flex({
42
43
  direction = "column",
43
44
  gap = 4,
44
45
  align = "stretch",
@@ -47,11 +48,11 @@ function Stack({
47
48
  className,
48
49
  children
49
50
  }) {
50
- const style = { "--cb-stack-gap": `var(--cb-space-${gap})` };
51
+ const style = { "--cb-flex-gap": `var(--cb-space-${gap})` };
51
52
  return /* @__PURE__ */ jsx(
52
53
  "div",
53
54
  {
54
- className: cn("cb-stack", `cb-stack--${direction}`, wrap && "cb-stack--wrap", className),
55
+ className: cn("cb-flex", `cb-flex--${direction}`, wrap && "cb-flex--wrap", className),
55
56
  style,
56
57
  "data-align": align,
57
58
  "data-justify": justify,
@@ -93,13 +94,72 @@ function Heading({ level = 2, size, className, children }) {
93
94
  const resolved = size ?? ["3xl", "2xl", "xl", "lg"][level - 1] ?? "lg";
94
95
  return /* @__PURE__ */ jsx(Tag, { className: cn("cb-heading", `cb-text--${resolved}`, className), children });
95
96
  }
97
+ function LinkButton({
98
+ variant = "solid",
99
+ tone = "brand",
100
+ size = "md",
101
+ iconStart,
102
+ iconEnd,
103
+ className,
104
+ children,
105
+ render,
106
+ ...rest
107
+ }) {
108
+ const iconOnly = children === void 0 || children === null || children === false || children === "";
109
+ const classes = cn(
110
+ "cb-button",
111
+ "cb-link-button",
112
+ `cb-button--${variant}`,
113
+ `cb-button--${size}`,
114
+ iconOnly && "cb-button--icon",
115
+ className
116
+ );
117
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
118
+ iconStart,
119
+ iconOnly ? null : /* @__PURE__ */ jsx("span", { className: "cb-button__label", children }),
120
+ iconOnly ? null : iconEnd
121
+ ] });
122
+ if (render && isValidElement(render)) {
123
+ return cloneElement(render, {
124
+ className: cn(classes, render.props.className),
125
+ "data-tone": tone,
126
+ ...rest,
127
+ children: body
128
+ });
129
+ }
130
+ return /* @__PURE__ */ jsx(
131
+ "a",
132
+ {
133
+ className: cn(
134
+ "cb-button",
135
+ "cb-link-button",
136
+ `cb-button--${variant}`,
137
+ `cb-button--${size}`,
138
+ iconOnly && "cb-button--icon",
139
+ className
140
+ ),
141
+ "data-tone": tone,
142
+ ...rest,
143
+ children: body
144
+ }
145
+ );
146
+ }
147
+ function Divider({ children, orientation = "horizontal", className }) {
148
+ if (orientation === "vertical") {
149
+ return /* @__PURE__ */ jsx("span", { className: cn("cb-divider", "cb-divider--vertical", className), role: "separator", "aria-orientation": "vertical" });
150
+ }
151
+ if (children) {
152
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-divider", "cb-divider--labelled", className), role: "separator", children: /* @__PURE__ */ jsx("span", { className: "cb-divider__label", children }) });
153
+ }
154
+ return /* @__PURE__ */ jsx("hr", { className: cn("cb-divider", className) });
155
+ }
96
156
  function Badge({ children, tone = "neutral", variant = "soft", size = "md", dot, className }) {
97
157
  return /* @__PURE__ */ jsxs("span", { className: cn("cb-badge", `cb-badge--${variant}`, `cb-badge--${size}`, className), "data-tone": tone, children: [
98
158
  dot ? /* @__PURE__ */ jsx("span", { className: "cb-badge__dot", "aria-hidden": "true" }) : null,
99
159
  children
100
160
  ] });
101
161
  }
102
- function EmptyState({ title, description, icon, actions, variant = "first-run", className }) {
162
+ function Empty({ title, description, icon, actions, variant = "first-run", className }) {
103
163
  return /* @__PURE__ */ jsxs("div", { className: cn("cb-empty", className), "data-variant": variant, children: [
104
164
  icon ? /* @__PURE__ */ jsx("span", { className: "cb-empty__icon", children: icon }) : null,
105
165
  /* @__PURE__ */ jsx("p", { className: "cb-empty__title", children: title }),
@@ -107,11 +167,11 @@ function EmptyState({ title, description, icon, actions, variant = "first-run",
107
167
  actions ? /* @__PURE__ */ jsx("div", { className: "cb-empty__actions", children: actions }) : null
108
168
  ] });
109
169
  }
110
- function Spinner({ size = "md", tone = "brand", label, className }) {
170
+ function Spin({ size = "md", tone = "brand", label, className }) {
111
171
  return /* @__PURE__ */ jsx(
112
172
  "span",
113
173
  {
114
- className: cn("cb-spinner", `cb-spinner--${size}`, className),
174
+ className: cn("cb-spin", `cb-spin--${size}`, className),
115
175
  "data-tone": tone,
116
176
  role: label ? "status" : void 0,
117
177
  "aria-label": label,
@@ -430,7 +490,7 @@ function LeaderboardSkeleton({ rows = 5, className }) {
430
490
  ] }, index)) });
431
491
  }
432
492
  var Leaderboard = Object.assign(LeaderboardRoot, { Skeleton: LeaderboardSkeleton });
433
- function Breadcrumbs({ items, maxItems = 4, className }) {
493
+ function Breadcrumb({ items, maxItems = 4, className }) {
434
494
  const collapsed = items.length > maxItems ? [items[0], { label: "\u2026" }, ...items.slice(items.length - (maxItems - 2))] : items;
435
495
  return /* @__PURE__ */ jsx("nav", { className: cn("cb-crumbs", className), "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "cb-crumbs__list", children: collapsed.map((crumb, index) => {
436
496
  const isLast = index === collapsed.length - 1;
@@ -440,22 +500,22 @@ function Breadcrumbs({ items, maxItems = 4, className }) {
440
500
  ] }, `${String(crumb.label)}-${index}`);
441
501
  }) }) });
442
502
  }
443
- function Stepper({ steps, current, orientation = "horizontal", className }) {
444
- return /* @__PURE__ */ jsx("ol", { className: cn("cb-stepper", `cb-stepper--${orientation}`, className), children: steps.map((step, index) => {
503
+ function Steps({ steps, current, orientation = "horizontal", className }) {
504
+ return /* @__PURE__ */ jsx("ol", { className: cn("cb-steps", `cb-steps--${orientation}`, className), children: steps.map((step, index) => {
445
505
  const state = index < current ? "done" : index === current ? "current" : "todo";
446
- return /* @__PURE__ */ jsxs("li", { className: "cb-stepper__step", "data-state": state, children: [
447
- /* @__PURE__ */ jsx("span", { className: "cb-stepper__marker", "aria-hidden": "true", children: state === "done" ? /* @__PURE__ */ jsx(Check, { size: 13, strokeWidth: 3 }) : index + 1 }),
448
- /* @__PURE__ */ jsxs("span", { className: "cb-stepper__text", children: [
449
- /* @__PURE__ */ jsxs("span", { className: "cb-stepper__label", children: [
506
+ return /* @__PURE__ */ jsxs("li", { className: "cb-steps__step", "data-state": state, children: [
507
+ /* @__PURE__ */ jsx("span", { className: "cb-steps__marker", "aria-hidden": "true", children: state === "done" ? /* @__PURE__ */ jsx(Check, { size: 13, strokeWidth: 3 }) : index + 1 }),
508
+ /* @__PURE__ */ jsxs("span", { className: "cb-steps__text", children: [
509
+ /* @__PURE__ */ jsxs("span", { className: "cb-steps__label", children: [
450
510
  step.label,
451
511
  /* @__PURE__ */ jsx("span", { className: "cb-visually-hidden", children: state === "done" ? " (completed)" : state === "current" ? " (current step)" : "" })
452
512
  ] }),
453
- step.description ? /* @__PURE__ */ jsx("span", { className: "cb-stepper__description", children: step.description }) : null
513
+ step.description ? /* @__PURE__ */ jsx("span", { className: "cb-steps__description", children: step.description }) : null
454
514
  ] })
455
515
  ] }, index);
456
516
  }) });
457
517
  }
458
- function StatCard({ label, value, delta, caption, hue, icon, visual, className }) {
518
+ function Statistic({ label, value, delta, caption, hue, icon, visual, className }) {
459
519
  return /* @__PURE__ */ jsxs(Surface, { variant: "tinted", hue, radius: "lg", padding: "md", className: cn("cb-stat", className), children: [
460
520
  /* @__PURE__ */ jsxs("div", { className: "cb-stat__head", children: [
461
521
  /* @__PURE__ */ jsx(Text, { size: "sm", tone: "muted", weight: "medium", children: label }),
@@ -483,6 +543,6 @@ function StatCardSkeleton({ withVisual = false, className }) {
483
543
  ] })
484
544
  ] });
485
545
  }
486
- StatCard.Skeleton = StatCardSkeleton;
546
+ Statistic.Skeleton = StatCardSkeleton;
487
547
 
488
- export { Avatar, AvatarGroup, Badge, BarMini, Breadcrumbs, Container, Donut, EmptyState, Grid, Heading, Leaderboard, ProgressBar, ProgressRing, Skeleton, Sparkline, Spinner, Stack, StatCard, Stepper, Surface, Text, Timeline, cn, donutArcs, hueForName, initialsOf, ringGeometry, sparklineGeometry };
548
+ export { Avatar, AvatarGroup, Badge, BarMini, Breadcrumb, Container, Divider, Donut, Empty, Flex, Grid, Heading, Leaderboard, LinkButton, ProgressBar, ProgressRing, Skeleton, Sparkline, Spin, Statistic, Steps, Surface, Text, Timeline, cn, donutArcs, hueForName, initialsOf, ringGeometry, sparklineGeometry };