colibri-ui 0.8.96 → 0.8.98
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/ColorPicker/ColorPicker.d.ts +3 -0
- package/ColorPicker/ColorPicker.js +116 -0
- package/ColorPicker/ColorPicker.module.css +22 -0
- package/ColorPicker/assets/none.png +0 -0
- package/ColorPicker/components/ColorPreview/ColorPreview.module.css +23 -0
- package/ColorPicker/components/ColorPreview/index.d.ts +10 -0
- package/ColorPicker/components/ColorPreview/index.js +29 -0
- package/ColorPicker/components/CustomColorModal/CustomColorModal.module.css +18 -0
- package/ColorPicker/components/CustomColorModal/index.d.ts +15 -0
- package/ColorPicker/components/CustomColorModal/index.js +107 -0
- package/ColorPicker/components/HueSlider/HueSlider.module.css +31 -0
- package/ColorPicker/components/HueSlider/index.d.ts +10 -0
- package/ColorPicker/components/HueSlider/index.js +73 -0
- package/ColorPicker/components/Palette/Palette.module.css +12 -0
- package/ColorPicker/components/Palette/index.d.ts +19 -0
- package/ColorPicker/components/Palette/index.js +99 -0
- package/ColorPicker/components/SaturationPanel/SaturationPanel.module.css +26 -0
- package/ColorPicker/components/SaturationPanel/index.d.ts +13 -0
- package/ColorPicker/components/SaturationPanel/index.js +86 -0
- package/ColorPicker/components/Swatch/Swatch.module.css +36 -0
- package/ColorPicker/components/Swatch/index.d.ts +14 -0
- package/ColorPicker/components/Swatch/index.js +47 -0
- package/ColorPicker/hooks/useEyeDropper.d.ts +8 -0
- package/ColorPicker/hooks/useEyeDropper.js +38 -0
- package/ColorPicker/hooks/useHexDraft.d.ts +19 -0
- package/ColorPicker/hooks/useHexDraft.js +49 -0
- package/ColorPicker/hooks/usePointerDrag.d.ts +20 -0
- package/ColorPicker/hooks/usePointerDrag.js +55 -0
- package/ColorPicker/index.d.ts +3 -0
- package/ColorPicker/index.js +9 -0
- package/ColorPicker/index.props.d.ts +43 -0
- package/ColorPicker/package.json +9 -0
- package/ColorPicker/presets.d.ts +3 -0
- package/ColorPicker/presets.js +92 -0
- package/ColorPicker/utils/color.d.ts +25 -0
- package/ColorPicker/utils/color.js +162 -0
- package/Icons/Pipette.d.ts +3 -0
- package/Icons/Pipette.js +35 -0
- package/Icons/index.d.ts +1 -0
- package/Icons/index.js +2 -0
- package/NavigationMenu/NavigationMenu.d.ts +3 -0
- package/NavigationMenu/NavigationMenu.js +170 -0
- package/NavigationMenu/NavigationMenu.module.css +67 -0
- package/NavigationMenu/components/MoreMenu/MoreMenu.module.css +19 -0
- package/NavigationMenu/components/MoreMenu/index.d.ts +16 -0
- package/NavigationMenu/components/MoreMenu/index.js +155 -0
- package/NavigationMenu/components/NavigationLink/NavigationLink.module.css +59 -0
- package/NavigationMenu/components/NavigationLink/index.d.ts +16 -0
- package/NavigationMenu/components/NavigationLink/index.js +51 -0
- package/NavigationMenu/helpers.d.ts +9 -0
- package/NavigationMenu/helpers.js +20 -0
- package/NavigationMenu/index.d.ts +2 -0
- package/NavigationMenu/index.js +7 -0
- package/NavigationMenu/index.props.d.ts +40 -0
- package/NavigationMenu/package.json +9 -0
- package/NavigationMenu/splitItems.d.ts +30 -0
- package/NavigationMenu/splitItems.js +54 -0
- package/NavigationMenu/useOverflowSplit.d.ts +25 -0
- package/NavigationMenu/useOverflowSplit.js +63 -0
- package/Theme/index.d.ts +8 -0
- package/Theme/themes/buenos_aires.d.ts +4 -0
- package/Theme/themes/buenos_aires.js +4 -0
- package/Theme/themes/jaipur.d.ts +4 -0
- package/Theme/themes/jaipur.js +4 -0
- package/esm/ColorPicker/ColorPicker.d.ts +3 -0
- package/esm/ColorPicker/ColorPicker.js +108 -0
- package/esm/ColorPicker/ColorPicker.module.css +22 -0
- package/esm/ColorPicker/assets/none.png +0 -0
- package/esm/ColorPicker/components/ColorPreview/ColorPreview.module.css +23 -0
- package/esm/ColorPicker/components/ColorPreview/index.d.ts +10 -0
- package/esm/ColorPicker/components/ColorPreview/index.js +21 -0
- package/esm/ColorPicker/components/CustomColorModal/CustomColorModal.module.css +18 -0
- package/esm/ColorPicker/components/CustomColorModal/index.d.ts +15 -0
- package/esm/ColorPicker/components/CustomColorModal/index.js +100 -0
- package/esm/ColorPicker/components/HueSlider/HueSlider.module.css +31 -0
- package/esm/ColorPicker/components/HueSlider/index.d.ts +10 -0
- package/esm/ColorPicker/components/HueSlider/index.js +66 -0
- package/esm/ColorPicker/components/Palette/Palette.module.css +12 -0
- package/esm/ColorPicker/components/Palette/index.d.ts +19 -0
- package/esm/ColorPicker/components/Palette/index.js +92 -0
- package/esm/ColorPicker/components/SaturationPanel/SaturationPanel.module.css +26 -0
- package/esm/ColorPicker/components/SaturationPanel/index.d.ts +13 -0
- package/esm/ColorPicker/components/SaturationPanel/index.js +79 -0
- package/esm/ColorPicker/components/Swatch/Swatch.module.css +36 -0
- package/esm/ColorPicker/components/Swatch/index.d.ts +14 -0
- package/esm/ColorPicker/components/Swatch/index.js +38 -0
- package/esm/ColorPicker/hooks/useEyeDropper.d.ts +8 -0
- package/esm/ColorPicker/hooks/useEyeDropper.js +36 -0
- package/esm/ColorPicker/hooks/useHexDraft.d.ts +19 -0
- package/esm/ColorPicker/hooks/useHexDraft.js +47 -0
- package/esm/ColorPicker/hooks/usePointerDrag.d.ts +20 -0
- package/esm/ColorPicker/hooks/usePointerDrag.js +53 -0
- package/esm/ColorPicker/index.d.ts +3 -0
- package/esm/ColorPicker/index.js +2 -0
- package/esm/ColorPicker/index.props.d.ts +43 -0
- package/esm/ColorPicker/presets.d.ts +3 -0
- package/esm/ColorPicker/presets.js +89 -0
- package/esm/ColorPicker/utils/color.d.ts +25 -0
- package/esm/ColorPicker/utils/color.js +152 -0
- package/esm/Icons/Pipette.d.ts +3 -0
- package/esm/Icons/Pipette.js +29 -0
- package/esm/Icons/index.d.ts +1 -0
- package/esm/Icons/index.js +1 -0
- package/esm/NavigationMenu/NavigationMenu.d.ts +3 -0
- package/esm/NavigationMenu/NavigationMenu.js +162 -0
- package/esm/NavigationMenu/NavigationMenu.module.css +67 -0
- package/esm/NavigationMenu/components/MoreMenu/MoreMenu.module.css +19 -0
- package/esm/NavigationMenu/components/MoreMenu/index.d.ts +16 -0
- package/esm/NavigationMenu/components/MoreMenu/index.js +147 -0
- package/esm/NavigationMenu/components/NavigationLink/NavigationLink.module.css +59 -0
- package/esm/NavigationMenu/components/NavigationLink/index.d.ts +16 -0
- package/esm/NavigationMenu/components/NavigationLink/index.js +41 -0
- package/esm/NavigationMenu/helpers.d.ts +9 -0
- package/esm/NavigationMenu/helpers.js +15 -0
- package/esm/NavigationMenu/index.d.ts +2 -0
- package/esm/NavigationMenu/index.js +1 -0
- package/esm/NavigationMenu/index.props.d.ts +40 -0
- package/esm/NavigationMenu/splitItems.d.ts +30 -0
- package/esm/NavigationMenu/splitItems.js +51 -0
- package/esm/NavigationMenu/useOverflowSplit.d.ts +25 -0
- package/esm/NavigationMenu/useOverflowSplit.js +61 -0
- package/esm/Theme/index.d.ts +8 -0
- package/esm/Theme/themes/buenos_aires.d.ts +4 -0
- package/esm/Theme/themes/buenos_aires.js +4 -0
- package/esm/Theme/themes/jaipur.d.ts +4 -0
- package/esm/Theme/themes/jaipur.js +4 -0
- package/esm/index.d.ts +4 -0
- package/esm/index.js +4 -0
- package/esm/theme_ba_variables.css +4 -0
- package/esm/theme_jaipur_variables.css +4 -0
- package/index.d.ts +4 -0
- package/index.js +10 -0
- package/package.json +1 -1
- package/theme_ba_variables.css +4 -0
- package/theme_jaipur_variables.css +4 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const clamp = (value, min = 0, max = 1) => Math.min(max, Math.max(min, value));
|
|
4
|
+
const round1 = value => Math.round(value * 10) / 10;
|
|
5
|
+
const hsv2rgb = ({
|
|
6
|
+
h,
|
|
7
|
+
s,
|
|
8
|
+
v
|
|
9
|
+
}) => {
|
|
10
|
+
const sat = s / 100;
|
|
11
|
+
const val = v / 100;
|
|
12
|
+
const channel = n => {
|
|
13
|
+
const k = (n + h / 60) % 6;
|
|
14
|
+
return Math.round((val - val * sat * Math.max(0, Math.min(k, 4 - k, 1))) * 255);
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
r: channel(5),
|
|
18
|
+
g: channel(3),
|
|
19
|
+
b: channel(1)
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
// Not rounded, so repeated conversions do not accumulate errors.
|
|
23
|
+
const rgb2hsv = ({
|
|
24
|
+
r,
|
|
25
|
+
g,
|
|
26
|
+
b
|
|
27
|
+
}) => {
|
|
28
|
+
const rn = r / 255;
|
|
29
|
+
const gn = g / 255;
|
|
30
|
+
const bn = b / 255;
|
|
31
|
+
const max = Math.max(rn, gn, bn);
|
|
32
|
+
const min = Math.min(rn, gn, bn);
|
|
33
|
+
const delta = max - min;
|
|
34
|
+
let h = 0;
|
|
35
|
+
if (delta !== 0) {
|
|
36
|
+
if (max === rn) {
|
|
37
|
+
h = (gn - bn) / delta % 6;
|
|
38
|
+
} else if (max === gn) {
|
|
39
|
+
h = (bn - rn) / delta + 2;
|
|
40
|
+
} else {
|
|
41
|
+
h = (rn - gn) / delta + 4;
|
|
42
|
+
}
|
|
43
|
+
h *= 60;
|
|
44
|
+
if (h < 0) h += 360;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
h,
|
|
48
|
+
s: max === 0 ? 0 : delta / max * 100,
|
|
49
|
+
v: max * 100
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/** HSV → `hsl(...)` for inline styles. */
|
|
53
|
+
const hsv2css = ({
|
|
54
|
+
h,
|
|
55
|
+
s,
|
|
56
|
+
v
|
|
57
|
+
}) => {
|
|
58
|
+
const sat = s / 100;
|
|
59
|
+
const val = v / 100;
|
|
60
|
+
const l = val * (1 - sat / 2);
|
|
61
|
+
const sl = l === 0 || l === 1 ? 0 : (val - l) / Math.min(l, 1 - l);
|
|
62
|
+
return `hsl(${round1(h)}, ${round1(sl * 100)}%, ${round1(l * 100)}%)`;
|
|
63
|
+
};
|
|
64
|
+
const HEX_RE = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
65
|
+
/** `#ABC`, `abc`, `#aabbcc` → `#aabbcc`. Invalid input → `null`. */
|
|
66
|
+
const normalizeHex = input => {
|
|
67
|
+
const match = HEX_RE.exec(input.trim());
|
|
68
|
+
if (!match) return null;
|
|
69
|
+
const digits = match[1].length === 3 ? match[1].replace(/./g, c => c + c) : match[1];
|
|
70
|
+
return `#${digits.toLowerCase()}`;
|
|
71
|
+
};
|
|
72
|
+
const hex2rgb = hex => {
|
|
73
|
+
const normalized = normalizeHex(hex);
|
|
74
|
+
if (!normalized) return null;
|
|
75
|
+
const int = parseInt(normalized.slice(1), 16);
|
|
76
|
+
return {
|
|
77
|
+
r: int >> 16 & 255,
|
|
78
|
+
g: int >> 8 & 255,
|
|
79
|
+
b: int & 255
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const rgb2hex = ({
|
|
83
|
+
r,
|
|
84
|
+
g,
|
|
85
|
+
b
|
|
86
|
+
}) => `#${[r, g, b].map(c => Math.round(clamp(c, 0, 255)).toString(16).padStart(2, "0")).join("")}`;
|
|
87
|
+
const hsv2hex = hsv => rgb2hex(hsv2rgb(hsv));
|
|
88
|
+
const hex2hsv = hex => {
|
|
89
|
+
const rgb = hex2rgb(hex);
|
|
90
|
+
return rgb ? rgb2hsv(rgb) : null;
|
|
91
|
+
};
|
|
92
|
+
const RGB_RE = /^rgba?\(\s*(\d{1,3})[\s,]+(\d{1,3})[\s,]+(\d{1,3})/i;
|
|
93
|
+
/** HEX or `rgb()`/`rgba()` → `#rrggbb`. Invalid input → `null`. */
|
|
94
|
+
const toHex = input => {
|
|
95
|
+
const rgbMatch = RGB_RE.exec(input.trim());
|
|
96
|
+
if (rgbMatch) {
|
|
97
|
+
const [r, g, b] = rgbMatch.slice(1).map(Number);
|
|
98
|
+
return rgb2hex({
|
|
99
|
+
r,
|
|
100
|
+
g,
|
|
101
|
+
b
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return normalizeHex(input);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* HEX → HSV that keeps hue and saturation from `prev` where the color has
|
|
108
|
+
* none: grey has no hue, black has neither. Without this, typing `#000000`
|
|
109
|
+
* would jump the hue slider to 0 (red).
|
|
110
|
+
*/
|
|
111
|
+
const toHsvPreservingHue = (hex, prev) => {
|
|
112
|
+
const next = hex2hsv(hex);
|
|
113
|
+
if (!next) return null;
|
|
114
|
+
if (next.v === 0) return {
|
|
115
|
+
h: prev.h,
|
|
116
|
+
s: prev.s,
|
|
117
|
+
v: 0
|
|
118
|
+
};
|
|
119
|
+
if (next.s === 0) return {
|
|
120
|
+
h: prev.h,
|
|
121
|
+
s: 0,
|
|
122
|
+
v: next.v
|
|
123
|
+
};
|
|
124
|
+
return next;
|
|
125
|
+
};
|
|
126
|
+
/** WCAG 2.x relative luminance, 0–1. */
|
|
127
|
+
const relativeLuminance = ({
|
|
128
|
+
r,
|
|
129
|
+
g,
|
|
130
|
+
b
|
|
131
|
+
}) => {
|
|
132
|
+
const [lr, lg, lb] = [r, g, b].map(c => {
|
|
133
|
+
const x = c / 255;
|
|
134
|
+
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
135
|
+
});
|
|
136
|
+
return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
|
|
137
|
+
};
|
|
138
|
+
const WHITE_LUMINANCE = 1;
|
|
139
|
+
const BLACK_LUMINANCE = 0;
|
|
140
|
+
const contrast = (a, b) => (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
|
|
141
|
+
/** Whether light text reads better than dark text on `background` (HEX). */
|
|
142
|
+
const prefersLightText = background => {
|
|
143
|
+
const rgb = hex2rgb(background);
|
|
144
|
+
if (!rgb) return false;
|
|
145
|
+
const luminance = relativeLuminance(rgb);
|
|
146
|
+
return contrast(luminance, WHITE_LUMINANCE) >= contrast(luminance, BLACK_LUMINANCE);
|
|
147
|
+
};
|
|
148
|
+
/** A light color that merges with a light surface and needs an outline. */
|
|
149
|
+
const isLightColor = (hex, threshold = 0.85) => {
|
|
150
|
+
const rgb = hex2rgb(hex);
|
|
151
|
+
return rgb ? relativeLuminance(rgb) >= threshold : false;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
exports.clamp = clamp;
|
|
155
|
+
exports.hex2hsv = hex2hsv;
|
|
156
|
+
exports.hsv2css = hsv2css;
|
|
157
|
+
exports.hsv2hex = hsv2hex;
|
|
158
|
+
exports.isLightColor = isLightColor;
|
|
159
|
+
exports.normalizeHex = normalizeHex;
|
|
160
|
+
exports.prefersLightText = prefersLightText;
|
|
161
|
+
exports.toHex = toHex;
|
|
162
|
+
exports.toHsvPreservingHue = toHsvPreservingHue;
|
package/Icons/Pipette.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tslib = require('tslib');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
9
|
+
|
|
10
|
+
const Pipette = _a => {
|
|
11
|
+
var {
|
|
12
|
+
fill = "currentColor",
|
|
13
|
+
width = 24,
|
|
14
|
+
height = 24,
|
|
15
|
+
ref
|
|
16
|
+
} = _a,
|
|
17
|
+
props = tslib.__rest(_a, ["fill", "width", "height", "ref"]);
|
|
18
|
+
return /*#__PURE__*/React__default.default.createElement("svg", Object.assign({
|
|
19
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
20
|
+
width: width,
|
|
21
|
+
height: height
|
|
22
|
+
}, props, {
|
|
23
|
+
ref: ref,
|
|
24
|
+
viewBox: "0 0 24 24",
|
|
25
|
+
fill: "none"
|
|
26
|
+
}), /*#__PURE__*/React__default.default.createElement("path", {
|
|
27
|
+
stroke: fill,
|
|
28
|
+
strokeWidth: "1.8",
|
|
29
|
+
strokeLinecap: "round",
|
|
30
|
+
strokeLinejoin: "round",
|
|
31
|
+
d: "M14.5 4.5l5 5M17 2.5a2.12 2.12 0 0 1 3 3L17.5 8l1 1-1.5 1.5-5-5L13.5 4l1 1L17 2.5zM12 7l-8 8v4h4l8-8"
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
exports.Pipette = Pipette;
|
package/Icons/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { Help } from "./Help";
|
|
|
14
14
|
export { Info } from "./Info";
|
|
15
15
|
export { Minus } from "./Minus";
|
|
16
16
|
export { More } from "./More";
|
|
17
|
+
export { Pipette } from "./Pipette";
|
|
17
18
|
export { Plus } from "./Plus";
|
|
18
19
|
export { Setting } from "./Setting";
|
|
19
20
|
export { Shield } from "./Shield";
|
package/Icons/index.js
CHANGED
|
@@ -16,6 +16,7 @@ var Help = require('./Help.js');
|
|
|
16
16
|
var Info = require('./Info.js');
|
|
17
17
|
var Minus = require('./Minus.js');
|
|
18
18
|
var More = require('./More.js');
|
|
19
|
+
var Pipette = require('./Pipette.js');
|
|
19
20
|
var Plus = require('./Plus.js');
|
|
20
21
|
var Setting = require('./Setting.js');
|
|
21
22
|
var Shield = require('./Shield.js');
|
|
@@ -40,6 +41,7 @@ exports.Help = Help.Help;
|
|
|
40
41
|
exports.Info = Info.Info;
|
|
41
42
|
exports.Minus = Minus.Minus;
|
|
42
43
|
exports.More = More.More;
|
|
44
|
+
exports.Pipette = Pipette.Pipette;
|
|
43
45
|
exports.Plus = Plus.Plus;
|
|
44
46
|
exports.Setting = Setting.Setting;
|
|
45
47
|
exports.Shield = Shield.Shield;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tslib = require('tslib');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var clsx = require('clsx');
|
|
6
|
+
var index$1 = require('./components/MoreMenu/index.js');
|
|
7
|
+
var index = require('./components/NavigationLink/index.js');
|
|
8
|
+
var helpers = require('./helpers.js');
|
|
9
|
+
var useOverflowSplit = require('./useOverflowSplit.js');
|
|
10
|
+
var styles = require('./NavigationMenu.module.css');
|
|
11
|
+
|
|
12
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
|
|
14
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
15
|
+
var clsx__default = /*#__PURE__*/_interopDefault(clsx);
|
|
16
|
+
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
17
|
+
|
|
18
|
+
const FOCUSABLE = "a[href], button, [tabindex]";
|
|
19
|
+
const getActiveIndex = items => items.findIndex(item => item.active);
|
|
20
|
+
const OverflowMenu = _a => {
|
|
21
|
+
var {
|
|
22
|
+
items,
|
|
23
|
+
pinnedItems = [],
|
|
24
|
+
moreItems = [],
|
|
25
|
+
moreLabel,
|
|
26
|
+
onItemClick,
|
|
27
|
+
renderLink,
|
|
28
|
+
navRef,
|
|
29
|
+
rootClassName
|
|
30
|
+
} = _a,
|
|
31
|
+
props = tslib.__rest(_a, ["items", "pinnedItems", "moreItems", "moreLabel", "onItemClick", "renderLink", "navRef", "rootClassName"]);
|
|
32
|
+
const {
|
|
33
|
+
split,
|
|
34
|
+
containerRef,
|
|
35
|
+
listRef,
|
|
36
|
+
measurerRef,
|
|
37
|
+
moreRef
|
|
38
|
+
} = useOverflowSplit.useOverflowSplit({
|
|
39
|
+
itemCount: items.length,
|
|
40
|
+
pinnedCount: pinnedItems.length,
|
|
41
|
+
activeIndex: getActiveIndex(items),
|
|
42
|
+
forceMore: moreItems.length > 0,
|
|
43
|
+
contentKey: [...items, ...pinnedItems].map(item => item.label).join("\n")
|
|
44
|
+
});
|
|
45
|
+
// After `items` change and before the layout effect remeasures, the old
|
|
46
|
+
// split may point to missing indexes: drop them, new items go to "More".
|
|
47
|
+
const visibleIndexes = split === null || split === void 0 ? void 0 : split.visible.filter(index => index < items.length);
|
|
48
|
+
const visibleItems = visibleIndexes ? visibleIndexes.map(index => items[index]) : items;
|
|
49
|
+
const hiddenItems = visibleIndexes ? [...items.filter((_, index) => !visibleIndexes.includes(index)), ...moreItems] : [];
|
|
50
|
+
const isMoreHidden = hiddenItems.length === 0;
|
|
51
|
+
// "More" disappeared with a focused link inside: focus the same item in the
|
|
52
|
+
// row, or the first row link when it is not there.
|
|
53
|
+
const restoreFocus = item => {
|
|
54
|
+
var _a, _b, _c, _d, _e;
|
|
55
|
+
const rows = Array.from((_b = (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : []);
|
|
56
|
+
const rowItems = [...visibleItems, ...pinnedItems];
|
|
57
|
+
const row = rows[rowItems.findIndex(rowItem => helpers.isSameItem(rowItem, item))];
|
|
58
|
+
(_e = (_c = row === null || row === void 0 ? void 0 : row.querySelector(FOCUSABLE)) !== null && _c !== void 0 ? _c : (_d = listRef.current) === null || _d === void 0 ? void 0 : _d.querySelector(FOCUSABLE)) === null || _e === void 0 ? void 0 : _e.focus();
|
|
59
|
+
};
|
|
60
|
+
const renderItem = (item, index$1) => (/*#__PURE__*/React__default.default.createElement("li", {
|
|
61
|
+
key: helpers.getItemKey(item, index$1),
|
|
62
|
+
className: styles__default.default.list_item
|
|
63
|
+
}, /*#__PURE__*/React__default.default.createElement(index.NavigationLink, {
|
|
64
|
+
item: item,
|
|
65
|
+
onClick: onItemClick,
|
|
66
|
+
renderLink: renderLink
|
|
67
|
+
})));
|
|
68
|
+
return /*#__PURE__*/React__default.default.createElement("nav", Object.assign({}, props, {
|
|
69
|
+
ref: node => {
|
|
70
|
+
containerRef.current = node;
|
|
71
|
+
helpers.assignRef(navRef, node);
|
|
72
|
+
},
|
|
73
|
+
className: clsx__default.default(rootClassName, styles__default.default.overflow, {
|
|
74
|
+
[styles__default.default.overflow_measuring]: !split
|
|
75
|
+
})
|
|
76
|
+
}), /*#__PURE__*/React__default.default.createElement("ul", {
|
|
77
|
+
ref: listRef,
|
|
78
|
+
className: styles__default.default.list
|
|
79
|
+
}, visibleItems.map(renderItem), pinnedItems.map(renderItem), /*#__PURE__*/React__default.default.createElement("li", {
|
|
80
|
+
ref: moreRef,
|
|
81
|
+
className: clsx__default.default(styles__default.default.list_item, {
|
|
82
|
+
[styles__default.default.more_hidden]: isMoreHidden
|
|
83
|
+
}),
|
|
84
|
+
"aria-hidden": isMoreHidden || undefined
|
|
85
|
+
}, /*#__PURE__*/React__default.default.createElement(index$1.MoreMenu, {
|
|
86
|
+
items: hiddenItems,
|
|
87
|
+
label: moreLabel,
|
|
88
|
+
hidden: isMoreHidden,
|
|
89
|
+
onItemClick: onItemClick,
|
|
90
|
+
renderLink: renderLink,
|
|
91
|
+
onFocusLost: restoreFocus
|
|
92
|
+
}))), /*#__PURE__*/React__default.default.createElement("div", {
|
|
93
|
+
ref: measurerRef,
|
|
94
|
+
className: styles__default.default.measurer,
|
|
95
|
+
"aria-hidden": "true"
|
|
96
|
+
}, [...items, ...pinnedItems].map((item, index$1) => (/*#__PURE__*/React__default.default.createElement("span", {
|
|
97
|
+
key: helpers.getItemKey(item, index$1),
|
|
98
|
+
className: index.getLinkClassName("item", item.active)
|
|
99
|
+
}, /*#__PURE__*/React__default.default.createElement(index.LinkContent, {
|
|
100
|
+
item: item
|
|
101
|
+
}))))));
|
|
102
|
+
};
|
|
103
|
+
const ScrollMenu = _a => {
|
|
104
|
+
var {
|
|
105
|
+
items,
|
|
106
|
+
pinnedItems = [],
|
|
107
|
+
moreItems = [],
|
|
108
|
+
onItemClick,
|
|
109
|
+
renderLink,
|
|
110
|
+
navRef,
|
|
111
|
+
rootClassName
|
|
112
|
+
} = _a,
|
|
113
|
+
props = tslib.__rest(_a, ["items", "pinnedItems", "moreItems", "onItemClick", "renderLink", "navRef", "rootClassName"]);
|
|
114
|
+
const scrollRef = React.useRef(null);
|
|
115
|
+
const activeRef = React.useRef(null);
|
|
116
|
+
const allItems = [...items, ...pinnedItems, ...moreItems];
|
|
117
|
+
const activeIndex = getActiveIndex(allItems);
|
|
118
|
+
// Center the active item when it is not fully visible.
|
|
119
|
+
helpers.useIsomorphicLayoutEffect(() => {
|
|
120
|
+
const container = scrollRef.current;
|
|
121
|
+
const active = activeRef.current;
|
|
122
|
+
if (!container || !active) return;
|
|
123
|
+
const {
|
|
124
|
+
offsetLeft,
|
|
125
|
+
offsetWidth
|
|
126
|
+
} = active;
|
|
127
|
+
const isVisible = offsetLeft >= container.scrollLeft && offsetLeft + offsetWidth <= container.scrollLeft + container.clientWidth;
|
|
128
|
+
if (!isVisible) {
|
|
129
|
+
container.scrollLeft = offsetLeft + offsetWidth / 2 - container.clientWidth / 2;
|
|
130
|
+
}
|
|
131
|
+
}, [activeIndex]);
|
|
132
|
+
return /*#__PURE__*/React__default.default.createElement("nav", Object.assign({}, props, {
|
|
133
|
+
ref: navRef,
|
|
134
|
+
className: clsx__default.default(rootClassName, styles__default.default.scroll)
|
|
135
|
+
}), /*#__PURE__*/React__default.default.createElement("div", {
|
|
136
|
+
ref: scrollRef,
|
|
137
|
+
className: styles__default.default.scroll_area
|
|
138
|
+
}, /*#__PURE__*/React__default.default.createElement("ul", {
|
|
139
|
+
className: styles__default.default.list
|
|
140
|
+
}, allItems.map((item, index$1) => (/*#__PURE__*/React__default.default.createElement("li", {
|
|
141
|
+
key: helpers.getItemKey(item, index$1),
|
|
142
|
+
ref: index$1 === activeIndex ? activeRef : undefined,
|
|
143
|
+
className: styles__default.default.list_item
|
|
144
|
+
}, /*#__PURE__*/React__default.default.createElement(index.NavigationLink, {
|
|
145
|
+
item: item,
|
|
146
|
+
onClick: onItemClick,
|
|
147
|
+
renderLink: renderLink
|
|
148
|
+
})))))));
|
|
149
|
+
};
|
|
150
|
+
const NavigationMenu = /*#__PURE__*/React.forwardRef((_a, ref) => {
|
|
151
|
+
var {
|
|
152
|
+
layout = "overflow",
|
|
153
|
+
size = "m",
|
|
154
|
+
moreLabel = "More",
|
|
155
|
+
className
|
|
156
|
+
} = _a,
|
|
157
|
+
props = tslib.__rest(_a, ["layout", "size", "moreLabel", "className"]);
|
|
158
|
+
const rootClassName = clsx__default.default(styles__default.default.root, styles__default.default[`size_${size}`], className);
|
|
159
|
+
return layout === "scroll" ? (/*#__PURE__*/React__default.default.createElement(ScrollMenu, Object.assign({}, props, {
|
|
160
|
+
navRef: ref,
|
|
161
|
+
rootClassName: rootClassName
|
|
162
|
+
}))) : (/*#__PURE__*/React__default.default.createElement(OverflowMenu, Object.assign({}, props, {
|
|
163
|
+
moreLabel: moreLabel,
|
|
164
|
+
navRef: ref,
|
|
165
|
+
rootClassName: rootClassName
|
|
166
|
+
})));
|
|
167
|
+
});
|
|
168
|
+
NavigationMenu.displayName = "NavigationMenu";
|
|
169
|
+
|
|
170
|
+
exports.NavigationMenu = NavigationMenu;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
font-family: var(--font-family-ui);
|
|
3
|
+
min-inline-size: 0;
|
|
4
|
+
}
|
|
5
|
+
.root.size_m {
|
|
6
|
+
--navigation-item-height: calc(var(--space-8) + var(--space-1));
|
|
7
|
+
--navigation-item-font-size: var(--font-size-text-md);
|
|
8
|
+
--navigation-item-line-height: var(--line-height-text-md);
|
|
9
|
+
--navigation-item-padding: var(--space-4);
|
|
10
|
+
--navigation-menu-gap: var(--space-2);
|
|
11
|
+
}
|
|
12
|
+
.root.size_s {
|
|
13
|
+
--navigation-item-height: var(--space-8);
|
|
14
|
+
--navigation-item-font-size: var(--font-size-text-sm);
|
|
15
|
+
--navigation-item-line-height: var(--line-height-text-sm);
|
|
16
|
+
--navigation-item-padding: var(--space-3);
|
|
17
|
+
--navigation-menu-gap: var(--space-1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.list {
|
|
21
|
+
align-items: center;
|
|
22
|
+
display: flex;
|
|
23
|
+
gap: var(--navigation-menu-gap);
|
|
24
|
+
list-style: none;
|
|
25
|
+
margin: 0;
|
|
26
|
+
padding: 0;
|
|
27
|
+
}
|
|
28
|
+
.list_item {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.overflow {
|
|
34
|
+
display: block;
|
|
35
|
+
position: relative;
|
|
36
|
+
}
|
|
37
|
+
.overflow_measuring {
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.more_hidden {
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
position: absolute;
|
|
44
|
+
visibility: hidden;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.measurer {
|
|
48
|
+
block-size: 0;
|
|
49
|
+
display: flex;
|
|
50
|
+
inline-size: max-content;
|
|
51
|
+
inset-block-start: 0;
|
|
52
|
+
inset-inline-start: 0;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
position: absolute;
|
|
56
|
+
visibility: hidden;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.scroll_area {
|
|
60
|
+
overflow: auto hidden;
|
|
61
|
+
overscroll-behavior-x: contain;
|
|
62
|
+
position: relative;
|
|
63
|
+
scrollbar-width: none;
|
|
64
|
+
}
|
|
65
|
+
.scroll_area::-webkit-scrollbar {
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
display: contents;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.chevron {
|
|
6
|
+
flex-shrink: 0;
|
|
7
|
+
transition: transform 0.15s ease;
|
|
8
|
+
}
|
|
9
|
+
.chevron_open {
|
|
10
|
+
transform: rotate(180deg);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.options {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
font-family: var(--font-family-ui);
|
|
17
|
+
min-inline-size: calc(var(--space-32) + var(--space-16));
|
|
18
|
+
padding: var(--space-1);
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { NavigationMenuItem, NavigationMenuRenderLink } from "../../index.props";
|
|
4
|
+
interface MoreMenuProps {
|
|
5
|
+
items: NavigationMenuItem[];
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
/** Nothing to show: the trigger stays in the DOM only to be measured. */
|
|
8
|
+
hidden: boolean;
|
|
9
|
+
onItemClick?: (item: NavigationMenuItem) => void;
|
|
10
|
+
renderLink?: NavigationMenuRenderLink;
|
|
11
|
+
/** The menu became hidden while this item had focus inside the dropdown. */
|
|
12
|
+
onFocusLost: (item: NavigationMenuItem) => void;
|
|
13
|
+
}
|
|
14
|
+
/** "More" trigger with a dropdown of the items that do not fit. */
|
|
15
|
+
export declare const MoreMenu: ({ items, label, hidden, onItemClick, renderLink, onFocusLost, }: MoreMenuProps) => React.JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var clsx = require('clsx');
|
|
5
|
+
var index$1 = require('../../../Dropdown/index.js');
|
|
6
|
+
require('../../../Icons/index.js');
|
|
7
|
+
var helpers = require('../../helpers.js');
|
|
8
|
+
var index = require('../NavigationLink/index.js');
|
|
9
|
+
var styles = require('./MoreMenu.module.css');
|
|
10
|
+
var Chevron = require('../../../Icons/Chevron.js');
|
|
11
|
+
|
|
12
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
|
|
14
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
15
|
+
var clsx__default = /*#__PURE__*/_interopDefault(clsx);
|
|
16
|
+
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
17
|
+
|
|
18
|
+
const getLinkPosition = event => {
|
|
19
|
+
const links = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
|
|
20
|
+
return {
|
|
21
|
+
links,
|
|
22
|
+
index: links.indexOf(event.target)
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const MoreOptions = ({
|
|
26
|
+
items,
|
|
27
|
+
onItemClick,
|
|
28
|
+
renderLink,
|
|
29
|
+
rootRef,
|
|
30
|
+
onItemFocus,
|
|
31
|
+
onTabOut
|
|
32
|
+
}) => {
|
|
33
|
+
// The dropdown is portaled to the end of the document: move focus into it,
|
|
34
|
+
// to the current section or to the first link.
|
|
35
|
+
helpers.useIsomorphicLayoutEffect(() => {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
const root = rootRef.current;
|
|
38
|
+
(_b = (_a = root === null || root === void 0 ? void 0 : root.querySelector('[aria-current="page"]')) !== null && _a !== void 0 ? _a : root === null || root === void 0 ? void 0 : root.querySelector('[role="menuitem"]')) === null || _b === void 0 ? void 0 : _b.focus();
|
|
39
|
+
}, [rootRef]);
|
|
40
|
+
// Focus events bubble from the links, whatever `renderLink` renders.
|
|
41
|
+
const handleFocus = event => {
|
|
42
|
+
const {
|
|
43
|
+
index
|
|
44
|
+
} = getLinkPosition(event);
|
|
45
|
+
if (index !== -1) onItemFocus(items[index]);
|
|
46
|
+
};
|
|
47
|
+
// Arrow keys move focus through the links, Home/End go to the first and the
|
|
48
|
+
// last one. Tab leaves through the trigger, so focus continues from it.
|
|
49
|
+
const handleKeyDown = event => {
|
|
50
|
+
var _a;
|
|
51
|
+
if (event.key === "Tab") {
|
|
52
|
+
onTabOut();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const {
|
|
56
|
+
links,
|
|
57
|
+
index
|
|
58
|
+
} = getLinkPosition(event);
|
|
59
|
+
const last = links.length - 1;
|
|
60
|
+
const targets = {
|
|
61
|
+
ArrowDown: index === last ? 0 : index + 1,
|
|
62
|
+
ArrowUp: index === 0 ? last : index - 1,
|
|
63
|
+
Home: 0,
|
|
64
|
+
End: last
|
|
65
|
+
};
|
|
66
|
+
if (index === -1 || !(event.key in targets)) return;
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
(_a = links[targets[event.key]]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
69
|
+
};
|
|
70
|
+
return (
|
|
71
|
+
/*#__PURE__*/
|
|
72
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions -- events come from the focused links
|
|
73
|
+
React__default.default.createElement("div", {
|
|
74
|
+
ref: rootRef,
|
|
75
|
+
className: styles__default.default.options,
|
|
76
|
+
onKeyDown: handleKeyDown,
|
|
77
|
+
onFocus: handleFocus
|
|
78
|
+
}, items.map((item, index$1) => (/*#__PURE__*/React__default.default.createElement(index.NavigationLink, {
|
|
79
|
+
key: helpers.getItemKey(item, index$1),
|
|
80
|
+
item: item,
|
|
81
|
+
view: "option",
|
|
82
|
+
role: "menuitem",
|
|
83
|
+
onClick: onItemClick,
|
|
84
|
+
renderLink: renderLink
|
|
85
|
+
}))))
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
/** "More" trigger with a dropdown of the items that do not fit. */
|
|
89
|
+
const MoreMenu = ({
|
|
90
|
+
items,
|
|
91
|
+
label,
|
|
92
|
+
hidden,
|
|
93
|
+
onItemClick,
|
|
94
|
+
renderLink,
|
|
95
|
+
onFocusLost
|
|
96
|
+
}) => {
|
|
97
|
+
const [isOpen, setOpen] = React.useState(false);
|
|
98
|
+
const rootRef = React.useRef(null);
|
|
99
|
+
const overlayRef = React.useRef(null);
|
|
100
|
+
const focusedItemRef = React.useRef(null);
|
|
101
|
+
// A resize hides "More" and removes the dropdown with the focused link.
|
|
102
|
+
// Layout effect: focus moves before the browser paints the new row.
|
|
103
|
+
helpers.useIsomorphicLayoutEffect(() => {
|
|
104
|
+
if (!hidden) return;
|
|
105
|
+
const item = focusedItemRef.current;
|
|
106
|
+
const isFocusLost = !document.activeElement || document.activeElement === document.body;
|
|
107
|
+
focusedItemRef.current = null;
|
|
108
|
+
setOpen(false);
|
|
109
|
+
if (item && isFocusLost) onFocusLost(item);
|
|
110
|
+
}, [hidden]);
|
|
111
|
+
// Selection, Escape and Tab remove the focused link together with the
|
|
112
|
+
// dropdown: return focus to the trigger.
|
|
113
|
+
const close = () => {
|
|
114
|
+
var _a, _b;
|
|
115
|
+
const trigger = (_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.firstElementChild;
|
|
116
|
+
if (((_b = overlayRef.current) === null || _b === void 0 ? void 0 : _b.contains(document.activeElement)) && trigger instanceof HTMLElement) {
|
|
117
|
+
trigger.focus();
|
|
118
|
+
}
|
|
119
|
+
focusedItemRef.current = null;
|
|
120
|
+
setOpen(false);
|
|
121
|
+
};
|
|
122
|
+
return /*#__PURE__*/React__default.default.createElement("span", {
|
|
123
|
+
ref: rootRef,
|
|
124
|
+
className: styles__default.default.root
|
|
125
|
+
}, /*#__PURE__*/React__default.default.createElement(index$1.Dropdown, {
|
|
126
|
+
trigger: ["click"],
|
|
127
|
+
placement: "bottom-end",
|
|
128
|
+
visible: isOpen,
|
|
129
|
+
disabled: hidden,
|
|
130
|
+
onVisibleChange: visible => visible ? setOpen(true) : close(),
|
|
131
|
+
overlay: /*#__PURE__*/React__default.default.createElement(MoreOptions, {
|
|
132
|
+
items: items,
|
|
133
|
+
onItemClick: onItemClick,
|
|
134
|
+
renderLink: renderLink,
|
|
135
|
+
rootRef: overlayRef,
|
|
136
|
+
onItemFocus: item => {
|
|
137
|
+
focusedItemRef.current = item;
|
|
138
|
+
},
|
|
139
|
+
onTabOut: close
|
|
140
|
+
})
|
|
141
|
+
}, /*#__PURE__*/React__default.default.createElement("button", {
|
|
142
|
+
type: "button",
|
|
143
|
+
className: index.getLinkClassName(),
|
|
144
|
+
tabIndex: hidden ? -1 : undefined
|
|
145
|
+
}, label, /*#__PURE__*/React__default.default.createElement(Chevron.Chevron, {
|
|
146
|
+
width: 16,
|
|
147
|
+
height: 16,
|
|
148
|
+
"aria-hidden": "true",
|
|
149
|
+
className: clsx__default.default(styles__default.default.chevron, {
|
|
150
|
+
[styles__default.default.chevron_open]: isOpen
|
|
151
|
+
})
|
|
152
|
+
}))));
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
exports.MoreMenu = MoreMenu;
|