@lessly/ui 0.6.0 → 0.7.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
@@ -986,9 +986,19 @@ interface ConnectionCardProps extends React$1.HTMLAttributes<HTMLDivElement> {
986
986
  glyph?: React$1.ReactNode;
987
987
  /** Override the default status label (e.g. "Token expired" for `error`). */
988
988
  statusLabel?: string;
989
- /** How many products hold a segment of this connection. */
989
+ /**
990
+ * How many products hold a segment of this connection. `undefined` renders the
991
+ * "Not granted to any product yet" default — pass it ONLY on a surface where
992
+ * that claim is true and knowable (the org roster); on a product surface pass
993
+ * neither `grantedCount` nor a footer `action` to render no footer at all.
994
+ */
990
995
  grantedCount?: number;
991
- /** Footer action — typically a <Button> ("Manage", "Reconnect", "Connect"). */
996
+ /**
997
+ * Footer action — typically a <Button> ("Manage", "Reconnect", "Connect").
998
+ * Flat mode: a disconnected card shows it in the header. Collapsible mode: the
999
+ * header is a disclosure trigger (nesting a button would be invalid), so the
1000
+ * action always renders in the expanded footer instead.
1001
+ */
992
1002
  action?: React$1.ReactNode;
993
1003
  /**
994
1004
  * Blast-radius disclosure. Some providers (GitHub, ClickUp) grant all-or-nothing
@@ -1000,12 +1010,24 @@ interface ConnectionCardProps extends React$1.HTMLAttributes<HTMLDivElement> {
1000
1010
  */
1001
1011
  access?: React$1.ReactNode;
1002
1012
  /**
1003
- * Drawer content rendered below the card body — the org surface's accordion
1004
- * drawer in the integration prototype (the per-product grants roster, inline
1005
- * requests, "+ Grant to a product"). The caller owns expand/collapse: pass
1006
- * children only while open. Omitted = the flat card, unchanged.
1013
+ * Drawer content rendered below the card body — the per-product grants roster,
1014
+ * inline requests, "+ Grant to a product". Flat mode: the caller owns visibility,
1015
+ * pass children only while open (unchanged behavior). Collapsible mode: pass
1016
+ * children unconditionally the card shows them while expanded.
1007
1017
  */
1008
1018
  children?: React$1.ReactNode;
1019
+ /**
1020
+ * Disclosure mode: the header row toggles the card open/closed; the footer and
1021
+ * drawer render only while open. The `access` blast-radius notice stays visible
1022
+ * even while collapsed — it is a safety disclosure, not a detail. Collapsed by
1023
+ * default.
1024
+ */
1025
+ collapsible?: boolean;
1026
+ /** Uncontrolled initial state (collapsible mode). Default: false (collapsed). */
1027
+ defaultOpen?: boolean;
1028
+ /** Controlled open state (collapsible mode); pair with `onOpenChange`. */
1029
+ open?: boolean;
1030
+ onOpenChange?: (open: boolean) => void;
1009
1031
  }
1010
1032
  declare const ConnectionCard: React$1.ForwardRefExoticComponent<ConnectionCardProps & React$1.RefAttributes<HTMLDivElement>>;
1011
1033
 
package/dist/index.js CHANGED
@@ -3366,7 +3366,7 @@ SegmentChip.displayName = "SegmentChip";
3366
3366
 
3367
3367
  // src/components/connection-card.tsx
3368
3368
  import * as React56 from "react";
3369
- import { AlertTriangle } from "lucide-react";
3369
+ import { AlertTriangle, ChevronDown as ChevronDown4 } from "lucide-react";
3370
3370
  import { Fragment as Fragment5, jsx as jsx66, jsxs as jsxs33 } from "react/jsx-runtime";
3371
3371
  var authLabels = { app: "App", oauth: "OAuth", token: "token" };
