@nextlyhq/ui 0.0.2-alpha.54 → 0.0.2-alpha.57

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/theme.css CHANGED
@@ -180,8 +180,9 @@
180
180
  --color-destructive-800: color-mix(in srgb, var(--nx-destructive), black 50%);
181
181
  --color-destructive-900: color-mix(in srgb, var(--nx-destructive), black 70%);
182
182
  --color-destructive-950: color-mix(in srgb, var(--nx-destructive), black 85%);
183
- /* Saturated fill for solid buttons; distinct from the text-tuned base so
184
- * white on-color text stays AA in both modes. */
183
+ /* Saturated fill for solid buttons, kept separate from the text-tuned base
184
+ * so each can be solved for its own job. The ink that lands on it is
185
+ * `-foreground`, which is white in light mode and black in dark. */
185
186
  --color-destructive-solid: var(--nx-destructive-solid);
186
187
  --color-destructive-foreground: var(--nx-destructive-foreground);
187
188
 
@@ -417,16 +418,18 @@
417
418
  * rounded-xl / rounded-2xl are NOT tiers of this knob. `tailwind-preset.ts`
418
419
  * exports only lg/md/sm, so a plugin using the preset gets Tailwind's fixed
419
420
  * 0.75rem / 1rem for them, detached from --radius; and the definitions below
420
- * add to the knob rather than subtracting, so at the shipped --radius: 0 they
421
- * resolve to 4px and 8px visibly round inside a square admin. Nothing in the
422
- * repo uses them. Treat them as off-contract and pick from sm/md/lg.
421
+ * ADD to the knob rather than subtracting, so they always sit rounder than
422
+ * the container tier and drift further as the knob grows. Nothing in the repo
423
+ * uses them. Treat them as off-contract and pick from sm/md/lg.
423
424
  *
424
- * What --radius: 0 really does: `lg` is 0, and `sm` and `md` compute to -4px
425
- * and -2px. They render square only because border-radius clamps a negative
426
- * length to 0 at used-value time. The token itself stays negative, so reading
427
- * `--radius-sm` for padding, an inset, or through getComputedStyle yields
428
- * -4px, not 0. Anything that consumes a step as a length rather than as a
429
- * corner must clamp it itself.
425
+ * At the shipped 0.5rem the three tiers are 4px, 6px and 8px. All positive,
426
+ * which matters: `sm` and `md` SUBTRACT from the knob, so a knob under 4px
427
+ * makes them negative. They still render square, because border-radius clamps
428
+ * a negative length to 0 at used-value time but the token keeps its
429
+ * negative value, so reading `--radius-sm` for a padding, an inset, or
430
+ * through getComputedStyle yields -4px rather than 0. Anything consuming a
431
+ * step as a LENGTH rather than as a corner must clamp it itself, and that
432
+ * obligation returns the moment someone dials the knob back down.
430
433
  *
431
434
  * A site that genuinely must stay square keeps `rounded-none` WITH an inline
432
435
  * comment saying why — overlapped-border strips, full-bleed fills inside an
@@ -518,11 +521,34 @@
518
521
  * and it shadows the real weight utility rather than adding to it.
519
522
  * ============================================ */
