@nextlyhq/ui 0.0.2-alpha.58 → 0.0.2-alpha.60

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.mjs CHANGED
@@ -291,7 +291,15 @@ var badgeVariants = cva3(
291
291
  success: "bg-success-100 text-success-700 dark:bg-success-900 dark:text-success-100",
292
292
  warning: "bg-warning-100 text-warning-700 dark:bg-warning-900 dark:text-warning-100",
293
293
  destructive: "bg-destructive-100 text-destructive-700 dark:bg-destructive-900 dark:text-destructive-100",
294
- outline: "border border-border! bg-transparent text-foreground dark:text-muted-foreground dark:border-border!"
294
+ /*
295
+ * The border colour is NOT important-marked. The base sets no border
296
+ * colour, so nothing inside this component competes for it — the only
297
+ * thing a mark could win against is the caller's own class. Seven
298
+ * outline badges pass one, including a status pill whose comment gives
299
+ * a 3:1 contrast reason for the colour it asks for, and every one of
300
+ * them was being overridden without any sign that it had been.
301
+ */
302
+ outline: "border border-border bg-transparent text-foreground dark:text-muted-foreground dark:border-border"
295
303
  }
296
304
  },
297
305
  defaultVariants: {
@@ -467,6 +475,13 @@ var COLS = {
467
475
  4: "grid-cols-4",
468
476
  6: "grid-cols-6"
469
477
  };
478
+ var RESPONSIVE_COLS = {
479
+ 1: "grid-cols-1",
480
+ 2: "grid-cols-1 @2xl:grid-cols-2",
481
+ 3: "grid-cols-1 @2xl:grid-cols-3",
482
+ 4: "grid-cols-1 @2xl:grid-cols-4",
483
+ 6: "grid-cols-1 @2xl:grid-cols-6"
484
+ };
470
485
  var Stack = forwardRef7(
471
486
  ({ direction = "col", gap = 4, className, ...props }, ref) => /* @__PURE__ */ jsx10(
472
487
  "div",
@@ -484,14 +499,20 @@ var Stack = forwardRef7(
484
499
  );
485
500
  Stack.displayName = "Stack";
486
501
  var Grid = forwardRef7(
487
- ({ cols = 2, gap = 4, className, ...props }, ref) => /* @__PURE__ */ jsx10(
488
- "div",
489
- {
490
- ref,
491
- className: cn("grid", COLS[cols], GAP[gap], className),
492
- ...props
502
+ ({ cols = 2, gap = 4, responsive = false, className, children, ...rest }, ref) => {
503
+ if (!responsive) {
504
+ return /* @__PURE__ */ jsx10(
505
+ "div",
506
+ {
507
+ ref,
508
+ className: cn("grid", COLS[cols], GAP[gap], className),
509
+ ...rest,
510
+ children
511
+ }
512
+ );
493
513
  }
494
- )
514
+ return /* @__PURE__ */ jsx10("div", { ref, className: cn("@container", className), ...rest, children: /* @__PURE__ */ jsx10("div", { className: cn("grid", RESPONSIVE_COLS[cols], GAP[gap]), children }) });
515
+ }
495
516
  );
496
517
  Grid.displayName = "Grid";
497
518
  var Stat = forwardRef7(
@@ -958,7 +979,20 @@ var tabsListVariants = cva8(
958
979
  // `gap-1` and `p-0` are invariant across the variants, so they belong here —
959
980
  // repeating them in each arm gives any future adjustment two sites to change
960
981
  // and one to forget.
961
- "inline-flex items-center justify-center gap-1 rounded-none p-0 text-muted-foreground",
982
+ //
983
+ // The rail the triggers sit on, and it is load-bearing rather than
984
+ // decoration. Each trigger draws a 2px border on its trailing edge and pulls
985
+ // itself half a step onto this one: with no rail here, that pull put a 2px
986
+ // line onto whatever followed the strip in the document, and above a rounded
987
+ // panel that is the corner curve — a straight bar crossing an 8px arc. The
988
+ // rail also gives an inactive tab somewhere to be inactive, which is what
989
+ // makes the active one read as selected rather than as the only tab.
990
+ //
991
+ // Which edge is the trailing one depends on orientation, so the rail follows
992
+ // it. Radix stamps `data-orientation` on the list and on every trigger, so
993
+ // both halves read the same source and cannot disagree about the axis. A
994
+ // fixed bottom rail draws a horizontal line beneath a vertical list.
995
+ "inline-flex items-center justify-center gap-1 rounded-none border-b border-border p-0 text-muted-foreground data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch data-[orientation=vertical]:border-b-0 data-[orientation=vertical]:border-r",
962
996
  {
963
997
  variants: { variant: TABS_LIST_VARIANTS },
964
998
  defaultVariants: { variant: "default" }
@@ -979,9 +1013,22 @@ var TABS_TRIGGER_SIZES = {
979
1013
  sm: "text-xs"
980
1014
  };
981
1015
  var tabsTriggerVariants = cva8(
982
- // Square corners: the active state is a 2px bottom border that has to run the
983
- // full width of the trigger.
984
- "inline-flex items-center justify-center whitespace-nowrap rounded-none bg-transparent px-4 py-2 font-medium cursor-pointer transition-all duration-200 border-b-2 relative -mb-0.5 data-[state=active]:border-b-primary! data-[state=active]:text-primary data-[state=inactive]:border-transparent data-[state=inactive]:text-muted-foreground hover:text-primary hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed",
1016
+ // Square corners: the active state is a 2px border on the trailing edge that
1017
+ // has to run the full length of the trigger and stay flush with its ends.
1018
+ //
1019
+ // The active colour is NOT important-marked. Nothing inside this component
1020
+ // competes for it — the inactive arm is mutually exclusive with the active
1021
+ // one — so the only thing an `!` could win against is a caller's class, which
1022
+ // is precisely what a theme supplies. Marking it made the one line under
1023
+ // every selected tab the one line a theme could not restyle, and the failure
1024
+ // read as "the theme did not apply here" rather than as an opt-out.
1025
+ //
1026
+ // The trailing edge is the bottom when the strip is horizontal and the right
1027
+ // when it is vertical, so the indicator, the half-step pull onto the rail and
1028
+ // the active colour all switch axis together off the same `data-orientation`
1029
+ // the list reads. Switching only some of them puts the selection affordance
1030
+ // on one axis and the line it sits on the other.
1031
+ "inline-flex items-center justify-center whitespace-nowrap rounded-none bg-transparent px-4 py-2 font-medium cursor-pointer transition-all duration-200 border-b-2 relative -mb-0.5 data-[state=active]:border-b-primary data-[state=active]:text-primary data-[state=inactive]:border-transparent data-[state=inactive]:text-muted-foreground hover:text-primary hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed data-[orientation=vertical]:border-b-0 data-[orientation=vertical]:mb-0 data-[orientation=vertical]:border-r-2 data-[orientation=vertical]:-mr-0.5 data-[orientation=vertical]:data-[state=active]:border-r-primary",
985
1032
  {
986
1033
  variants: { size: TABS_TRIGGER_SIZES },
987
1034
  defaultVariants: { size: "default" }
@@ -1601,7 +1648,9 @@ import * as DialogPrimitive2 from "@radix-ui/react-dialog";
1601
1648
  import { cva as cva11 } from "class-variance-authority";
1602
1649
  import * as React7 from "react";
1603
1650
  import { jsx as jsx26, jsxs as jsxs8 } from "react/jsx-runtime";
1604
- var Sheet = DialogPrimitive2.Root;
1651
+ var SheetModalContext = React7.createContext(true);
1652
+ var Sheet = ({ modal = true, children, ...props }) => /* @__PURE__ */ jsx26(SheetModalContext.Provider, { value: modal, children: /* @__PURE__ */ jsx26(DialogPrimitive2.Root, { modal, ...props, children }) });
1653
+ Sheet.displayName = "Sheet";
1605
1654
  var SheetTrigger = DialogPrimitive2.Trigger;
1606
1655
  var SheetClose = DialogPrimitive2.Close;
1607
1656
  var SheetPortal = DialogPrimitive2.Portal;
@@ -1642,8 +1691,9 @@ var sheetVariants = cva11(
1642
1691
  );
1643
1692
  var SheetContent = React7.forwardRef(({ side = "right", className, children, ...props }, ref) => {
1644
1693
  const portalContainer = usePortalContainer();
1694
+ const modal = React7.useContext(SheetModalContext);
1645
1695
  return /* @__PURE__ */ jsxs8(SheetPortal, { container: portalContainer, children: [
1646
- /* @__PURE__ */ jsx26(SheetOverlay, {}),
1696
+ modal ? /* @__PURE__ */ jsx26(SheetOverlay, {}) : null,
1647
1697
  /* @__PURE__ */ jsx26(
1648
1698
  DialogPrimitive2.Content,
1649
1699
  {
@@ -3883,6 +3933,172 @@ function useActiveShortcuts() {
3883
3933
  manager.activeBindings
3884
3934
  );
3885
3935
  }
3936
+
3937
+ // src/components/field-shell.tsx
3938
+ import { cva as cva13 } from "class-variance-authority";
3939
+ import { cloneElement, Fragment as Fragment3, useEffect as useEffect3, useId as useId2 } from "react";
3940
+ import { jsx as jsx40, jsxs as jsxs18 } from "react/jsx-runtime";
3941
+ var controlWidth = cva13("w-full", {
3942
+ variants: {
3943
+ width: {
3944
+ half: "max-w-[var(--nx-field-half,380px)]",
3945
+ full: "max-w-[var(--nx-field-full,760px)]",
3946
+ fill: ""
3947
+ }
3948
+ },
3949
+ defaultVariants: { width: "half" }
3950
+ });
3951
+ function readControlProps(child) {
3952
+ const props = child.props;
3953
+ return props && typeof props === "object" ? props : {};
3954
+ }
3955
+ function isRenderFunction(children) {
3956
+ return typeof children === "function";
3957
+ }
3958
+ function computeControlWiring(ownProps, requestedId, renderedMessageIds, hasError) {
3959
+ const ownId = typeof ownProps.id === "string" && ownProps.id !== "" ? ownProps.id : void 0;
3960
+ const id = ownId ?? requestedId;
3961
+ const ownDescribedByIds = typeof ownProps["aria-describedby"] === "string" ? ownProps["aria-describedby"].split(" ").filter(Boolean) : [];
3962
+ const describedByIds = Array.from(
3963
+ /* @__PURE__ */ new Set([...ownDescribedByIds, ...renderedMessageIds])
3964
+ );
3965
+ const describedBy = describedByIds.length > 0 ? describedByIds.join(" ") : void 0;
3966
+ const ownInvalid = typeof ownProps["aria-invalid"] === "boolean" ? ownProps["aria-invalid"] : void 0;
3967
+ const invalid = hasError ? true : ownInvalid;
3968
+ return { id, describedBy, invalid };
3969
+ }
3970
+ function FieldShell({
3971
+ label,
3972
+ description,
3973
+ error,
3974
+ width = "half",
3975
+ htmlFor,
3976
+ className,
3977
+ children
3978
+ }) {
3979
+ const generatedControlId = useId2();
3980
+ const messageBaseId = useId2();
3981
+ const descriptionId = `${messageBaseId}-description`;
3982
+ const errorId = `${messageBaseId}-error`;
3983
+ const requestedId = htmlFor ?? generatedControlId;
3984
+ const renderedMessageIds = [
3985
+ description ? descriptionId : null,
3986
+ error ? errorId : null
3987
+ ].filter((id) => id !== null);
3988
+ const wiring = isRenderFunction(children) ? computeControlWiring({}, requestedId, renderedMessageIds, Boolean(error)) : computeControlWiring(
3989
+ readControlProps(children),
3990
+ requestedId,
3991
+ renderedMessageIds,
3992
+ Boolean(error)
3993
+ );
3994
+ const isFragmentChild = !isRenderFunction(children) && children.type === Fragment3;
3995
+ useEffect3(() => {
3996
+ if (!isDevelopmentRuntime()) return;
3997
+ if (isFragmentChild) return;
3998
+ const landed = document.getElementById(wiring.id) !== null;
3999
+ const fieldName = typeof label === "string" ? `"${label}"` : "this field";
4000
+ devWarnOnce(
4001
+ landed,
4002
+ `FieldShell: the id computed for ${fieldName} never appeared on any element in the document, so its label and any description are not connected to the control. This happens when the control is a compound component \u2014 a Radix \`Select\`, \`RadioGroup\`, or similar \u2014 whose root does not forward \`id\`/\`aria-describedby\`/\`aria-invalid\` to a real DOM node. Pass \`children\` as a function and apply those to the control's actual focusable element (its trigger) instead of a single top-level clone.`
4003
+ );
4004
+ }, [wiring.id, label, isFragmentChild]);
4005
+ let control;
4006
+ if (isRenderFunction(children)) {
4007
+ control = children(wiring);
4008
+ } else {
4009
+ devWarnOnce(
4010
+ !isFragmentChild,
4011
+ "FieldShell: `children` must be a single element, not a Fragment. A Fragment forwards no props to the elements inside it, so the id, aria-describedby and aria-invalid this component computes would have nowhere to land \u2014 the label and the control would look wired up and never actually connect. Wrap the intended elements in a real element instead, such as a `<div>`."
4012
+ );
4013
+ const controlOverrides = {
4014
+ id: wiring.id,
4015
+ ...wiring.describedBy !== void 0 ? { "aria-describedby": wiring.describedBy } : {},
4016
+ ...wiring.invalid !== void 0 ? { "aria-invalid": wiring.invalid } : {}
4017
+ };
4018
+ control = isFragmentChild ? children : cloneElement(children, controlOverrides);
4019
+ }
4020
+ return /* @__PURE__ */ jsxs18("div", { className: cn("flex flex-col gap-1.5", className), children: [
4021
+ label ? /* @__PURE__ */ jsx40(
4022
+ "label",
4023
+ {
4024
+ htmlFor: wiring.id,
4025
+ className: "text-sm font-medium text-foreground",
4026
+ children: label
4027
+ }
4028
+ ) : null,
4029
+ /* @__PURE__ */ jsx40("div", { className: controlWidth({ width }), children: control }),
4030
+ description ? /* @__PURE__ */ jsx40("p", { id: descriptionId, className: "text-sm text-muted-foreground", children: description }) : null,
4031
+ error ? /* @__PURE__ */ jsx40("p", { id: errorId, className: "text-sm font-medium text-destructive", children: error }) : null
4032
+ ] });
4033
+ }
4034
+
4035
+ // src/components/form-section.tsx
4036
+ import { useId as useId3 } from "react";
4037
+ import { jsx as jsx41, jsxs as jsxs19 } from "react/jsx-runtime";
4038
+ function FormSection({
4039
+ label,
4040
+ description,
4041
+ className,
4042
+ children
4043
+ }) {
4044
+ const labelId = useId3();
4045
+ return /* @__PURE__ */ jsxs19("section", { "aria-labelledby": labelId, className: cn("space-y-2", className), children: [
4046
+ /* @__PURE__ */ jsxs19("div", { className: "space-y-1", children: [
4047
+ /* @__PURE__ */ jsx41(
4048
+ "p",
4049
+ {
4050
+ id: labelId,
4051
+ className: "text-xs font-semibold tracking-[0.08em] uppercase text-muted-foreground",
4052
+ children: label
4053
+ }
4054
+ ),
4055
+ description ? /* @__PURE__ */ jsx41("p", { className: "text-sm text-muted-foreground", children: description }) : null
4056
+ ] }),
4057
+ /* @__PURE__ */ jsx41(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx41("div", { className: "divide-y divide-foreground/10 px-6", children }) })
4058
+ ] });
4059
+ }
4060
+
4061
+ // src/components/form-layout.tsx
4062
+ import { cva as cva14 } from "class-variance-authority";
4063
+ import { jsx as jsx42, jsxs as jsxs20 } from "react/jsx-runtime";
4064
+ var measure = cva14("mx-auto w-full px-6 py-8", {
4065
+ variants: {
4066
+ width: {
4067
+ form: "max-w-[56rem]",
4068
+ wide: "max-w-[72rem]"
4069
+ }
4070
+ },
4071
+ defaultVariants: { width: "form" }
4072
+ });
4073
+ function FormLayout({
4074
+ width = "form",
4075
+ className,
4076
+ children
4077
+ }) {
4078
+ return /* @__PURE__ */ jsx42("div", { className: cn(measure({ width }), className), children });
4079
+ }
4080
+ function FormActions({ dirty, className, children }) {
4081
+ return /* @__PURE__ */ jsxs20(
4082
+ "div",
4083
+ {
4084
+ className: cn(
4085
+ "sticky bottom-0 mt-8 flex items-center justify-end gap-3",
4086
+ "border-t border-border bg-background/95 py-4 backdrop-blur",
4087
+ className
4088
+ ),
4089
+ children: [
4090
+ dirty ? (
4091
+ // `role="status"` (implying `aria-live="polite"`) so a screen-reader
4092
+ // user is told when the flag flips false to true, not only sighted
4093
+ // users watching the bar. This is the only live region FormActions
4094
+ // renders, one per form.
4095
+ /* @__PURE__ */ jsx42("span", { role: "status", className: "mr-auto text-sm text-muted-foreground", children: "You have unsaved changes" })
4096
+ ) : null,
4097
+ children
4098
+ ]
4099
+ }
4100
+ );
4101
+ }
3886
4102
  export {
3887
4103
  Accordion,
3888
4104
  AccordionContent,
@@ -3967,7 +4183,11 @@ export {
3967
4183
  DropdownMenuSubContent,
3968
4184
  DropdownMenuSubTrigger,
3969
4185
  DropdownMenuTrigger,
4186
+ FieldShell,
4187
+ FormActions,
3970
4188
  FormLabelWithTooltip,
4189
+ FormLayout,
4190
+ FormSection,
3971
4191
  Grid,
3972
4192
  Input,
3973
4193
  Label,