3372
3372
  var statusMeta = {
@@ -3378,53 +3378,123 @@ function monogram(name) {
3378
3378
  return name.replace(/[^a-zA-Z0-9 ]/g, " ").split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
3379
3379
  }
3380
3380
  var ConnectionCard = React56.forwardRef(
3381
- ({ name, scope, auth, status, glyph, statusLabel, grantedCount, action, access, children, className, ...props }, ref) => {
3381
+ ({
3382
+ name,
3383
+ scope,
3384
+ auth,
3385
+ status,
3386
+ glyph,
3387
+ statusLabel,
3388
+ grantedCount,
3389
+ action,
3390
+ access,
3391
+ children,
3392
+ collapsible = false,
3393
+ defaultOpen,
3394
+ open,
3395
+ onOpenChange,
3396
+ className,
3397
+ ...props
3398
+ }, ref) => {
3382
3399
  const meta = statusMeta[status];
3383
3400
  const connected = status !== "disconnected";
3384
- return /* @__PURE__ */ jsxs33(Card, { ref, className: cn(status === "disconnected" && "border-dashed border-border-default", className), ...props, children: [
3385
- /* @__PURE__ */ jsxs33("div", { className: "flex items-center gap-3 p-4", children: [
3386
- /* @__PURE__ */ jsx66(
3387
- "div",
3388
- {
3389
- "aria-hidden": "true",
3390
- className: "flex size-8 shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-elevated text-sm font-bold text-text-secondary",
3391
- children: glyph ?? monogram(name)
3392
- }
3393
- ),
3394
- /* @__PURE__ */ jsxs33("div", { className: "min-w-0", children: [
3395
- /* @__PURE__ */ jsx66("p", { className: "truncate text-sm font-semibold text-text-primary", children: name }),
3396
- /* @__PURE__ */ jsxs33("div", { className: "mt-0.5 flex flex-wrap gap-1", children: [
3397
- /* @__PURE__ */ jsx66(Badge, { variant: "secondary", children: scope }),
3398
- /* @__PURE__ */ jsx66(Badge, { variant: "secondary", children: authLabels[auth] })
3399
- ] })
3400
- ] }),
3401
- /* @__PURE__ */ jsx66("div", { className: "ml-auto shrink-0", children: connected ? /* @__PURE__ */ jsxs33(Badge, { variant: meta.badge, className: "gap-1.5", children: [
3402
- /* @__PURE__ */ jsx66(StatusDot, { status: meta.dot, size: "sm" }),
3403
- statusLabel ?? meta.label
3404
- ] }) : action })
3405
- ] }),
3406
- access && /* @__PURE__ */ jsxs33(
3407
- "div",
3401
+ const glyphBox = /* @__PURE__ */ jsx66(
3402
+ "div",
3403
+ {
3404
+ "aria-hidden": "true",
3405
+ className: "flex size-8 shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-elevated text-sm font-bold text-text-secondary",
3406
+ children: glyph ?? monogram(name)
3407
+ }
3408
+ );
3409
+ const identity = /* @__PURE__ */ jsxs33("div", { className: "min-w-0", children: [
3410
+ /* @__PURE__ */ jsx66("p", { className: "truncate text-sm font-semibold text-text-primary", children: name }),
3411
+ /* @__PURE__ */ jsxs33("div", { className: "mt-0.5 flex flex-wrap gap-1", children: [
3412
+ /* @__PURE__ */ jsx66(Badge, { variant: "secondary", children: scope }),
3413
+ /* @__PURE__ */ jsx66(Badge, { variant: "secondary", children: authLabels[auth] })
3414
+ ] })
3415
+ ] });
3416
+ const statusBadge = /* @__PURE__ */ jsxs33(Badge, { variant: meta.badge, className: "gap-1.5", children: [
3417
+ /* @__PURE__ */ jsx66(StatusDot, { status: meta.dot, size: "sm" }),
3418
+ statusLabel ?? meta.label
3419
+ ] });
3420
+ const accessNote = access ? /* @__PURE__ */ jsxs33(
3421
+ "div",
3422
+ {
3423
+ role: "note",
3424
+ className: "flex items-start gap-2 border-t border-border-subtle bg-bg-warning-subtle px-4 py-2.5 text-xs font-medium text-text-warning",
3425
+ children: [
3426
+ /* @__PURE__ */ jsx66(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": "true" }),
3427
+ /* @__PURE__ */ jsx66("span", { children: access })
3428
+ ]
3429
+ }
3430
+ ) : null;
3431
+ const footerAction = collapsible ? action : connected && action;
3432
+ const footer = grantedCount !== void 0 || footerAction ? /* @__PURE__ */ jsxs33("div", { className: "flex items-center justify-between border-t border-border-subtle px-4 py-3", children: [
3433
+ /* @__PURE__ */ jsx66("span", { className: "text-sm text-text-secondary", children: grantedCount !== void 0 ? /* @__PURE__ */ jsxs33(Fragment5, { children: [
3434
+ "Granted to ",
3435
+ /* @__PURE__ */ jsx66("span", { className: "font-semibold text-text-primary", children: grantedCount }),
3436
+ " ",
3437
+ grantedCount === 1 ? "product" : "products"
3438
+ ] }) : /* @__PURE__ */ jsx66("span", { className: "text-text-tertiary", children: "Not granted to any product yet" }) }),
3439
+ footerAction
3440
+ ] }) : null;
3441
+ const drawer = children ? /* @__PURE__ */ jsx66("div", { "data-slot": "drawer", className: "border-t border-border-subtle", children }) : null;
3442
+ if (!collapsible) {
3443
+ return /* @__PURE__ */ jsxs33(
3444
+ Card,
3408
3445
  {
3409
- role: "note",
3410
- className: "flex items-start gap-2 border-t border-border-subtle bg-bg-warning-subtle px-4 py-2.5 text-xs font-medium text-text-warning",
3446
+ ref,
3447
+ className: cn(status === "disconnected" && "border-dashed border-border-default", className),
3448
+ ...props,
3411
3449
  children: [
3412
- /* @__PURE__ */ jsx66(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": "true" }),
3413
- /* @__PURE__ */ jsx66("span", { children: access })
3450
+ /* @__PURE__ */ jsxs33("div", { className: "flex items-center gap-3 p-4", children: [
3451
+ glyphBox,
3452
+ identity,
3453
+ /* @__PURE__ */ jsx66("div", { className: "ml-auto shrink-0", children: connected ? statusBadge : action })
3454
+ ] }),
3455
+ accessNote,
3456
+ footer,
3457
+ drawer
3414
3458
  ]
3415
3459
  }
3416
- ),
3417
- (grantedCount !== void 0 || connected && action) && /* @__PURE__ */ jsxs33("div", { className: "flex items-center justify-between border-t border-border-subtle px-4 py-3", children: [
3418
- /* @__PURE__ */ jsx66("span", { className: "text-sm text-text-secondary", children: grantedCount !== void 0 ? /* @__PURE__ */ jsxs33(Fragment5, { children: [
3419
- "Granted to ",
3420
- /* @__PURE__ */ jsx66("span", { className: "font-semibold text-text-primary", children: grantedCount }),
3421
- " ",
3422
- grantedCount === 1 ? "product" : "products"
3423
- ] }) : /* @__PURE__ */ jsx66("span", { className: "text-text-tertiary", children: "Not granted to any product yet" }) }),
3424
- connected && action
3425
- ] }),
3426
- children && /* @__PURE__ */ jsx66("div", { "data-slot": "drawer", className: "border-t border-border-subtle", children })
3427
- ] });
3460
+ );
3461
+ }
3462
+ return /* @__PURE__ */ jsx66(Collapsible, { defaultOpen, open, onOpenChange, children: /* @__PURE__ */ jsxs33(
3463
+ Card,
3464
+ {
3465
+ ref,
3466
+ className: cn(status === "disconnected" && "border-dashed border-border-default", className),
3467
+ ...props,
3468
+ children: [
3469
+ /* @__PURE__ */ jsx66(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsxs33(
3470
+ "button",
3471
+ {
3472
+ type: "button",
3473
+ className: "group flex w-full cursor-pointer items-center gap-3 rounded-[inherit] p-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
3474
+ children: [
3475
+ glyphBox,
3476
+ identity,
3477
+ /* @__PURE__ */ jsxs33("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
3478
+ statusBadge,
3479
+ /* @__PURE__ */ jsx66(
3480
+ ChevronDown4,
3481
+ {
3482
+ "aria-hidden": "true",
3483
+ className: "size-4 shrink-0 text-text-tertiary transition-transform group-data-[state=open]:rotate-180"
3484
+ }
3485
+ )
3486
+ ] })
3487
+ ]
3488
+ }
3489
+ ) }),
3490
+ accessNote,
3491
+ /* @__PURE__ */ jsxs33(CollapsibleContent2, { children: [
3492
+ footer,
3493
+ drawer
3494
+ ] })
3495
+ ]
3496
+ }
3497
+ ) });
3428
3498
  }
3429
3499
  );
3430
3500
  ConnectionCard.displayName = "ConnectionCard";
package/dist/styles.css CHANGED
@@ -11,13 +11,13 @@
11
11
  */
12
12
 
13
13
  :root {
14
- --bg-primary: #0e1015;
14
+ --bg-primary: #191b24;
15
15
  --bg-secondary: #12141b;
16
- --bg-surface: #191b24;
17
- --bg-elevated: #1f222d;
18
- --bg-sunken: #08090c;
16
+ --bg-surface: #1f222d;
17
+ --bg-elevated: #252936;
18
+ --bg-sunken: #0e1015;
19
19
  --bg-brand: #06297d;
20
- --bg-brand-subtle: #0e204c;
20
+ --bg-brand-subtle: #1f2541;
21
21
  --bg-brand-hover: #08349b;
22
22
  --bg-brand-bright: #165ff2;
23
23
  --bg-brand-bright-hover: #3d7af4;
@@ -33,7 +33,7 @@
33
33
  --bg-rose-subtle: #1a090b;
34
34
  --text-primary: #eeeff0;
35
35
  --text-secondary: #a5a9b5;
36
- --text-tertiary: #8a91a7;
36
+ --text-tertiary: #8a93a8;
37
37
  --text-disabled: #60687f;
38
38
  --text-inverse: #0e1015;
39
39
  --text-on-brand: #f6f7f7;
@@ -57,7 +57,7 @@
57
57
  --icon-success: #2eda76;
58
58
  --icon-warning: #efb042;
59
59
  --icon-danger: #f06a6a;
60
- --border-subtle: #30343f;
60
+ --border-subtle: #2a2e3b;
61
61
  --border-default: #60687f;
62
62
  --border-strong: #737c95;
63
63
  --border-brand: #165ff2;
@@ -69,7 +69,7 @@
69
69
  --field-bg-hover: #12141b;
70
70
  --field-bg-focus: #0e1015;
71
71
  --field-bg-valid: #191b24;
72
- --field-border: #30343f;
72
+ --field-border: #2a2e3b;
73
73
  --field-border-hover: #393d4b;
74
74
  --field-border-focus: #165ff2;
75
75
  --field-border-filled: #393d4b;
@@ -271,12 +271,12 @@
271
271
  }
272
272
 
273
273
  .light {
274
- --bg-primary: #eeeff0;
275
- --bg-secondary: #e1e2e4;
276
- --bg-surface: #f6f7f7;
277
- --bg-elevated: #ffffff;
278
- --bg-sunken: #cfd1d7;
279
- --bg-brand-subtle: #eaeef4;
274
+ --bg-primary: #f6f7f7;
275
+ --bg-secondary: #eeeff0;
276
+ --bg-surface: #f2f3f4;
277
+ --bg-elevated: #edeef0;
278
+ --bg-sunken: #e1e2e4;
279
+ --bg-brand-subtle: #e4ecfe;
280
280
  --bg-brand-hover: #05226a;
281
281
  --bg-brand-bright-hover: #0b4cd5;
282
282
  --bg-success-subtle: #ebf3ef;
@@ -291,7 +291,7 @@
291
291
  --text-tertiary: #535a6e;
292
292
  --text-disabled: #737c95;
293
293
  --text-inverse: #f6f7f7;
294
- --text-brand: #0940b8;
294
+ --text-brand: #0b4cd5;
295
295
  --text-success: #13703c;
296
296
  --text-warning: #7a5409;
297
297
  --text-danger: #94120e;
@@ -303,7 +303,7 @@
303
303
  --icon-primary: #252936;
304
304
  --icon-secondary: #535a6e;
305
305
  --icon-disabled: #8a91a7;
306
- --icon-brand: #0940b8;
306
+ --icon-brand: #165ff2;
307
307
  --icon-success: #13703c;
308
308
  --icon-warning: #7a5409;
309
309
  --icon-danger: #94120e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessly/ui",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Lessly design system — shared UI primitives, tokens, and theme",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.3",
@@ -57,7 +57,7 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@lessly/tokens": "^0.1.0",
60
+ "@lessly/tokens": "^0.2.0",
61
61
  "@radix-ui/react-accordion": "^1.2.16",
62
62
  "@radix-ui/react-alert-dialog": "^1.1.19",
63
63
  "@radix-ui/react-aspect-ratio": "^1.1.11",