520
523
  @theme {
521
- --font-sans: var(--font-inter), Inter, sans-serif;
524
+ /* The host app supplies the face through the variable; the family name is the
525
+ * var()'s own FALLBACK ARGUMENT, covering a host that installed the font
526
+ * without `next/font`, and the generic stack after it covers a host that did
527
+ * neither. Naming only the family would resolve to nothing under `next/font`,
528
+ * which self-hosts the face and exposes it ONLY as a variable -- a bare
529
+ * "Geist" there silently falls through to the system sans.
530
+ *
531
+ * The fallback has to sit INSIDE the parentheses. A family listed after the
532
+ * closing `)` is not reached when the variable is undefined: an unresolved
533
+ * `var()` with no fallback makes the whole declaration invalid at
534
+ * computed-value time, which for an inherited property like font-family means
535
+ * the element inherits its parent's face and the entire stack is discarded.
536
+ * The comma list looks like a fallback chain and is not one, so a host that
537
+ * skipped the variable rendered in whatever the page already used rather than
538
+ * in the generic sans this stack names.
539
+ *
540
+ * These are declared in a non-inline `@theme`, so they are emitted into
541
+ * `:root` and substitution happens THERE, not on the consuming element. A
542
+ * host must expose the variable at or above that scope -- `next/font`'s
543
+ * generated class belongs on `<html>`. On `<body>` the variable is invisible
544
+ * to this declaration no matter how it is spelled. */
545
+ --font-sans:
546
+ var(--font-geist, Geist), ui-sans-serif, system-ui, -apple-system,
547
+ "Segoe UI", sans-serif;
522
548
  --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
523
549
  --font-mono:
524
- ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
525
- "Courier New", monospace;
550
+ var(--font-geist-mono, "Geist Mono"), ui-monospace, SFMono-Regular, Menlo,
551
+ Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
526
552
 
527
553
  /* Body text, captions */
528
554
  --font-weight-normal: 400;
@@ -685,22 +711,23 @@
685
711
 
686
712
  :root {
687
713
  /* Base colors - using Slate neutral scale */
688
- --nx-background: oklch(1 0 0);
714
+ --nx-background: oklch(0.99 0 0);
689
715
  /* Pure white for weightless aesthetic */
690
- --nx-page-background: oklch(0.9818 0 0);
716
+ --nx-page-background: oklch(0.97 0 0);
691
717
  /* #f9f9f9 for page content area */
692
- --nx-foreground: oklch(0.2079 0.0399 265.73);
693
- /* slate-900 */
718
+ --nx-foreground: oklch(0 0 0);
694
719
 
695
720
  /* Component colors */
696
721
  --nx-card: oklch(1 0 0);
697
722
  /* pure white for cards to pop on off-white bg */
698
- --nx-card-foreground: oklch(0.2079 0.0399 265.73);
699
- /* slate-900 */
700
- --nx-popover: oklch(1 0 0);
723
+ --nx-card-foreground: oklch(0 0 0);
724
+ --nx-popover: oklch(0.99 0 0);
701
725
  /* white */
702
- --nx-popover-foreground: oklch(0.2079 0.0399 265.73);
703
- /* slate-900 */
726
+ --nx-popover-foreground: oklch(0 0 0);
727
+ /* Table headers, table footers and the pagination bar. A surface, so it lives
728
+ with the other surfaces: declared next to the component that paints it, a
729
+ palette change would move everything around it and leave it behind. */
730
+ --nx-table-header-bg: oklch(1 0 0);
704
731
 
705
732
  /* Brand colors - Blue-Cyan Professional Palette */
706
733
  --nx-primary: oklch(0 0 0);
@@ -709,41 +736,41 @@
709
736
  /* white */
710
737
 
711
738
  /* Secondary colors - using slate-100 for subtle backgrounds */
712
- --nx-secondary: oklch(0.9684 0.0068 247.9);
739
+ --nx-secondary: oklch(0.94 0 0);
713
740
  /* slate-100 */
714
- --nx-secondary-foreground: oklch(0.2079 0.0399 265.73);
715
- /* slate-900 */
741
+ --nx-secondary-foreground: oklch(0 0 0);
716
742
 
717
743
  /* Utility colors */
718
- --nx-muted: oklch(0.9696 0 0);
744
+ --nx-muted: oklch(0.97 0 0);
719
745
  /* Darkened so muted text clears WCAG 4.5:1 on the muted surface (was 4.35:1). */
720
- --nx-muted-foreground: oklch(0.54 0.0407 257.44);
721
- /* slate-500 */
722
- --nx-accent: oklch(0.5547 0.0407 257.44);
723
- /* Slate-500 per user request #64748b */
724
- --nx-accent-foreground: oklch(1 0 0);
725
- /* white */
746
+ --nx-muted-foreground: oklch(0.44 0 0);
747
+ /* A hover and selection SURFACE, not ink: one step off the page, with
748
+ `--nx-accent-foreground` as the only thing meant to be read on it. */
749
+ --nx-accent: oklch(0.94 0 0);
750
+ --nx-accent-foreground: oklch(0 0 0);
726
751
 
727
752
  /* State colors - WCAG 2.2 AA Compliant.
728
753
  * Each status has two roles kept as separate tokens, the industry-standard
729
754
  * split (Radix/Primer): the base token is the readable TEXT color used by
730
755
  * `text-destructive` and derived shades, and `-solid` is the saturated FILL
731
- * used by buttons under white on-color text. One value cannot serve both in
732
- * dark mode (a fill dark enough for white text is too dark to read as text
733
- * on the page), so they diverge there. */
756
+ * used by buttons under the on-color ink. In light mode both roles can be
757
+ * the same value and the ink is white. In dark mode they cannot: a fill
758
+ * light enough to read as text on the page is too light for white text, so
759
+ * success splits the fill and destructive flips its ink to black instead.
760
+ * The on-color token is what varies, not just the fill. */
734
761
  /* Darkened from red-500; renders as #d92c32, ~4.8:1 as text on white. */
735
- --nx-destructive: oklch(0.5778 0.2078 25.33);
736
- --nx-destructive-solid: oklch(0.5778 0.2078 25.33);
762
+ --nx-destructive: oklch(0.5661 0.19 23.03);
763
+ --nx-destructive-solid: oklch(0.5661 0.19 23.03);
737
764
  --nx-destructive-foreground: oklch(1 0 0);
738
765
  /* white */
739
766
  /* Darkened from green-600; renders as #00852c, ~4.8:1 as text on white. */
740
- --nx-success: oklch(0.53 0.17 149.2);
767
+ --nx-success: oklch(0.4981 0.17 149.2);
741
768
  --nx-success-solid: oklch(0.53 0.17 149.2);
742
769
  --nx-success-foreground: oklch(1 0 0);
743
770
  /* white */
744
771
  /* Darkened from amber-500 so it reads as text on white at WCAG 4.5:1 (was
745
772
  * 2.15:1); its light shades still tint badge/alert surfaces. */
746
- --nx-warning: oklch(0.565 0.1646 70.11);
773
+ --nx-warning: oklch(0.529 0.1646 70.11);
747
774
  --nx-warning-foreground: oklch(0.2079 0.0399 265.73);
748
775
  /* slate-900 - REQUIRED for AA contrast (warning-500 on white = 3.4:1, fails AA) */
749
776
 
@@ -763,14 +790,14 @@
763
790
  --nx-code-bg: oklch(0.9761 0.0035 247.86);
764
791
  --nx-code-fg: oklch(0.2079 0.0399 265.73);
765
792
  /* Darkened slightly to clear WCAG 4.5:1 on the code surface (was 4.44:1). */
766
- --nx-code-comment: oklch(0.541 0.0407 257.44);
793
+ --nx-code-comment: oklch(0.5264 0.0407 257.44);
767
794
  --nx-code-keyword: oklch(0.4882 0.2172 303.9);
768
795
  --nx-code-string: oklch(0.4478 0.1189 152.1);
769
796
  --nx-code-number: oklch(0.5106 0.1518 44.2);
770
797
  --nx-code-function: oklch(0.4757 0.1444 254.6);
771
798
  --nx-code-operator: oklch(0.4936 0.1418 8.4);
772
799
  /* Darkened slightly to clear WCAG 4.5:1 on the code surface (was 4.44:1). */
773
- --nx-code-punctuation: oklch(0.541 0.0407 257.44);
800
+ --nx-code-punctuation: oklch(0.5264 0.0407 257.44);
774
801
  --nx-code-variable: oklch(0.4694 0.1173 62.3);
775
802
  --nx-code-tag: oklch(0.5054 0.1905 27.5);
776
803
  --nx-code-deleted: oklch(0.5054 0.1905 27.5);
@@ -799,12 +826,12 @@
799
826
  /* For a scrim over CONTENT rather than the page: an image's hover actions,
800
827
  * where the content beneath still has to read through. */
801
828
  --nx-overlay-soft: oklch(0 0 0 / 0.4);
802
- --nx-border-subtle: oklch(0 0 0 / 0.08);
803
- --nx-border: oklch(0 0 0 / 0.14);
829
+ --nx-border-subtle: color-mix(in srgb, oklch(0.92 0 0), transparent 60%);
830
+ --nx-border: oklch(0.92 0 0);
804
831
  /* Alpha raised so the composited strong border clears WCAG 3:1 / ~4:1 (was 2.11:1). */
805
- --nx-border-strong: oklch(0 0 0 / 0.502);
832
+ --nx-border-strong: oklch(0.58 0 0);
806
833
  /* Darkened so the field border clears WCAG 3:1 on the page (was 2.32:1). */
807
- --nx-input: oklch(0.6454 0.0116 286.11);
834
+ --nx-input: oklch(0.6154 0 0);
808
835
  --nx-ring: var(--nx-primary);
809
836
 
810
837
  /* The base colour of the elevation ramp (--shadow-sm..xl, -inner-subtle,
@@ -815,7 +842,6 @@
815
842
  /* Chart colors for data visualization */
816
843
  --nx-chart-1: var(--nx-primary);
817
844
  --nx-chart-2: oklch(0.7399 0.1277 209.34);
818
- /* cyan-500 */
819
845
  --nx-chart-3: oklch(0.6273 0.17 149.2);
820
846
  /* green-600 */
821
847
  --nx-chart-4: oklch(0.7686 0.1646 70.11);
@@ -824,7 +850,10 @@
824
850
  /* red-500 */
825
851
 
826
852
  /* Design tokens */
827
- --radius: 0px;
853
+ /* The one corner knob. Every tier below derives from it, so this single
854
+ * declaration re-rounds the whole admin and a plugin built against the
855
+ * published preset re-rounds with it. */
856
+ --radius: 0.5rem;
828
857
 
829
858
  /* Control sizing — derived from a single height knob so control density
830
859
  * scales from one place. Steps map to the control size variants:
@@ -840,25 +869,25 @@
840
869
  /* white */
841
870
 
842
871
  /* Sidebar specific colors - using Slate scale */
843
- --nx-sidebar-background: var(--nx-background);
872
+ --nx-sidebar-background: oklch(0.99 0 0);
844
873
  /* flat with the content surface; nav is separated by borders, not fill */
845
- --nx-sidebar-foreground: oklch(0.372 0.0392 257.3);
846
- /* slate-700 */
847
- --nx-sidebar-primary: oklch(0.2079 0.0399 265.73);
848
- /* slate-900 */
849
- --nx-sidebar-primary-foreground: oklch(0.9838 0.0035 247.86);
874
+ /* Resting nav ink sits a step back from the active ink, so an active row
875
+ * reads as emphasised by its INK as well as its fill. The chosen palette
876
+ * used one value for both, which leaves the fill carrying the whole signal
877
+ * -- and the fill is the part a low-contrast surface weakens first. */
878
+ --nx-sidebar-foreground: oklch(0.44 0 0);
879
+ --nx-sidebar-primary: oklch(0 0 0);
880
+ --nx-sidebar-primary-foreground: oklch(1 0 0);
850
881
  /* slate-50 */
851
- --nx-sidebar-accent: oklch(0.9684 0.0068 247.9);
882
+ --nx-sidebar-accent: oklch(0.94 0 0);
852
883
  /* slate-100 */
853
- --nx-sidebar-accent-foreground: oklch(0.2079 0.0399 265.73);
854
- /* slate-900 */
884
+ --nx-sidebar-accent-foreground: oklch(0 0 0);
855
885
  /* Darkened so the sidebar border clears WCAG 3:1 on its surface (was 1.23:1). */
856
- --nx-sidebar-border: oklch(0.6446 0.0126 255.53);
857
- /* slate-200 */
886
+ --nx-sidebar-border: oklch(0.63 0 0);
858
887
  --nx-sidebar-ring: var(--nx-primary);
859
888
 
860
889
  /* Data table specific colors */
861
- --nx-table-row-hover: oklch(0.9848 0 0);
890
+ --nx-table-row-hover: oklch(0.97 0 0);
862
891
  --nx-table-row-selected: var(--nx-primary);
863
892
  --nx-table-border: var(--nx-border);
864
893
  }
@@ -866,61 +895,67 @@
866
895
  /* Dark theme overrides when using class-based theming */
867
896
  .dark {
868
897
  /* Base colors - using Slate dark scale */
869
- --nx-background: oklch(0.1358 0.0163 262.71);
898
+ --nx-background: oklch(0 0 0);
870
899
  /* ultra deep dark background (darker than slate-950) */
871
- --nx-page-background: oklch(0.1358 0.0163 262.71);
900
+ --nx-page-background: oklch(0.23 0 0);
872
901
  /* Matches background in dark mode */
873
- --nx-foreground: oklch(0.9838 0.0035 247.86);
902
+ --nx-foreground: oklch(1 0 0);
874
903
  /* slate-50 */
875
904
 
876
905
  /* Component colors */
877
- --nx-card: oklch(0.1916 0.0228 266.36);
906
+ --nx-card: oklch(0.14 0 0);
878
907
  /* raised one tonal step above --nx-background (222 47% 4%) so panels/cards
879
908
  * read as distinct surfaces in dark mode instead of merging into the page */
880
- --nx-card-foreground: oklch(0.9838 0.0035 247.86);
909
+ --nx-card-foreground: oklch(1 0 0);
881
910
  /* slate-50 */
882
911
  /* Darkened toward the card elevation so borders, inputs, and muted text
883
912
  * placed on a popover clear their WCAG minimums (border was 2.85:1, input
884
913
  * 1.73:1); the surface was too light for its own contents. */
885
- --nx-popover: oklch(0.24 0.0249 257.44);
886
- /* slate-800 */
887
- --nx-popover-foreground: oklch(0.9838 0.0035 247.86);
914
+ --nx-popover: oklch(0.18 0 0);
915
+ --nx-popover-foreground: oklch(1 0 0);
888
916
  /* slate-50 */
917
+ /* A step above the card beneath it, carrying no chroma of its own: a hue here
918
+ would tint the header band independently of every surface around it. */
919
+ --nx-table-header-bg: oklch(0.1573 0 0);
889
920
 
890
921
  /* Brand colors - Blue-Cyan Professional Palette (Dark Mode) */
891
922
  --nx-primary: oklch(1 0 0); /* High contrast White for dark mode */
892
- --nx-primary-foreground: oklch(0.2079 0.0399 265.73);
893
- /* slate-900 */
923
+ --nx-primary-foreground: oklch(0 0 0);
894
924
 
895
925
  /* Secondary colors - using slate-800 for dark backgrounds */
896
- --nx-secondary: oklch(0.28 0.0369 259.97);
926
+ --nx-secondary: oklch(0.25 0 0);
897
927
  /* slate-800 */
898
- --nx-secondary-foreground: oklch(0.9838 0.0035 247.86);
928
+ --nx-secondary-foreground: oklch(1 0 0);
899
929
  /* slate-50 */
900
930
 
901
931
  /* Utility colors */
902
- --nx-muted: oklch(0.28 0.0369 259.97);
903
- /* slate-800 */
904
- --nx-muted-foreground: oklch(0.7107 0.0351 256.79);
905
- /* slate-400 */
906
- --nx-accent: oklch(
907
- 0.5547 0.0407 257.44
908
- ); /* Slate-500 per user request #64748b */
932
+ --nx-muted: oklch(0.23 0 0);
933
+ --nx-muted-foreground: oklch(0.72 0 0);
934
+ /* The hover and selected surface: one step lighter than --nx-muted so a row
935
+ under the pointer separates from a merely quiet one. Neutral, not the blue
936
+ it was once labelled -- a tinted accent reads as a status on a surface that
937
+ carries none. */
938
+ --nx-accent: oklch(0.32 0 0);
909
939
  --nx-accent-foreground: oklch(1 0 0);
910
940
  /* white */
911
941
 
912
942
  /* State colors - WCAG 2.2 AA Compliant (Dark Mode).
913
943
  * The base token is lightened from the fill color so it reads as text on the
914
- * dark page at 4.5:1; `-solid` keeps the darker fill so white on-color text
915
- * on buttons still clears 4.5:1. Warning is already light enough to read on
916
- * dark, so its base is unchanged here. */
944
+ * dark page at 4.5:1. The two statuses then resolve the on-color problem
945
+ * differently, because a fill light enough to read as text on a dark page is
946
+ * too light for white text: destructive keeps ONE value for base and fill
947
+ * and flips its ink to black (6.90:1), while success keeps a darker separate
948
+ * fill and keeps its ink white (4.80:1). Warning is already light enough to
949
+ * read on dark, so its base is unchanged here. */
917
950
  /* Lightened red so it reads as text at WCAG 4.5:1 on the dark page and on the
918
951
  * lighter popover surface (was 4.17:1 on the page, 4.25:1 on a popover). */
919
- --nx-destructive: oklch(0.65 0.2078 25.33);
920
- /* Darker red fill so white on-color button text clears 4.5:1. */
921
- --nx-destructive-solid: oklch(0.5778 0.2078 25.33);
922
- --nx-destructive-foreground: oklch(1 0 0);
923
- /* white */
952
+ --nx-destructive: oklch(0.69 0.2 23.91);
953
+ /* Same value as the base: the button fill needs no darkening here because
954
+ its ink is black rather than white. */
955
+ --nx-destructive-solid: oklch(0.69 0.2 23.91);
956
+ /* Black, not white. The dark-mode fill is light enough that black on-color
957
+ text reads at 6.90:1 where white would fall below the minimum. */
958
+ --nx-destructive-foreground: oklch(0 0 0);
924
959
  /* Lightened green so it reads as text at WCAG 4.5:1 on the dark page and on
925
960
  * the lighter popover surface (was 4.17:1 on the page, 4.29:1 on a popover). */
926
961
  --nx-success: oklch(0.6 0.1921 149.58);
@@ -968,23 +1003,25 @@
968
1003
  --nx-overlay: oklch(0 0 0 / 0.7);
969
1004
  --nx-overlay-strong: oklch(0 0 0 / 0.85);
970
1005
  --nx-overlay-soft: oklch(0 0 0 / 0.5);
971
- --nx-border-subtle: oklch(1 0 0 / 0.08);
972
- --nx-border: oklch(1 0 0 / 0.14);
1006
+ --nx-border-subtle: color-mix(in srgb, oklch(0.26 0 0), transparent 60%);
1007
+ --nx-border: oklch(0.26 0 0);
973
1008
  /* Alpha raised so the composited strong border clears WCAG 3:1 / ~4:1 (was 3.00:1). */
974
- --nx-border-strong: oklch(1 0 0 / 0.418);
1009
+ --nx-border-strong: oklch(0.5444 0 0);
975
1010
  /* Brightened so the field border clears WCAG 3:1 on the lighter popover
976
1011
  * surface as well as the page and card (was 1.73:1 on a popover). */
977
- --nx-input: oklch(0.54 0.0128 285.92);
1012
+ --nx-input: oklch(0.5445 0 0);
978
1013
  --nx-ring: var(--nx-primary);
979
1014
 
980
1015
  /* Black in dark mode too: the ramp reads as depth against the deep surfaces
981
- * rather than as a glow, which a lightened shadow colour would produce. */
1016
+ * rather than as a glow, which a lightened shadow colour would produce.
1017
+ * One --shadow-* ramp serves both modes and inlines a single set of
1018
+ * color-mix opacities, so this token is the only place elevation is
1019
+ * retuned -- overriding it resolves the whole ramp to that hue. */
982
1020
  --nx-shadow-color: oklch(0 0 0);
983
1021
 
984
1022
  /* Chart colors for data visualization (same as light mode) */
985
1023
  --nx-chart-1: var(--nx-primary);
986
1024
  --nx-chart-2: oklch(0.7399 0.1277 209.34);
987
- /* cyan-500 */
988
1025
  --nx-chart-3: oklch(0.7229 0.1921 149.58);
989
1026
  /* green-500 */
990
1027
  --nx-chart-4: oklch(0.7686 0.1646 70.11);
@@ -993,7 +1030,7 @@
993
1030
  /* red-500 */
994
1031
 
995
1032
  /* Sidebar specific colors - dark mode */
996
- --nx-sidebar-background: var(--nx-background);
1033
+ --nx-sidebar-background: oklch(0.18 0 0);
997
1034
  /* flat with the content surface; nav is separated by borders, not fill */
998
1035
  /* Resting nav ink, dimmed off slate-50 so it sits a step below
999
1036
  * --nx-sidebar-accent-foreground the way the light pair does: the lightness
@@ -1001,22 +1038,19 @@
1001
1038
  * slate-900, so an active row reads as emphasised by its ink and not only by
1002
1039
  * its fill. Chroma and hue track the theme's other secondary ink
1003
1040
  * (--nx-muted-foreground) so lightness alone carries the hierarchy. */
1004
- --nx-sidebar-foreground: oklch(0.82 0.0351 256.79);
1005
- --nx-sidebar-primary: oklch(0.9838 0.0035 247.86);
1041
+ --nx-sidebar-foreground: oklch(0.72 0 0);
1042
+ --nx-sidebar-primary: oklch(1 0 0);
1006
1043
  /* slate-50 */
1007
- --nx-sidebar-primary-foreground: oklch(0.2079 0.0399 265.73);
1008
- /* slate-900 */
1009
- --nx-sidebar-accent: oklch(0.28 0.0369 259.97);
1010
- /* slate-800 */
1011
- --nx-sidebar-accent-foreground: oklch(0.9838 0.0035 247.86);
1044
+ --nx-sidebar-primary-foreground: oklch(0 0 0);
1045
+ --nx-sidebar-accent: oklch(0.32 0 0);
1046
+ --nx-sidebar-accent-foreground: oklch(1 0 0);
1012
1047
  /* slate-50 */
1013
1048
  /* Lightened so the sidebar border clears WCAG 3:1 on its surface (was 1.19:1). */
1014
- --nx-sidebar-border: oklch(0.4975 0.0129 257.43);
1049
+ --nx-sidebar-border: oklch(0.519 0 0);
1015
1050
  --nx-sidebar-ring: var(--nx-primary);
1016
1051
 
1017
1052
  /* Data table specific colors - dark mode */
1018
- --nx-table-row-hover: oklch(0.28 0.0369 259.97);
1019
- /* slate-800 */
1053
+ --nx-table-row-hover: oklch(0.23 0 0);
1020
1054
  --nx-table-row-selected: var(--nx-primary);
1021
1055
  --nx-table-border: var(--nx-border);
1022
1056
  /* inherit the retuned white-alpha border (was a fixed faint slate) */
@@ -88,8 +88,8 @@ Do not hardcode hex, `rgb()`, `rgba()`, or named colors. The only literals allow
88
88
 
89
89
  ### Corner radius: pick a tier, never a value
90
90
 
91
- The admin ships square today (`--radius: 0`), but the whole scale derives from that one
92
- knob, so an operator who sets `--radius: 12px` re-rounds every surface at once. Your plugin
91
+ The admin ships rounded today (`--radius: 0.5rem`), and the whole scale derives from that
92
+ one knob, so an operator who sets `--radius: 0` squares every surface at once. Your plugin
93
93
  keeps up with that only if it picks a **tier by element category** rather than writing a
94
94
  length. Hardcoding a corner — `rounded-none` just as much as `rounded-[6px]` or
95
95
  `border-radius: 4px` — pins your UI at that value and opts it out of the knob.
@@ -113,14 +113,16 @@ Four consequences worth internalising:
113
113
  - **`rounded-xl` and `rounded-2xl` are not tiers of this knob.** The preset exports only
114
114
  `lg`, `md` and `sm`, so a plugin built against it gets Tailwind's fixed `0.75rem` /
115
115
  `1rem` for those two, detached from `--radius`. Inside the admin's own build they are
116
- defined, but as `--radius + 4px` and `--radius + 8px`, so at the shipped `--radius: 0`
117
- they resolve to `4px` and `8px` — visibly round inside a square admin. Pick from
118
- `sm` / `md` / `lg`.
119
- - **`--radius: 0` is not "every step is zero".** `--radius-lg` is `0`, but `--radius-sm`
120
- and `--radius-md` compute to `-4px` and `-2px`. They _render_ square only because
121
- `border-radius` clamps a negative length to `0`. The token keeps its negative value, so
122
- reading a step for padding, for an inset, or through `getComputedStyle` gives you `-4px`,
123
- not `0`. Clamp it yourself if you consume a step as a length rather than as a corner.
116
+ defined, but as `--radius + 4px` and `--radius + 8px`, so at the shipped `--radius: 0.5rem`
117
+ they resolve to `12px` and `16px` — rounder than the container tier, and drifting further
118
+ as the knob grows. Pick from `sm` / `md` / `lg`.
119
+ - **A low knob drives the lower steps negative.** At the shipped `0.5rem` the three tiers
120
+ are `4px`, `6px` and `8px`, all positive. But `sm` and `md` SUBTRACT from the knob, so
121
+ anything under `4px` `--radius: 0` most obviously computes them to `-4px` and `-2px`.
122
+ They _render_ square only because `border-radius` clamps a negative length to `0` at
123
+ used-value time. The token keeps its negative value, so reading a step for padding, for an
124
+ inset, or through `getComputedStyle` gives you `-4px`, not `0`. Clamp it yourself if you
125
+ consume a step as a length rather than as a corner.
124
126
  - **A rounded container must handle full-bleed children.** If a child paints a background
125
127
  all the way to the container's edge (a tinted footer, a sticky header, a hover row), give
126
128
  the container `overflow-hidden` or give the child a matching corner with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextlyhq/ui",
3
- "version": "0.0.2-alpha.54",
3
+ "version": "0.0.2-alpha.57",
4
4
  "description": "Nextly UI — Reusable React component library for Nextly plugins and custom admin UIs",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -46,6 +46,16 @@
46
46
  "default": "./dist/utils.cjs"
47
47
  }
48
48
  },
