@onyxdesign/core 1.0.0 → 1.1.1

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **The interface, in black & white.**
6
6
 
7
- [Website](https://onyx-six-ashy.vercel.app/) · [Documentation](https://onyx-six-ashy.vercel.app/docs/) · [Pricing](https://onyx-six-ashy.vercel.app/#pricing)
7
+ [Website](https://onyxdesign.vercel.app/) · [Documentation](https://onyxdesign.vercel.app/docs/) · [Pricing](https://onyxdesign.vercel.app/#pricing)
8
8
 
9
9
  Onyx is a zero-dependency UI component library for designers who believe restraint is a feature. Two colors, sharp typography, and every detail considered. One CSS file, one small script, light and dark themes built in.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onyxdesign/core",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "A black & white, zero-dependency UI component library for people who care about the details.",
5
5
  "main": "src/onyx.js",
6
6
  "style": "src/onyx.css",
@@ -28,7 +28,7 @@
28
28
  "type": "git",
29
29
  "url": "git+https://github.com/codewithanshuman/Onyx.git"
30
30
  },
31
- "homepage": "https://onyx-six-ashy.vercel.app/",
31
+ "homepage": "https://onyxdesign.vercel.app/",
32
32
  "bugs": {
33
33
  "url": "https://github.com/codewithanshuman/Onyx/issues"
34
34
  },
package/src/onyx.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Onyx v1.0.0 — a black & white component library for the web
2
+ * Onyx v1.1.1 — a black & white component library for the web
3
3
  * Core: MIT License. Onyx Pro components require a commercial license.
4
4
  */
5
5
 
@@ -19,9 +19,10 @@
19
19
  --ox-surface: #ffffff;
20
20
  --ox-border: #e8e8e8;
21
21
  --ox-border-strong: #d4d4d4;
22
+ --ox-control-border: #8f8f8f;
22
23
  --ox-fg: #0a0a0a;
23
- --ox-fg-muted: #6b6b6b;
24
- --ox-fg-subtle: #a3a3a3;
24
+ --ox-fg-muted: #5c5c5c;
25
+ --ox-fg-subtle: #737373;
25
26
  --ox-inverse: #ffffff;
26
27
  --ox-ring: rgba(10, 10, 10, 0.12);
27
28
  --ox-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
@@ -38,9 +39,10 @@
38
39
  --ox-surface: #0a0a0a;
39
40
  --ox-border: #222222;
40
41
  --ox-border-strong: #383838;
42
+ --ox-control-border: #5e5e5e;
41
43
  --ox-fg: #fafafa;
42
- --ox-fg-muted: #a1a1a1;
43
- --ox-fg-subtle: #6b6b6b;
44
+ --ox-fg-muted: #a8a8a8;
45
+ --ox-fg-subtle: #8a8a8a;
44
46
  --ox-inverse: #000000;
45
47
  --ox-ring: rgba(255, 255, 255, 0.18);
46
48
  --ox-shadow-sm: 0 0 0 1px rgba(255, 255, 255, 0.02);
@@ -61,9 +63,10 @@
61
63
  --ox-surface: #0a0a0a;
62
64
  --ox-border: #222222;
63
65
  --ox-border-strong: #383838;
66
+ --ox-control-border: #5e5e5e;
64
67
  --ox-fg: #fafafa;
65
- --ox-fg-muted: #a1a1a1;
66
- --ox-fg-subtle: #6b6b6b;
68
+ --ox-fg-muted: #a8a8a8;
69
+ --ox-fg-subtle: #8a8a8a;
67
70
  --ox-inverse: #000000;
68
71
  --ox-ring: rgba(255, 255, 255, 0.18);
69
72
  --ox-shadow-sm: 0 0 0 1px rgba(255, 255, 255, 0.02);
@@ -78,9 +81,10 @@
78
81
  --ox-surface: #ffffff;
79
82
  --ox-border: #e8e8e8;
80
83
  --ox-border-strong: #d4d4d4;
84
+ --ox-control-border: #8f8f8f;
81
85
  --ox-fg: #0a0a0a;
82
- --ox-fg-muted: #6b6b6b;
83
- --ox-fg-subtle: #a3a3a3;
86
+ --ox-fg-muted: #5c5c5c;
87
+ --ox-fg-subtle: #737373;
84
88
  --ox-inverse: #ffffff;
85
89
  --ox-ring: rgba(10, 10, 10, 0.12);
86
90
  --ox-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
@@ -341,7 +345,7 @@
341
345
  font-size: var(--ox-text-sm);
342
346
  color: var(--ox-fg);
343
347
  background: var(--ox-surface);
344
- border: 1px solid var(--ox-border);
348
+ border: 1px solid var(--ox-control-border);
345
349
  border-radius: var(--ox-radius);
346
350
  transition: border-color var(--ox-duration) var(--ox-ease), box-shadow var(--ox-duration) var(--ox-ease);
347
351
  }
