@lessly/ui 2.0.0 → 2.2.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 +36 -23
- package/dist/index.js +57 -51
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -429,27 +429,25 @@ declare const Slider: React$1.ForwardRefExoticComponent<Omit<SliderPrimitive.Sli
|
|
|
429
429
|
/**
|
|
430
430
|
* Two signals on two channels that never touch (#446), and two treatments for two shapes.
|
|
431
431
|
*
|
|
432
|
-
* **Fill means the pointer is here
|
|
433
|
-
*
|
|
432
|
+
* **Fill means the pointer is here — except inside a track, where it means "this one".**
|
|
433
|
+
* `ToggleGroup` picks the treatment from its `type`, the question being "can nothing be selected?".
|
|
434
434
|
*
|
|
435
435
|
* - `frame` (a standalone `Toggle`, and every `type="multiple"` group): each item carries its own
|
|
436
436
|
* 1px hairline and selection steps it up to `border-strong` — 5.17:1 against the page in light and
|
|
437
|
-
* 5.47:1 in dark, and 3.63:1 / 4.31:1 above the resting hairline it replaces.
|
|
438
|
-
*
|
|
439
|
-
* - `track` (every `type="single"` group): the
|
|
440
|
-
*
|
|
441
|
-
* 1.
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
* 1.11:1 against the page in dark and 1.14:1 in light, and `shadow-sm` is the one dark step
|
|
452
|
-
* `@lessly/tokens@0.7.0` leaves without a rim, so in dark it can only darken.
|
|
437
|
+
* 5.47:1 in dark, and 3.63:1 / 4.31:1 above the resting hairline it replaces. Nothing is filled but
|
|
438
|
+
* the hovered item, so a filter row still says which of its filters are applied.
|
|
439
|
+
* - `track` (every `type="single"` group): the selected slot is a `bg-brand-subtle` tint with its
|
|
440
|
+
* label in `text-brand` at 600 against the unselected 500. No edge on the slot and no frame around
|
|
441
|
+
* the set. The tint measures 1.104:1 against the page in light and 1.144:1 in dark; the selected
|
|
442
|
+
* word measures 1.55:1 light and 1.26:1 against the unselected word — 1.19:1 and 1.35:1 with hue
|
|
443
|
+
* removed — and 5.89:1 light / 5.07:1 dark against the tint it sits on. The weight step is the one
|
|
444
|
+
* channel that survives hue removal.
|
|
445
|
+
*
|
|
446
|
+
* The `frame` mark is a **ring**, not a border: a ring is a box-shadow, so it costs no layout and an
|
|
447
|
+
* icon-only toggle stays square at the height its `size` declares. `track` draws no ring and no
|
|
448
|
+
* `shadow-sm`: `shadow-sm` is the one dark step `@lessly/tokens@0.7.0` leaves without a rim, so in
|
|
449
|
+
* dark it can only darken, and its blur composites to 1.11:1 against the page in dark and 1.14:1 in
|
|
450
|
+
* light.
|
|
453
451
|
*
|
|
454
452
|
* Focus is an **outline held 2px off the box** — a flush ring painted over the selection mark, and
|
|
455
453
|
* focused-selected and focused-unselected measured 1.55:1 apart, which is #446 on the keyboard.
|
|
@@ -477,12 +475,18 @@ declare const Toggle: React$1.ForwardRefExoticComponent<Omit<TogglePrimitive.Tog
|
|
|
477
475
|
*
|
|
478
476
|
* `multiple` is a set of independent chips — a filter row, where nothing need be on — so each chip
|
|
479
477
|
* carries its own edge and selection steps that edge up. `single` is one object with a moving part,
|
|
480
|
-
* so it takes the segmented-control treatment (#346)
|
|
481
|
-
* frame with no fill — and the selected slot is the only filled thing in the row.
|
|
478
|
+
* so it takes the segmented-control treatment (#346).
|
|
482
479
|
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
480
|
+
* A `single` set of **glyphs** is the exception, and it is the treatment's own limit rather than a
|
|
481
|
+
* special case: with no word to thicken the mark would be the tint alone, which greyscale erases.
|
|
482
|
+
* That set takes `frame`, the same as a `multiple` one (#755).
|
|
483
|
+
*
|
|
484
|
+
* **The selected slot is a tint and a heavier word, and the track draws nothing else.** The
|
|
485
|
+
* `bg-brand-subtle` fill measures 1.104:1 against the page in light and 1.144:1 in dark; the label
|
|
486
|
+
* goes to `text-brand` at 600 against the unselected 500, which is 1.55:1 light and 1.26:1 dark
|
|
487
|
+
* against the unselected word — 1.19:1 and 1.35:1 with hue removed, so the weight step is the one
|
|
488
|
+
* channel that does not depend on seeing colour. There is no edge on the slot and no frame around
|
|
489
|
+
* the set: down a stacked list a frame put a rounded rectangle around every row.
|
|
486
490
|
*/
|
|
487
491
|
declare const ToggleGroup: React$1.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React$1.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React$1.RefAttributes<HTMLDivElement>, "ref">) & VariantProps<(props?: ({
|
|
488
492
|
treatment?: "track" | "frame" | null | undefined;
|
|
@@ -1389,6 +1393,15 @@ interface ExtensionIconProps extends LucideProps {
|
|
|
1389
1393
|
/**
|
|
1390
1394
|
* Renders the lucide-react icon named by a kebab-case manifest id, falling back
|
|
1391
1395
|
* to the Puzzle glyph (plus a one-time dev warning) for unknown ids.
|
|
1396
|
+
*
|
|
1397
|
+
* A fallback carries `data-icon-fallback="<the unresolved id>"`, in every
|
|
1398
|
+
* environment. That is the contract a consumer gates on — one DOM query in a
|
|
1399
|
+
* snapshot, an E2E assertion, or a grep over built output names both the fact
|
|
1400
|
+
* and the id, without a throw and without a line of production console output:
|
|
1401
|
+
*
|
|
1402
|
+
* ```ts
|
|
1403
|
+
* expect(container.querySelector('[data-icon-fallback]')).toBeNull();
|
|
1404
|
+
* ```
|
|
1392
1405
|
*/
|
|
1393
1406
|
declare const ExtensionIcon: React$1.ForwardRefExoticComponent<Omit<ExtensionIconProps, "ref"> & React$1.RefAttributes<SVGSVGElement>>;
|
|
1394
1407
|
|
package/dist/index.js
CHANGED
|
@@ -158,7 +158,13 @@ var DialogContent = React3.forwardRef(({ className, children, showClose = true,
|
|
|
158
158
|
{
|
|
159
159
|
ref,
|
|
160
160
|
className: cn(
|
|
161
|
-
|
|
161
|
+
// The gap at both screen edges is an inset, not a narrower width: `inset-x-4 w-auto` sets
|
|
162
|
+
// the two edges and `mx-auto` centres whatever `max-w-lg` leaves over. Capping the width
|
|
163
|
+
// instead — a `max-w-[calc(100vw-2rem)]` beside `max-w-lg` — is the same tailwind-merge
|
|
164
|
+
// group, so it evicts the 512px cap, and a caller's own `max-w-2xl` would take the gap
|
|
165
|
+
// with it. The 16 is `AppShell`'s own mobile inset (#337), so this edge lands on the edge
|
|
166
|
+
// of the page behind the overlay.
|
|
167
|
+
"fixed inset-x-4 top-[50%] z-50 mx-auto grid w-auto max-w-lg translate-y-[-50%] gap-4 rounded-lg border border-border-subtle bg-bg-elevated p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
162
168
|
className
|
|
163
169
|
),
|
|
164
170
|
...props,
|
|
@@ -842,7 +848,7 @@ import * as TogglePrimitive from "@radix-ui/react-toggle";
|
|
|
842
848
|
import { cva as cva3 } from "class-variance-authority";
|
|
843
849
|
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
844
850
|
var toggleVariants = cva3(
|
|
845
|
-
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium text-text-secondary transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus disabled:pointer-events-none disabled:opacity-50
|
|
851
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium text-text-secondary transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
846
852
|
{
|
|
847
853
|
variants: {
|
|
848
854
|
/**
|
|
@@ -850,8 +856,8 @@ var toggleVariants = cva3(
|
|
|
850
856
|
* lone toggle is by definition something that may be off.
|
|
851
857
|
*/
|
|
852
858
|
treatment: {
|
|
853
|
-
frame: "ring-1 ring-inset ring-border-subtle hover:bg-bg-secondary hover:text-text-primary data-[state=on]:ring-border-strong",
|
|
854
|
-
track: "data-[state=off]:hover:
|
|
859
|
+
frame: "ring-1 ring-inset ring-border-subtle hover:bg-bg-secondary hover:text-text-primary data-[state=on]:text-text-primary data-[state=on]:ring-border-strong",
|
|
860
|
+
track: "data-[state=off]:hover:text-text-primary data-[state=on]:bg-bg-brand-subtle data-[state=on]:text-text-brand"
|
|
855
861
|
},
|
|
856
862
|
variant: {
|
|
857
863
|
default: "bg-transparent",
|
|
@@ -903,17 +909,18 @@ var SlidingThumb = ({ index, count }) => /* @__PURE__ */ jsx20(
|
|
|
903
909
|
"span",
|
|
904
910
|
{
|
|
905
911
|
"aria-hidden": "true",
|
|
906
|
-
className: "motion-segment-thumb pointer-events-none absolute inset-y-0
|
|
907
|
-
style: { width: `calc(
|
|
912
|
+
className: "motion-segment-thumb pointer-events-none absolute inset-y-0 left-0 rounded-md bg-bg-brand-subtle",
|
|
913
|
+
style: { width: `calc(100% / ${count})`, transform: `translateX(${index * 100}%)` }
|
|
908
914
|
}
|
|
909
915
|
);
|
|
910
916
|
var ToggleGroup = React16.forwardRef(({ className, variant, size, treatment, sliding, children, ...props }, ref) => {
|
|
911
|
-
const
|
|
917
|
+
const slots = React16.Children.toArray(children).filter(React16.isValidElement);
|
|
918
|
+
const wordless = slots.length > 0 && slots.every((slot) => isIconOnly(slot.props.children));
|
|
919
|
+
const resolved = treatment ?? (props.type === "single" && !wordless ? "track" : "frame");
|
|
912
920
|
const [mirror, setMirror] = React16.useState(
|
|
913
921
|
() => typeof props.defaultValue === "string" ? props.defaultValue : void 0
|
|
914
922
|
);
|
|
915
923
|
const selected = typeof props.value === "string" ? props.value : mirror;
|
|
916
|
-
const slots = React16.Children.toArray(children).filter(React16.isValidElement);
|
|
917
924
|
const index = slots.findIndex((slot) => slot.props.value === selected);
|
|
918
925
|
const rootProps = sliding && typeof props.value !== "string" ? {
|
|
919
926
|
...props,
|
|
@@ -929,13 +936,18 @@ var ToggleGroup = React16.forwardRef(({ className, variant, size, treatment, sli
|
|
|
929
936
|
className: cn(
|
|
930
937
|
resolved === "track" ? (
|
|
931
938
|
// `w-fit` because a flex or grid parent blockifies `inline-flex` and then stretches it:
|
|
932
|
-
// measured, the track spanned the full 1400px inside Storybook's column.
|
|
933
|
-
|
|
939
|
+
// measured, the track spanned the full 1400px inside Storybook's column. Nothing is
|
|
940
|
+
// drawn here: no frame, no fill, and so no padding to hold slots off a frame — which is
|
|
941
|
+
// what lets the thumb be exactly `100% / count` wide and travel in multiples of itself.
|
|
942
|
+
"inline-flex w-fit items-center gap-0.5"
|
|
934
943
|
) : "flex items-center justify-center gap-1",
|
|
935
|
-
// A sliding track
|
|
936
|
-
//
|
|
937
|
-
//
|
|
938
|
-
|
|
944
|
+
// A sliding track's slots share its width equally, because the thumb is one slot wide and
|
|
945
|
+
// travels in multiples of itself. The gap goes for the same reason: a gap between slots is
|
|
946
|
+
// width the thumb's own step does not account for. The track sizes to those slots rather
|
|
947
|
+
// than to whatever holds it — given a parent and no width of its own it used to span the
|
|
948
|
+
// whole of it, and with no frame drawn the slots then read as loose words with a tint
|
|
949
|
+
// somewhere in them (#754). A caller wanting full width writes one, as every call site did.
|
|
950
|
+
sliding && "relative flex w-fit gap-0",
|
|
939
951
|
className
|
|
940
952
|
),
|
|
941
953
|
...rootProps,
|
|
@@ -960,12 +972,12 @@ var ToggleGroupItem = React16.forwardRef(({ className, children, variant, size,
|
|
|
960
972
|
// the height its size declares. A slot is the one place this matters most — the items of a
|
|
961
973
|
// group sit side by side, so one that is 8px wider than its neighbours is visible.
|
|
962
974
|
isIconOnly(children) && ICON_ONLY_BOX,
|
|
963
|
-
// Inside a track, the selected slot is also the heavier word: weight is the one channel
|
|
964
|
-
//
|
|
975
|
+
// Inside a track, the selected slot is also the heavier word: weight is the one channel that
|
|
976
|
+
// survives hue removal, and the tint under it is 1.104:1 light / 1.144:1 dark on its own.
|
|
965
977
|
context.treatment === "track" && "data-[state=on]:font-semibold",
|
|
966
|
-
// Under a sliding thumb the
|
|
967
|
-
//
|
|
968
|
-
context.sliding && "relative z-10 flex-1 data-[state=on]:bg-transparent
|
|
978
|
+
// Under a sliding thumb the tint belongs to the thumb, which is the one that travels; a second
|
|
979
|
+
// copy on the slot would sit still while the first one moved. The word stays the slot's own.
|
|
980
|
+
context.sliding && "relative z-10 flex-1 data-[state=on]:bg-transparent"
|
|
969
981
|
);
|
|
970
982
|
if (inert) {
|
|
971
983
|
return /* @__PURE__ */ jsx20("span", { ref, "aria-hidden": "true", className: cn(box, "invisible", className), children });
|
|
@@ -1158,7 +1170,13 @@ var AlertDialogContent = React21.forwardRef(({ className, ...props }, ref) => /*
|
|
|
1158
1170
|
{
|
|
1159
1171
|
ref,
|
|
1160
1172
|
className: cn(
|
|
1161
|
-
|
|
1173
|
+
// The gap at both screen edges is an inset, not a narrower width: `inset-x-4 w-auto` sets
|
|
1174
|
+
// the two edges and `mx-auto` centres whatever `max-w-lg` leaves over. Capping the width
|
|
1175
|
+
// instead — a `max-w-[calc(100vw-2rem)]` beside `max-w-lg` — is the same tailwind-merge
|
|
1176
|
+
// group, so it evicts the 512px cap, and a caller's own `max-w-2xl` would take the gap
|
|
1177
|
+
// with it. The 16 is `AppShell`'s own mobile inset (#337), so this edge lands on the edge
|
|
1178
|
+
// of the page behind the overlay.
|
|
1179
|
+
"fixed inset-x-4 top-[50%] z-50 mx-auto grid w-auto max-w-lg translate-y-[-50%] gap-4 rounded-lg border border-border-subtle bg-bg-elevated p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
1162
1180
|
className
|
|
1163
1181
|
),
|
|
1164
1182
|
...props
|
|
@@ -3426,7 +3444,7 @@ var ExtensionIcon = React51.forwardRef(
|
|
|
3426
3444
|
const Icon = resolveIcon(name);
|
|
3427
3445
|
if (!Icon) {
|
|
3428
3446
|
warnUnknownIconOnce(name);
|
|
3429
|
-
return /* @__PURE__ */ jsx62(Puzzle, { ref, ...props });
|
|
3447
|
+
return /* @__PURE__ */ jsx62(Puzzle, { ref, ...props, "data-icon-fallback": name });
|
|
3430
3448
|
}
|
|
3431
3449
|
return /* @__PURE__ */ jsx62(Icon, { ref, ...props });
|
|
3432
3450
|
}
|
|
@@ -6058,25 +6076,15 @@ function ThresholdControl({
|
|
|
6058
6076
|
onChange
|
|
6059
6077
|
}) {
|
|
6060
6078
|
return /* @__PURE__ */ jsx95(
|
|
6061
|
-
|
|
6079
|
+
ToggleGroup,
|
|
6062
6080
|
{
|
|
6063
|
-
|
|
6081
|
+
type: "single",
|
|
6082
|
+
size: "sm",
|
|
6064
6083
|
"aria-label": `Interrupt me for ${label}`,
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
type: "button",
|
|
6070
|
-
"aria-pressed": value === threshold,
|
|
6071
|
-
onClick: () => onChange(threshold),
|
|
6072
|
-
className: cn(
|
|
6073
|
-
"rounded-[7px] border border-transparent px-2.5 py-1 text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus",
|
|
6074
|
-
value === threshold ? "border-border-selected bg-bg-selected font-semibold text-text-primary" : "text-text-secondary hover:text-text-primary"
|
|
6075
|
-
),
|
|
6076
|
-
children: text
|
|
6077
|
-
},
|
|
6078
|
-
threshold
|
|
6079
|
-
))
|
|
6084
|
+
value,
|
|
6085
|
+
onValueChange: (next) => next && onChange(next),
|
|
6086
|
+
className: "shrink-0",
|
|
6087
|
+
children: THRESHOLDS.map(([threshold, text]) => /* @__PURE__ */ jsx95(ToggleGroupItem, { value: threshold, children: text }, threshold))
|
|
6080
6088
|
}
|
|
6081
6089
|
);
|
|
6082
6090
|
}
|
|
@@ -6138,20 +6146,18 @@ var NotificationSettings = React79.forwardRef(
|
|
|
6138
6146
|
return /* @__PURE__ */ jsxs63("div", { ref, className: cn("space-y-5", className), children: [
|
|
6139
6147
|
products && products.length > 0 && /* @__PURE__ */ jsxs63("div", { children: [
|
|
6140
6148
|
/* @__PURE__ */ jsx95("p", { className: "mb-2 px-0.5 text-xs font-bold uppercase tracking-wider text-text-tertiary", children: "Preferences for" }),
|
|
6141
|
-
/* @__PURE__ */ jsx95(
|
|
6142
|
-
|
|
6149
|
+
/* @__PURE__ */ jsx95(
|
|
6150
|
+
ToggleGroup,
|
|
6143
6151
|
{
|
|
6144
|
-
type: "
|
|
6145
|
-
"
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
),
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
t.id
|
|
6154
|
-
)) })
|
|
6152
|
+
type: "single",
|
|
6153
|
+
size: "sm",
|
|
6154
|
+
"aria-label": "Preferences for",
|
|
6155
|
+
value: tab,
|
|
6156
|
+
onValueChange: (next) => next && setTab(next),
|
|
6157
|
+
className: "flex-wrap",
|
|
6158
|
+
children: [{ id: DEFAULT_KEY, label: "Default" }, ...products].map((t) => /* @__PURE__ */ jsx95(ToggleGroupItem, { value: t.id, children: t.label }, t.id))
|
|
6159
|
+
}
|
|
6160
|
+
)
|
|
6155
6161
|
] }),
|
|
6156
6162
|
isDefault ? /* @__PURE__ */ jsxs63(Fragment11, { children: [
|
|
6157
6163
|
/* @__PURE__ */ jsx95("p", { className: "text-sm leading-relaxed text-text-secondary", children: "Your defaults, applied to every product unless you customize one. Everything addressed to you always lands in the in-app record; that cannot be turned off." }),
|