49
+ "./color": {
50
+ "import": {
51
+ "types": "./dist/color.d.ts",
52
+ "default": "./dist/color.mjs"
53
+ },
54
+ "require": {
55
+ "types": "./dist/color.d.cts",
56
+ "default": "./dist/color.cjs"
57
+ }
58
+ },
49
59
  "./theme.css": "./dist/theme.css",
50
60
  "./styles.css": "./dist/styles.css",
51
61
  "./styles.scoped.css": "./dist/styles.scoped.css"
@@ -55,10 +65,11 @@
55
65
  ],
56
66
  "files": [
57
67
  "dist",
58
- "docs"
68
+ "docs",
69
+ "!dist/metafile-*.json"
59
70
  ],
60
71
  "engines": {
61
- "node": ">=20.0.0"
72
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
62
73
  },
63
74
  "peerDependencies": {
64
75
  "lucide-react": ">=0.400.0",
@@ -68,6 +79,7 @@
68
79
  "devDependencies": {
69
80
  "@tailwindcss/cli": "^4.3.2",
70
81
  "@types/culori": "^4.0.1",
82
+ "@types/node": "^20.19.17",
71
83
  "@types/react": "19.2.0",
72
84
  "@types/react-dom": "19.2.0",
73
85
  "@vitejs/plugin-react": "^5.1.0",
@@ -86,9 +98,10 @@
86
98
  "typescript": "^5.9.3",
87
99
  "vite-tsconfig-paths": "^5.1.4",
88
100
  "vitest": "^4.1.0",
89
- "@nextlyhq/admin-css": "0.0.2-alpha.54",
90
- "@nextlyhq/eslint-config": "0.0.2-alpha.54",
91
- "@nextlyhq/tsconfig": "0.0.2-alpha.54"
101
+ "@nextlyhq/admin-css": "0.0.2-alpha.57",
102
+ "@nextlyhq/eslint-config": "0.0.2-alpha.57",
103
+ "@nextlyhq/module-specifiers": "0.0.2-alpha.57",
104
+ "@nextlyhq/tsconfig": "0.0.2-alpha.57"
92
105
  },
93
106
  "dependencies": {
94
107
  "@radix-ui/react-accordion": "^1.2.12",
@@ -104,6 +117,7 @@
104
117
  "@radix-ui/react-radio-group": "^1.3.8",
105
118
  "@radix-ui/react-select": "^2.2.6",
106
119
  "@radix-ui/react-separator": "^1.1.8",
120
+ "@radix-ui/react-slider": "^1.4.7",
107
121
  "@radix-ui/react-slot": "^1.2.4",
108
122
  "@radix-ui/react-switch": "^1.2.6",
109
123
  "@radix-ui/react-tabs": "^1.1.13",
@@ -133,10 +147,10 @@
133
147
  "author": "Nextly <contact@nextlyhq.com> (https://nextlyhq.com)",
134
148
  "scripts": {
135
149
  "build": "pnpm build:js && node scripts/build-css.mjs",
136
- "build:js": "rimraf dist && tsup && tsup --config tsup.server-safe.config.ts && node scripts/check-client-directive.mjs",
150
+ "build:js": "rimraf dist && tsup && tsup --config tsup.server-safe.config.ts && node scripts/check-client-directive.mjs && node scripts/check-server-safe-artifacts.mjs",
137
151
  "build:css": "node scripts/build-css.mjs",
138
152
  "dev": "tsup --watch & tsup --config tsup.server-safe.config.ts --watch & wait",
139
- "check-types": "tsc --noEmit",
153
+ "check-types": "tsc --noEmit && tsc --noEmit -p tsconfig.tests.json",
140
154
  "lint": "eslint . --max-warnings 0",
141
155
  "lint:fix": "eslint . --fix",
142
156
  "test": "vitest run --passWithNoTests",