@@ -349,7 +353,7 @@
349
353
  .ox-textarea::placeholder { color: var(--ox-fg-subtle); }
350
354
  .ox-input:hover,
351
355
  .ox-select:hover,
352
- .ox-textarea:hover { border-color: var(--ox-border-strong); }
356
+ .ox-textarea:hover { border-color: var(--ox-fg-muted); }
353
357
  .ox-input:focus,
354
358
  .ox-select:focus,
355
359
  .ox-textarea:focus {
@@ -403,7 +407,7 @@
403
407
  height: 1rem;
404
408
  margin: 0;
405
409
  background: var(--ox-surface);
406
- border: 1px solid var(--ox-border-strong);
410
+ border: 1px solid var(--ox-control-border);
407
411
  cursor: pointer;
408
412
  transition: background var(--ox-duration) var(--ox-ease), border-color var(--ox-duration) var(--ox-ease);
409
413
  }
@@ -441,7 +445,7 @@
441
445
  width: 2.25rem;
442
446
  height: 1.25rem;
443
447
  flex-shrink: 0;
444
- background: var(--ox-border-strong);
448
+ background: var(--ox-control-border);
445
449
  border-radius: var(--ox-radius-full);
446
450
  transition: background var(--ox-duration) var(--ox-ease);
447
451
  }
@@ -818,7 +822,7 @@
818
822
  overflow-x: auto;
819
823
  tab-size: 2;
820
824
  }
821
- .ox-code .c { color: #5f5f5f; }
825
+ .ox-code .c { color: #8a8a8a; } /* 5.6:1 on the code background */
822
826
  .ox-code .t { color: #ffffff; }
823
827
  .ox-code .a { color: #9a9a9a; }
824
828
  .ox-code .s { color: #e5e5e5; font-style: italic; }
@@ -847,8 +851,84 @@
847
851
  transition: color var(--ox-duration) var(--ox-ease), background var(--ox-duration) var(--ox-ease);
848
852
  }
849
853
  .ox-navbar-links a:hover { color: var(--ox-fg); background: var(--ox-bg-muted); }
854
+
855
+ /* Mobile menu: links collapse behind a toggle below 760px
856
+ (or below 1024px with data-collapse="lg" on .ox-navbar) */
857
+ .ox-navbar-toggle { display: none; }
858
+ .ox-navbar-menu { display: none; }
859
+ .ox-navbar-toggle {
860
+ position: relative;
861
+ width: 2.5rem;
862
+ height: 2.5rem;
863
+ padding: 0;
864
+ color: var(--ox-fg);
865
+ background: transparent;
866
+ border: 1px solid var(--ox-border);
867
+ border-radius: var(--ox-radius-full);
868
+ cursor: pointer;
869
+ }
870
+ .ox-navbar-toggle span {
871
+ position: absolute;
872
+ left: 50%;
873
+ top: 50%;
874
+ width: 1rem;
875
+ height: 1.5px;
876
+ margin: -0.75px 0 0 -0.5rem;
877
+ background: currentColor;
878
+ border-radius: 2px;
879
+ transition: transform 260ms var(--ox-ease);
880
+ }
881
+ .ox-navbar-toggle span:first-child { transform: translateY(-3.5px); }
882
+ .ox-navbar-toggle span:last-child { transform: translateY(3.5px); }
883
+ .ox-navbar[data-open="true"] .ox-navbar-toggle span:first-child { transform: rotate(45deg); }
884
+ .ox-navbar[data-open="true"] .ox-navbar-toggle span:last-child { transform: rotate(-45deg); }
885
+ /* Absolute, not fixed: the navbar's backdrop-filter makes it the containing block
886
+ for fixed children, so the menu is sized from the navbar's bottom edge instead */
887
+ .ox-navbar-menu {
888
+ position: absolute;
889
+ top: 100%;
890
+ left: 0;
891
+ right: 0;
892
+ height: calc(100vh - 3.75rem);
893
+ height: calc(100dvh - 3.75rem);
894
+ z-index: 99;
895
+ flex-direction: column;
896
+ gap: var(--ox-space-1);
897
+ padding: var(--ox-space-4) var(--ox-space-4) var(--ox-space-8);
898
+ overflow-y: auto;
899
+ background: var(--ox-bg);
900
+ border-top: 1px solid var(--ox-border);
901
+ opacity: 0;
902
+ transform: translateY(-8px);
903
+ visibility: hidden;
904
+ transition: opacity 220ms var(--ox-ease), transform 260ms var(--ox-ease), visibility 260ms;
905
+ }
906
+ .ox-navbar[data-open="true"] .ox-navbar-menu { opacity: 1; transform: none; visibility: visible; }
907
+ .ox-navbar-menu > a:not(.ox-btn) {
908
+ display: flex;
909
+ align-items: center;
910
+ justify-content: space-between;
911
+ min-height: 3.25rem;
912
+ padding: 0 var(--ox-space-2);
913
+ font-size: var(--ox-text-lg);
914
+ font-weight: 500;
915
+ letter-spacing: -0.01em;
916
+ color: var(--ox-fg);
917
+ text-decoration: none;
918
+ border-bottom: 1px solid var(--ox-border);
919
+ }
920
+ .ox-navbar-menu > a:not(.ox-btn)::after { content: "→"; color: var(--ox-fg-subtle); }
921
+ .ox-navbar-menu .ox-btn { margin-top: var(--ox-space-4); }
922
+ body.ox-nav-locked { overflow: hidden; }
850
923
  @media (max-width: 760px) {
851
924
  .ox-navbar-links { display: none; }
925
+ .ox-navbar-toggle { display: inline-grid; }
926
+ .ox-navbar-menu { display: flex; }
927
+ }
928
+ @media (max-width: 1023px) {
929
+ .ox-navbar[data-collapse="lg"] .ox-navbar-links { display: none; }
930
+ .ox-navbar[data-collapse="lg"] .ox-navbar-toggle { display: inline-grid; }
931
+ .ox-navbar[data-collapse="lg"] .ox-navbar-menu { display: flex; }
852
932
  }
853
933
 
854
934
  /* =========================================================
@@ -867,7 +947,186 @@
867
947
  }
868
948
 
869
949
  /* =========================================================
870
- 19. Keyframes & motion preferences
950
+ 19. Breadcrumb & pagination
951
+ ========================================================= */
952
+ .ox-breadcrumb ol { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ox-space-2); margin: 0; padding: 0; list-style: none; font-size: var(--ox-text-sm); }
953
+ .ox-breadcrumb li { display: inline-flex; align-items: center; gap: var(--ox-space-2); color: var(--ox-fg-muted); }
954
+ .ox-breadcrumb li + li::before { content: "/"; color: var(--ox-fg-subtle); }
955
+ .ox-breadcrumb a { color: inherit; text-decoration: none; border-radius: var(--ox-radius-sm); transition: color var(--ox-duration); }
956
+ .ox-breadcrumb a:hover { color: var(--ox-fg); }
957
+ .ox-breadcrumb [aria-current="page"] { color: var(--ox-fg); font-weight: 500; }
958
+
959
+ .ox-pagination ul { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ox-space-1); margin: 0; padding: 0; list-style: none; }
960
+ .ox-pagination a,
961
+ .ox-pagination span {
962
+ display: inline-grid;
963
+ place-items: center;
964
+ min-width: 2.25rem;
965
+ height: 2.25rem;
966
+ padding: 0 var(--ox-space-2);
967
+ font-size: var(--ox-text-sm);
968
+ font-variant-numeric: tabular-nums;
969
+ color: var(--ox-fg-muted);
970
+ text-decoration: none;
971
+ border: 1px solid transparent;
972
+ border-radius: var(--ox-radius);
973
+ transition: background var(--ox-duration), color var(--ox-duration), border-color var(--ox-duration);
974
+ }
975
+ .ox-pagination a:hover { color: var(--ox-fg); background: var(--ox-bg-muted); }
976
+ .ox-pagination [aria-current="page"] { color: var(--ox-inverse); background: var(--ox-fg); border-color: var(--ox-fg); }
977
+ .ox-pagination [aria-disabled="true"] { opacity: 0.35; pointer-events: none; }
978
+ .ox-pagination .ox-gap { min-width: 1.5rem; }
979
+
980
+ /* =========================================================
981
+ 20. Range slider & number stepper
982
+ ========================================================= */
983
+ .ox-range {
984
+ --_pct: 50%;
985
+ -webkit-appearance: none;
986
+ appearance: none;
987
+ width: 100%;
988
+ height: 1.5rem;
989
+ margin: 0;
990
+ background: transparent;
991
+ cursor: pointer;
992
+ }
993
+ .ox-range::-webkit-slider-runnable-track { height: 4px; border-radius: 99px; background: linear-gradient(to right, var(--ox-fg) var(--_pct), var(--ox-bg-muted) var(--_pct)); }
994
+ .ox-range::-moz-range-track { height: 4px; border-radius: 99px; background: var(--ox-bg-muted); }
995
+ .ox-range::-moz-range-progress { height: 4px; border-radius: 99px; background: var(--ox-fg); }
996
+ .ox-range::-webkit-slider-thumb { -webkit-appearance: none; width: 1.25rem; height: 1.25rem; margin-top: calc(2px - 0.625rem); border-radius: 50%; background: var(--ox-bg); border: 2px solid var(--ox-fg); box-shadow: var(--ox-shadow-sm); transition: transform var(--ox-duration) var(--ox-ease); }
997
+ .ox-range::-moz-range-thumb { width: 1rem; height: 1rem; border-radius: 50%; background: var(--ox-bg); border: 2px solid var(--ox-fg); }
998
+ .ox-range:active::-webkit-slider-thumb { transform: scale(1.15); }
999
+ .ox-range:focus-visible { outline: none; }
1000
+ .ox-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--ox-ring); }
1001
+ .ox-range:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px var(--ox-ring); }
1002
+
1003
+ .ox-stepper { display: inline-flex; align-items: stretch; height: 2.5rem; background: var(--ox-surface); border: 1px solid var(--ox-control-border); border-radius: var(--ox-radius); overflow: hidden; }
1004
+ .ox-stepper:focus-within { border-color: var(--ox-fg); box-shadow: 0 0 0 3px var(--ox-ring); }
1005
+ .ox-stepper button { width: 2.5rem; padding: 0; font-size: var(--ox-text-lg); line-height: 1; color: var(--ox-fg); background: transparent; border: 0; cursor: pointer; transition: background var(--ox-duration); }
1006
+ .ox-stepper button:hover { background: var(--ox-bg-muted); }
1007
+ .ox-stepper button:disabled { opacity: 0.3; cursor: not-allowed; background: transparent; }
1008
+ .ox-stepper input { width: 3.5rem; padding: 0; font: inherit; font-size: var(--ox-text-sm); font-variant-numeric: tabular-nums; text-align: center; color: var(--ox-fg); background: transparent; border: 0; border-inline: 1px solid var(--ox-border); outline: none; -moz-appearance: textfield; }
1009
+ .ox-stepper input::-webkit-outer-spin-button,
1010
+ .ox-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
1011
+
1012
+ /* =========================================================
1013
+ 21. Chips (toggleable filters)
1014
+ ========================================================= */
1015
+ .ox-chips { display: flex; flex-wrap: wrap; gap: var(--ox-space-2); }
1016
+ .ox-chip {
1017
+ display: inline-flex;
1018
+ align-items: center;
1019
+ gap: 0.4rem;
1020
+ height: 2rem;
1021
+ padding: 0 var(--ox-space-3);
1022
+ font: 500 var(--ox-text-sm) var(--ox-font-sans);
1023
+ color: var(--ox-fg-muted);
1024
+ background: var(--ox-surface);
1025
+ border: 1px solid var(--ox-border);
1026
+ border-radius: var(--ox-radius-full);
1027
+ cursor: pointer;
1028
+ transition: color var(--ox-duration), background var(--ox-duration), border-color var(--ox-duration), transform var(--ox-duration) var(--ox-ease);
1029
+ }
1030
+ .ox-chip:hover { color: var(--ox-fg); border-color: var(--ox-border-strong); }
1031
+ .ox-chip:active { transform: scale(0.97); }
1032
+ .ox-chip[aria-pressed="true"] { color: var(--ox-inverse); background: var(--ox-fg); border-color: var(--ox-fg); }
1033
+ .ox-chip[aria-pressed="true"]::before { content: "✓"; font-size: 0.7rem; }
1034
+
1035
+ /* =========================================================
1036
+ 22. Table
1037
+ ========================================================= */
1038
+ .ox-table-wrap { overflow-x: auto; border: 1px solid var(--ox-border); border-radius: var(--ox-radius-lg); background: var(--ox-surface); }
1039
+ .ox-table { width: 100%; border-collapse: collapse; font-size: var(--ox-text-sm); font-variant-numeric: tabular-nums; }
1040
+ .ox-table caption { padding: var(--ox-space-3) var(--ox-space-4); text-align: left; color: var(--ox-fg-muted); caption-side: bottom; font-size: var(--ox-text-xs); }
1041
+ .ox-table th { padding: var(--ox-space-3) var(--ox-space-4); text-align: left; font-size: var(--ox-text-xs); font-weight: 500; color: var(--ox-fg-muted); background: var(--ox-bg-subtle); border-bottom: 1px solid var(--ox-border); white-space: nowrap; }
1042
+ .ox-table td { padding: var(--ox-space-3) var(--ox-space-4); border-bottom: 1px solid var(--ox-border); }
1043
+ .ox-table tbody tr:last-child td { border-bottom: 0; }
1044
+ .ox-table tbody tr { transition: background var(--ox-duration); }
1045
+ .ox-table tbody tr:hover { background: var(--ox-bg-subtle); }
1046
+ .ox-table .num { text-align: right; }
1047
+ .ox-table-striped tbody tr:nth-child(even) { background: var(--ox-bg-subtle); }
1048
+
1049
+ /* =========================================================
1050
+ 23. Empty state & timeline
1051
+ ========================================================= */
1052
+ .ox-empty { display: grid; justify-items: center; gap: var(--ox-space-2); padding: var(--ox-space-12) var(--ox-space-6); text-align: center; border: 1.5px dashed var(--ox-border-strong); border-radius: var(--ox-radius-lg); }
1053
+ .ox-empty-icon { display: grid; place-items: center; width: 3rem; height: 3rem; margin-bottom: var(--ox-space-2); font-size: var(--ox-text-xl); color: var(--ox-fg); background: var(--ox-bg-muted); border-radius: 50%; }
1054
+ .ox-empty-title { margin: 0; font-size: var(--ox-text-base); font-weight: 600; }
1055
+ .ox-empty-text { max-width: 36ch; margin: 0 0 var(--ox-space-3); font-size: var(--ox-text-sm); color: var(--ox-fg-muted); }
1056
+
1057
+ .ox-timeline { margin: 0; padding: 0; list-style: none; }
1058
+ .ox-timeline li { position: relative; padding: 0 0 var(--ox-space-6) var(--ox-space-8); }
1059
+ .ox-timeline li::before { content: ""; position: absolute; left: 0.3rem; top: 0.45rem; width: 0.65rem; height: 0.65rem; border-radius: 50%; background: var(--ox-bg); border: 2px solid var(--ox-fg); }
1060
+ .ox-timeline li::after { content: ""; position: absolute; left: calc(0.3rem + 0.325rem - 0.5px); top: 1.4rem; bottom: 0.2rem; width: 1px; background: var(--ox-border-strong); }
1061
+ .ox-timeline li:last-child { padding-bottom: 0; }
1062
+ .ox-timeline li:last-child::after { display: none; }
1063
+ .ox-timeline li[data-state="done"]::before { background: var(--ox-fg); }
1064
+ .ox-timeline li[data-state="upcoming"]::before { border-color: var(--ox-border-strong); border-style: dashed; }
1065
+ .ox-timeline time { display: block; margin-bottom: 2px; font: 400 var(--ox-text-xs) var(--ox-font-mono); color: var(--ox-fg-subtle); }
1066
+ .ox-timeline b { display: block; font-size: var(--ox-text-sm); font-weight: 500; }
1067
+ .ox-timeline p { margin: 2px 0 0; font-size: var(--ox-text-sm); color: var(--ox-fg-muted); }
1068
+
1069
+ /* =========================================================
1070
+ 24. Banner (dismissible announcement)
1071
+ ========================================================= */
1072
+ .ox-banner {
1073
+ position: relative;
1074
+ display: flex;
1075
+ flex-wrap: wrap;
1076
+ align-items: center;
1077
+ justify-content: center;
1078
+ gap: var(--ox-space-2) var(--ox-space-4);
1079
+ padding: var(--ox-space-2) 3rem;
1080
+ font-size: var(--ox-text-sm);
1081
+ text-align: center;
1082
+ color: var(--ox-inverse);
1083
+ background: var(--ox-fg);
1084
+ }
1085
+ .ox-banner a { color: inherit; font-weight: 500; text-underline-offset: 3px; }
1086
+ .ox-banner-close { position: absolute; right: var(--ox-space-2); top: 50%; display: grid; place-items: center; width: 2rem; height: 2rem; margin-top: -1rem; padding: 0; color: inherit; background: transparent; border: 0; border-radius: var(--ox-radius-sm); opacity: 0.7; cursor: pointer; }
1087
+ .ox-banner-close:hover { opacity: 1; background: color-mix(in srgb, var(--ox-inverse) 15%, transparent); }
1088
+ .ox-banner[hidden] { display: none; }
1089
+
1090
+ /* =========================================================
1091
+ 25. Avatar status
1092
+ ========================================================= */
1093
+ .ox-avatar[data-status] { position: relative; overflow: visible; }
1094
+ .ox-avatar[data-status]::after { content: ""; position: absolute; right: -1px; bottom: -1px; width: 28%; height: 28%; min-width: 8px; min-height: 8px; border-radius: 50%; border: 2px solid var(--ox-surface); }
1095
+ .ox-avatar[data-status="online"]::after { background: var(--ox-fg); }
1096
+ .ox-avatar[data-status="away"]::after { background: var(--ox-bg); box-shadow: inset 0 0 0 2px var(--ox-fg); }
1097
+ .ox-avatar[data-status="offline"]::after { background: var(--ox-border-strong); }
1098
+
1099
+ /* Skip link: hidden until a keyboard user tabs to it, then jumps past the navigation */
1100
+ .ox-skip {
1101
+ position: fixed;
1102
+ left: var(--ox-space-4);
1103
+ top: var(--ox-space-3);
1104
+ z-index: 1200;
1105
+ padding: var(--ox-space-2) var(--ox-space-4);
1106
+ font: 500 var(--ox-text-sm) var(--ox-font-sans);
1107
+ color: var(--ox-inverse);
1108
+ background: var(--ox-fg);
1109
+ border-radius: var(--ox-radius-full);
1110
+ text-decoration: none;
1111
+ transform: translateY(-200%);
1112
+ transition: transform var(--ox-duration) var(--ox-ease);
1113
+ }
1114
+ .ox-skip:focus { transform: none; }
1115
+
1116
+ /* =========================================================
1117
+ 26. Touch screens: comfortable tap targets
1118
+ ========================================================= */
1119
+ @media (pointer: coarse) {
1120
+ .ox-btn-sm, .ox-chip, .ox-pagination a, .ox-pagination span { min-height: 2.5rem; }
1121
+ .ox-btn-sm.ox-btn-icon { width: 2.5rem; }
1122
+ .ox-segmented button { height: 2.25rem; }
1123
+ .ox-tab { padding-block: var(--ox-space-3); }
1124
+ .ox-dropdown-item { min-height: 2.75rem; }
1125
+ .ox-checkbox, .ox-radio, .ox-switch { min-height: 2.25rem; }
1126
+ }
1127
+
1128
+ /* =========================================================
1129
+ 27. Keyframes & motion preferences
871
1130
  ========================================================= */
872
1131
  @keyframes ox-spin { to { transform: rotate(360deg); } }
873
1132
  @keyframes ox-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@@ -884,3 +1143,7 @@
884
1143
  transition-duration: 1ms !important;
885
1144
  }
886
1145
  }
1146
+
1147
+ /* Theme switch: the new theme is revealed by a clip-path circle (see Onyx.theme.set) */
1148
+ ::view-transition-old(root),
1149
+ ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
package/src/onyx.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Onyx v1.0.0 — interactive behaviors
2
+ * Onyx v1.1.1 — interactive behaviors
3
3
  * Zero dependencies. Works via data attributes, or the window.Onyx API.
4
4
  */
5
5
  (function (global) {
@@ -107,11 +107,25 @@
107
107
  return global.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
108
108
  }
109
109
 
110
- function setTheme(theme) {
110
+ function applyTheme(theme) {
111
111
  document.documentElement.setAttribute("data-theme", theme);
112
112
  try { localStorage.setItem(THEME_KEY, theme); } catch (e) { /* storage unavailable */ }
113
113
  }
114
114
 
115
+ // With a point ({ x, y }), the new theme spreads out from it like ink, where the browser supports it
116
+ function setTheme(theme, from) {
117
+ var reduce = global.matchMedia && matchMedia("(prefers-reduced-motion: reduce)").matches;
118
+ if (!from || reduce || !document.startViewTransition) { applyTheme(theme); return; }
119
+ var r = Math.hypot(Math.max(from.x, innerWidth - from.x), Math.max(from.y, innerHeight - from.y));
120
+ var vt = document.startViewTransition(function () { applyTheme(theme); });
121
+ vt.ready.then(function () {
122
+ document.documentElement.animate(
123
+ { clipPath: ["circle(0px at " + from.x + "px " + from.y + "px)", "circle(" + r + "px at " + from.x + "px " + from.y + "px)"] },
124
+ { duration: 520, easing: "cubic-bezier(0.2, 0.8, 0.2, 1)", pseudoElement: "::view-transition-new(root)" }
125
+ );
126
+ }).catch(function () {});
127
+ }
128
+
115
129
  function restoreTheme() {
116
130
  try {
117
131
  var saved = localStorage.getItem(THEME_KEY);
@@ -125,10 +139,101 @@
125
139
  document.querySelectorAll(".ox-navbar").forEach(function (n) { n.setAttribute("data-scrolled", scrolled); });
126
140
  }
127
141
 
142
+ /* ---------- Mobile navigation menu ---------- */
143
+ function setNav(nav, open) {
144
+ if (!nav) return;
145
+ nav.setAttribute("data-open", String(open));
146
+ var toggle = nav.querySelector("[data-ox-nav-toggle]");
147
+ if (toggle) {
148
+ toggle.setAttribute("aria-expanded", String(open));
149
+ toggle.setAttribute("aria-label", open ? "Close menu" : "Open menu");
150
+ }
151
+ document.body.classList.toggle("ox-nav-locked", open);
152
+ }
153
+
154
+ /* ---------- Range slider fill ---------- */
155
+ function syncRange(r) {
156
+ var min = +r.min || 0, max = r.max === "" ? 100 : +r.max;
157
+ r.style.setProperty("--_pct", ((+r.value - min) / (max - min || 1)) * 100 + "%");
158
+ }
159
+
160
+ /* ---------- Number stepper ---------- */
161
+ function step(btn) {
162
+ var box = btn.closest(".ox-stepper");
163
+ var input = box.querySelector("input");
164
+ var by = +btn.getAttribute("data-step") * (+input.step || 1);
165
+ var min = input.min === "" ? -Infinity : +input.min;
166
+ var max = input.max === "" ? Infinity : +input.max;
167
+ var next = Math.min(max, Math.max(min, (+input.value || 0) + by));
168
+ input.value = +next.toFixed(6);
169
+ syncStepper(box);
170
+ input.dispatchEvent(new Event("input", { bubbles: true }));
171
+ input.dispatchEvent(new Event("change", { bubbles: true }));
172
+ }
173
+ function syncStepper(box) {
174
+ var input = box.querySelector("input");
175
+ var v = +input.value;
176
+ box.querySelectorAll("[data-step]").forEach(function (b) {
177
+ var dir = +b.getAttribute("data-step");
178
+ b.disabled = dir < 0 ? input.min !== "" && v <= +input.min : input.max !== "" && v >= +input.max;
179
+ });
180
+ }
181
+
182
+ /* ---------- Chips ---------- */
183
+ function toggleChip(chip) {
184
+ var group = chip.closest(".ox-chips");
185
+ var on = chip.getAttribute("aria-pressed") !== "true";
186
+ if (group && group.hasAttribute("data-single")) {
187
+ group.querySelectorAll(".ox-chip").forEach(function (c) { c.setAttribute("aria-pressed", "false"); });
188
+ on = true;
189
+ }
190
+ chip.setAttribute("aria-pressed", String(on));
191
+ if (group) {
192
+ var values = Array.prototype.filter.call(group.querySelectorAll(".ox-chip"), function (c) { return c.getAttribute("aria-pressed") === "true"; })
193
+ .map(function (c) { return c.value || c.textContent.trim(); });
194
+ group.dispatchEvent(new CustomEvent("ox:change", { bubbles: true, detail: { value: values } }));
195
+ }
196
+ }
197
+
198
+ function initWidgets(root) {
199
+ (root || document).querySelectorAll(".ox-range").forEach(syncRange);
200
+ (root || document).querySelectorAll(".ox-stepper").forEach(syncStepper);
201
+ // Banners dismissed before stay hidden
202
+ (root || document).querySelectorAll(".ox-banner[data-ox-banner]").forEach(function (b) {
203
+ try { if (localStorage.getItem("ox-banner-" + b.getAttribute("data-ox-banner"))) b.hidden = true; } catch (e) { /* storage unavailable */ }
204
+ });
205
+ }
206
+
128
207
  /* ---------- Event delegation ---------- */
208
+ document.addEventListener("input", function (e) {
209
+ if (e.target.classList && e.target.classList.contains("ox-range")) syncRange(e.target);
210
+ var box = e.target.closest && e.target.closest(".ox-stepper");
211
+ if (box) syncStepper(box);
212
+ });
213
+
129
214
  document.addEventListener("click", function (e) {
130
215
  var t = e.target;
131
216
 
217
+ var navToggle = t.closest("[data-ox-nav-toggle]");
218
+ if (navToggle) { var nav = navToggle.closest(".ox-navbar"); setNav(nav, nav.getAttribute("data-open") !== "true"); return; }
219
+ if (t.closest(".ox-navbar-menu a")) setNav(t.closest(".ox-navbar"), false);
220
+
221
+ var stepBtn = t.closest(".ox-stepper [data-step]");
222
+ if (stepBtn) { step(stepBtn); return; }
223
+
224
+ var chip = t.closest(".ox-chip");
225
+ if (chip) { toggleChip(chip); return; }
226
+
227
+ var dismiss = t.closest("[data-ox-dismiss]");
228
+ if (dismiss) {
229
+ var banner = dismiss.closest(".ox-banner");
230
+ if (banner) {
231
+ banner.hidden = true;
232
+ try { if (banner.getAttribute("data-ox-banner")) localStorage.setItem("ox-banner-" + banner.getAttribute("data-ox-banner"), "1"); } catch (err) { /* storage unavailable */ }
233
+ }
234
+ return;
235
+ }
236
+
132
237
  var opener = t.closest("[data-ox-modal-open]");
133
238
  if (opener) { openModal(opener.getAttribute("data-ox-modal-open")); return; }
134
239
 
@@ -160,7 +265,11 @@
160
265
  var toaster = t.closest("[data-ox-toast]");
161
266
  if (toaster) toast(toaster.getAttribute("data-ox-toast"));
162
267
 
163
- if (t.closest("[data-ox-theme-toggle]")) setTheme(currentTheme() === "dark" ? "light" : "dark");
268
+ var themeBtn = t.closest("[data-ox-theme-toggle]");
269
+ if (themeBtn) {
270
+ var r = themeBtn.getBoundingClientRect();
271
+ setTheme(currentTheme() === "dark" ? "light" : "dark", { x: r.left + r.width / 2, y: r.top + r.height / 2 });
272
+ }
164
273
  });
165
274
 
166
275
  document.addEventListener("keydown", function (e) {
@@ -168,6 +277,8 @@
168
277
  if (e.key === "Escape") {
169
278
  if (openModalEl) closeModal(openModalEl);
170
279
  closeAllDropdowns();
280
+ var openNav = document.querySelector('.ox-navbar[data-open="true"]');
281
+ if (openNav) { setNav(openNav, false); var tg = openNav.querySelector("[data-ox-nav-toggle]"); if (tg) tg.focus(); }
171
282
  }
172
283
  if (e.key === "Tab" && openModalEl) trapFocus(openModalEl, e);
173
284
 
@@ -183,14 +294,24 @@
183
294
  });
184
295
 
185
296
  global.addEventListener("scroll", syncNavbars, { passive: true });
186
- document.addEventListener("DOMContentLoaded", syncNavbars);
297
+ // Close the mobile menu if the window grows past the breakpoint while it's open
298
+ global.addEventListener("resize", function () {
299
+ document.querySelectorAll('.ox-navbar[data-open="true"]').forEach(function (n) {
300
+ var toggle = n.querySelector("[data-ox-nav-toggle]");
301
+ if (toggle && getComputedStyle(toggle).display === "none") setNav(n, false);
302
+ });
303
+ });
304
+ document.addEventListener("DOMContentLoaded", function () { syncNavbars(); initWidgets(); });
305
+ if (document.readyState !== "loading") initWidgets();
187
306
  restoreTheme();
188
307
 
189
308
  global.Onyx = {
190
- version: "1.0.0",
309
+ version: "1.1.1",
191
310
  modal: { open: openModal, close: closeModal },
192
311
  toast: toast,
193
312
  copy: copy,
194
- theme: { get: currentTheme, set: setTheme }
313
+ theme: { get: currentTheme, set: setTheme },
314
+ nav: { open: function (n) { setNav(n || document.querySelector(".ox-navbar"), true); }, close: function (n) { setNav(n || document.querySelector(".ox-navbar"), false); } },
315
+ init: initWidgets
195
316
  };
196
317
  })(window);