@dalcvil/steam-green-react 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -25
- package/README.md +38 -12
- package/dist/components/Avatar/Avatar.css +136 -0
- package/dist/components/Avatar/Avatar.d.ts +22 -0
- package/dist/components/Avatar/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar/Avatar.js +49 -0
- package/dist/components/Avatar/Avatar.js.map +1 -0
- package/dist/components/Avatar/index.d.ts +3 -0
- package/dist/components/Avatar/index.d.ts.map +1 -0
- package/dist/components/Button/Button.css +117 -0
- package/dist/components/Button/Button.css.d.ts +1 -0
- package/dist/components/Button/Button.d.ts +18 -0
- package/dist/components/Button/Button.d.ts.map +1 -0
- package/dist/components/Button/Button.js +29 -0
- package/dist/components/Button/Button.js.map +1 -0
- package/dist/components/Button/index.d.ts +3 -0
- package/dist/components/Button/index.d.ts.map +1 -0
- package/dist/components/Checkbox/Checkbox.css +116 -0
- package/dist/components/Checkbox/Checkbox.css.d.ts +1 -0
- package/dist/components/Checkbox/Checkbox.d.ts +19 -0
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox/Checkbox.js +51 -0
- package/dist/components/Checkbox/Checkbox.js.map +1 -0
- package/dist/components/Checkbox/index.d.ts +3 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -0
- package/dist/components/ColorPicker/ColorPicker.css +331 -0
- package/dist/components/ColorPicker/ColorPicker.css.d.ts +1 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts +29 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts.map +1 -0
- package/dist/components/ColorPicker/ColorPicker.js +534 -0
- package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
- package/dist/components/ColorPicker/index.d.ts +3 -0
- package/dist/components/{GreenButton → ColorPicker}/index.d.ts.map +1 -1
- package/dist/components/Console/Console.css +96 -0
- package/dist/components/Console/Console.css.d.ts +1 -0
- package/dist/components/Console/Console.d.ts +26 -0
- package/dist/components/Console/Console.d.ts.map +1 -0
- package/dist/components/Console/Console.js +59 -0
- package/dist/components/Console/Console.js.map +1 -0
- package/dist/components/Console/index.d.ts +3 -0
- package/dist/components/Console/index.d.ts.map +1 -0
- package/dist/components/Dialog/Dialog.css +137 -0
- package/dist/components/Dialog/Dialog.css.d.ts +1 -0
- package/dist/components/Dialog/Dialog.d.ts +28 -0
- package/dist/components/Dialog/Dialog.d.ts.map +1 -0
- package/dist/components/Dialog/Dialog.js +151 -0
- package/dist/components/Dialog/Dialog.js.map +1 -0
- package/dist/components/Dialog/index.d.ts +3 -0
- package/dist/components/Dialog/index.d.ts.map +1 -0
- package/dist/components/Divider/Divider.css +68 -0
- package/dist/components/Divider/Divider.css.d.ts +1 -0
- package/dist/components/Divider/Divider.d.ts +12 -0
- package/dist/components/Divider/Divider.d.ts.map +1 -0
- package/dist/components/Divider/Divider.js +23 -0
- package/dist/components/Divider/Divider.js.map +1 -0
- package/dist/components/Divider/index.d.ts +3 -0
- package/dist/components/Divider/index.d.ts.map +1 -0
- package/dist/components/FieldLabel/FieldLabel.css +98 -0
- package/dist/components/FieldLabel/FieldLabel.css.d.ts +1 -0
- package/dist/components/FieldLabel/FieldLabel.d.ts +29 -0
- package/dist/components/FieldLabel/FieldLabel.d.ts.map +1 -0
- package/dist/components/FieldLabel/FieldLabel.js +42 -0
- package/dist/components/FieldLabel/FieldLabel.js.map +1 -0
- package/dist/components/FieldLabel/index.d.ts +3 -0
- package/dist/components/FieldLabel/index.d.ts.map +1 -0
- package/dist/components/GroupBox/GroupBox.css +101 -0
- package/dist/components/GroupBox/GroupBox.css.d.ts +1 -0
- package/dist/components/GroupBox/GroupBox.d.ts +14 -0
- package/dist/components/GroupBox/GroupBox.d.ts.map +1 -0
- package/dist/components/GroupBox/GroupBox.js +25 -0
- package/dist/components/GroupBox/GroupBox.js.map +1 -0
- package/dist/components/GroupBox/index.d.ts +3 -0
- package/dist/components/GroupBox/index.d.ts.map +1 -0
- package/dist/components/IconButton/IconButton.css +184 -0
- package/dist/components/IconButton/IconButton.css.d.ts +1 -0
- package/dist/components/IconButton/IconButton.d.ts +56 -0
- package/dist/components/IconButton/IconButton.d.ts.map +1 -0
- package/dist/components/IconButton/IconButton.js +77 -0
- package/dist/components/IconButton/IconButton.js.map +1 -0
- package/dist/components/IconButton/index.d.ts +3 -0
- package/dist/components/IconButton/index.d.ts.map +1 -0
- package/dist/components/LevelMeter/LevelMeter.css +117 -0
- package/dist/components/LevelMeter/LevelMeter.css.d.ts +1 -0
- package/dist/components/LevelMeter/LevelMeter.d.ts +20 -0
- package/dist/components/LevelMeter/LevelMeter.d.ts.map +1 -0
- package/dist/components/LevelMeter/LevelMeter.js +44 -0
- package/dist/components/LevelMeter/LevelMeter.js.map +1 -0
- package/dist/components/LevelMeter/index.d.ts +3 -0
- package/dist/components/LevelMeter/index.d.ts.map +1 -0
- package/dist/components/Link/Link.css +127 -0
- package/dist/components/Link/Link.css.d.ts +1 -0
- package/dist/components/Link/Link.d.ts +26 -0
- package/dist/components/Link/Link.d.ts.map +1 -0
- package/dist/components/Link/Link.js +60 -0
- package/dist/components/Link/Link.js.map +1 -0
- package/dist/components/Link/index.d.ts +3 -0
- package/dist/components/Link/index.d.ts.map +1 -0
- package/dist/components/List/List.css +224 -0
- package/dist/components/List/List.css.d.ts +1 -0
- package/dist/components/List/List.d.ts +49 -0
- package/dist/components/List/List.d.ts.map +1 -0
- package/dist/components/List/List.js +300 -0
- package/dist/components/List/List.js.map +1 -0
- package/dist/components/List/index.d.ts +3 -0
- package/dist/components/List/index.d.ts.map +1 -0
- package/dist/components/Menu/Menu.css +122 -0
- package/dist/components/Menu/Menu.css.d.ts +1 -0
- package/dist/components/Menu/Menu.d.ts +46 -0
- package/dist/components/Menu/Menu.d.ts.map +1 -0
- package/dist/components/Menu/Menu.js +246 -0
- package/dist/components/Menu/Menu.js.map +1 -0
- package/dist/components/Menu/index.d.ts +3 -0
- package/dist/components/Menu/index.d.ts.map +1 -0
- package/dist/components/MenuBar/MenuBar.css +94 -0
- package/dist/components/MenuBar/MenuBar.css.d.ts +1 -0
- package/dist/components/MenuBar/MenuBar.d.ts +34 -0
- package/dist/components/MenuBar/MenuBar.d.ts.map +1 -0
- package/dist/components/MenuBar/MenuBar.js +129 -0
- package/dist/components/MenuBar/MenuBar.js.map +1 -0
- package/dist/components/MenuBar/index.d.ts +3 -0
- package/dist/components/MenuBar/index.d.ts.map +1 -0
- package/dist/components/NavBar/NavBar.css +179 -0
- package/dist/components/NavBar/NavBar.css.d.ts +1 -0
- package/dist/components/NavBar/NavBar.d.ts +39 -0
- package/dist/components/NavBar/NavBar.d.ts.map +1 -0
- package/dist/components/NavBar/NavBar.js +198 -0
- package/dist/components/NavBar/NavBar.js.map +1 -0
- package/dist/components/NavBar/index.d.ts +3 -0
- package/dist/components/NavBar/index.d.ts.map +1 -0
- package/dist/components/Notification/Notification.css +176 -0
- package/dist/components/Notification/Notification.css.d.ts +1 -0
- package/dist/components/Notification/Notification.d.ts +26 -0
- package/dist/components/Notification/Notification.d.ts.map +1 -0
- package/dist/components/Notification/Notification.js +181 -0
- package/dist/components/Notification/Notification.js.map +1 -0
- package/dist/components/Notification/index.d.ts +3 -0
- package/dist/components/Notification/index.d.ts.map +1 -0
- package/dist/components/Panel/Panel.css +56 -0
- package/dist/components/Panel/Panel.css.d.ts +1 -0
- package/dist/components/Panel/Panel.d.ts +14 -0
- package/dist/components/Panel/Panel.d.ts.map +1 -0
- package/dist/components/Panel/Panel.js +42 -0
- package/dist/components/Panel/Panel.js.map +1 -0
- package/dist/components/Panel/index.d.ts +3 -0
- package/dist/components/Panel/index.d.ts.map +1 -0
- package/dist/components/ProgressBar/ProgressBar.css +108 -0
- package/dist/components/ProgressBar/ProgressBar.css.d.ts +1 -0
- package/dist/components/ProgressBar/ProgressBar.d.ts +22 -0
- package/dist/components/ProgressBar/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar/ProgressBar.js +43 -0
- package/dist/components/ProgressBar/ProgressBar.js.map +1 -0
- package/dist/components/ProgressBar/index.d.ts +3 -0
- package/dist/components/ProgressBar/index.d.ts.map +1 -0
- package/dist/components/Radio/Radio.css +125 -0
- package/dist/components/Radio/Radio.css.d.ts +1 -0
- package/dist/components/Radio/Radio.d.ts +40 -0
- package/dist/components/Radio/Radio.d.ts.map +1 -0
- package/dist/components/Radio/Radio.js +91 -0
- package/dist/components/Radio/Radio.js.map +1 -0
- package/dist/components/Radio/index.d.ts +3 -0
- package/dist/components/Radio/index.d.ts.map +1 -0
- package/dist/components/RichText/RichText.css +96 -0
- package/dist/components/RichText/RichText.css.d.ts +1 -0
- package/dist/components/RichText/RichText.d.ts +25 -0
- package/dist/components/RichText/RichText.d.ts.map +1 -0
- package/dist/components/RichText/RichText.js +56 -0
- package/dist/components/RichText/RichText.js.map +1 -0
- package/dist/components/RichText/index.d.ts +3 -0
- package/dist/components/RichText/index.d.ts.map +1 -0
- package/dist/components/ScrollArea/ScrollArea.css +236 -0
- package/dist/components/ScrollArea/ScrollArea.css.d.ts +1 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts +25 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -0
- package/dist/components/ScrollArea/ScrollArea.js +124 -0
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -0
- package/dist/components/ScrollArea/index.d.ts +3 -0
- package/dist/components/ScrollArea/index.d.ts.map +1 -0
- package/dist/components/Scrollbar/Scrollbar.css +260 -0
- package/dist/components/Scrollbar/Scrollbar.css.d.ts +1 -0
- package/dist/components/Scrollbar/Scrollbar.d.ts +31 -0
- package/dist/components/Scrollbar/Scrollbar.d.ts.map +1 -0
- package/dist/components/Scrollbar/Scrollbar.js +277 -0
- package/dist/components/Scrollbar/Scrollbar.js.map +1 -0
- package/dist/components/Scrollbar/index.d.ts +3 -0
- package/dist/components/Scrollbar/index.d.ts.map +1 -0
- package/dist/components/Select/Select.css +299 -0
- package/dist/components/Select/Select.css.d.ts +1 -0
- package/dist/components/Select/Select.d.ts +52 -0
- package/dist/components/Select/Select.d.ts.map +1 -0
- package/dist/components/Select/Select.js +281 -0
- package/dist/components/Select/Select.js.map +1 -0
- package/dist/components/Select/index.d.ts +3 -0
- package/dist/components/Select/index.d.ts.map +1 -0
- package/dist/components/Slider/Slider.css +158 -0
- package/dist/components/Slider/Slider.css.d.ts +1 -0
- package/dist/components/Slider/Slider.d.ts +26 -0
- package/dist/components/Slider/Slider.d.ts.map +1 -0
- package/dist/components/Slider/Slider.js +67 -0
- package/dist/components/Slider/Slider.js.map +1 -0
- package/dist/components/Slider/index.d.ts +3 -0
- package/dist/components/Slider/index.d.ts.map +1 -0
- package/dist/components/SpinBox/SpinBox.css +152 -0
- package/dist/components/SpinBox/SpinBox.css.d.ts +1 -0
- package/dist/components/SpinBox/SpinBox.d.ts +28 -0
- package/dist/components/SpinBox/SpinBox.d.ts.map +1 -0
- package/dist/components/SpinBox/SpinBox.js +207 -0
- package/dist/components/SpinBox/SpinBox.js.map +1 -0
- package/dist/components/SpinBox/index.d.ts +3 -0
- package/dist/components/SpinBox/index.d.ts.map +1 -0
- package/dist/components/Spinner/Spinner.css +137 -0
- package/dist/components/Spinner/Spinner.css.d.ts +1 -0
- package/dist/components/Spinner/Spinner.d.ts +20 -0
- package/dist/components/Spinner/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner/Spinner.js +36 -0
- package/dist/components/Spinner/Spinner.js.map +1 -0
- package/dist/components/Spinner/index.d.ts +3 -0
- package/dist/components/Spinner/index.d.ts.map +1 -0
- package/dist/components/Splitter/Splitter.css +119 -0
- package/dist/components/Splitter/Splitter.css.d.ts +1 -0
- package/dist/components/Splitter/Splitter.d.ts +25 -0
- package/dist/components/Splitter/Splitter.d.ts.map +1 -0
- package/dist/components/Splitter/Splitter.js +212 -0
- package/dist/components/Splitter/Splitter.js.map +1 -0
- package/dist/components/Splitter/index.d.ts +3 -0
- package/dist/components/Splitter/index.d.ts.map +1 -0
- package/dist/components/StatusBar/StatusBar.css +97 -0
- package/dist/components/StatusBar/StatusBar.css.d.ts +1 -0
- package/dist/components/StatusBar/StatusBar.d.ts +22 -0
- package/dist/components/StatusBar/StatusBar.d.ts.map +1 -0
- package/dist/components/StatusBar/StatusBar.js +56 -0
- package/dist/components/StatusBar/StatusBar.js.map +1 -0
- package/dist/components/StatusBar/index.d.ts +3 -0
- package/dist/components/StatusBar/index.d.ts.map +1 -0
- package/dist/components/StatusLabel/StatusLabel.css +93 -0
- package/dist/components/StatusLabel/StatusLabel.css.d.ts +1 -0
- package/dist/components/StatusLabel/StatusLabel.d.ts +20 -0
- package/dist/components/StatusLabel/StatusLabel.d.ts.map +1 -0
- package/dist/components/StatusLabel/StatusLabel.js +42 -0
- package/dist/components/StatusLabel/StatusLabel.js.map +1 -0
- package/dist/components/StatusLabel/index.d.ts +3 -0
- package/dist/components/StatusLabel/index.d.ts.map +1 -0
- package/dist/components/Table/Table.css +253 -0
- package/dist/components/Table/Table.css.d.ts +1 -0
- package/dist/components/Table/Table.d.ts +67 -0
- package/dist/components/Table/Table.d.ts.map +1 -0
- package/dist/components/Table/Table.js +429 -0
- package/dist/components/Table/Table.js.map +1 -0
- package/dist/components/Table/index.d.ts +3 -0
- package/dist/components/Table/index.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.css +138 -0
- package/dist/components/Tabs/Tabs.css.d.ts +1 -0
- package/dist/components/Tabs/Tabs.d.ts +34 -0
- package/dist/components/Tabs/Tabs.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.js +140 -0
- package/dist/components/Tabs/Tabs.js.map +1 -0
- package/dist/components/Tabs/index.d.ts +3 -0
- package/dist/components/Tabs/index.d.ts.map +1 -0
- package/dist/components/TextInput/TextInput.css +133 -0
- package/dist/components/TextInput/TextInput.css.d.ts +1 -0
- package/dist/components/TextInput/TextInput.d.ts +16 -0
- package/dist/components/TextInput/TextInput.d.ts.map +1 -0
- package/dist/components/TextInput/TextInput.js +38 -0
- package/dist/components/TextInput/TextInput.js.map +1 -0
- package/dist/components/TextInput/index.d.ts +3 -0
- package/dist/components/TextInput/index.d.ts.map +1 -0
- package/dist/components/Textarea/Textarea.css +121 -0
- package/dist/components/Textarea/Textarea.css.d.ts +1 -0
- package/dist/components/Textarea/Textarea.d.ts +21 -0
- package/dist/components/Textarea/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea/Textarea.js +61 -0
- package/dist/components/Textarea/Textarea.js.map +1 -0
- package/dist/components/Textarea/index.d.ts +3 -0
- package/dist/components/Textarea/index.d.ts.map +1 -0
- package/dist/components/TitleBar/TitleBar.css +76 -0
- package/dist/components/TitleBar/TitleBar.css.d.ts +1 -0
- package/dist/components/TitleBar/TitleBar.d.ts +20 -0
- package/dist/components/TitleBar/TitleBar.d.ts.map +1 -0
- package/dist/components/TitleBar/TitleBar.js +43 -0
- package/dist/components/TitleBar/TitleBar.js.map +1 -0
- package/dist/components/TitleBar/index.d.ts +3 -0
- package/dist/components/TitleBar/index.d.ts.map +1 -0
- package/dist/components/ToggleButton/ToggleButton.css +162 -0
- package/dist/components/ToggleButton/ToggleButton.css.d.ts +1 -0
- package/dist/components/ToggleButton/ToggleButton.d.ts +27 -0
- package/dist/components/ToggleButton/ToggleButton.d.ts.map +1 -0
- package/dist/components/ToggleButton/ToggleButton.js +52 -0
- package/dist/components/ToggleButton/ToggleButton.js.map +1 -0
- package/dist/components/ToggleButton/index.d.ts +3 -0
- package/dist/components/ToggleButton/index.d.ts.map +1 -0
- package/dist/components/Toolbar/Toolbar.css +202 -0
- package/dist/components/Toolbar/Toolbar.css.d.ts +1 -0
- package/dist/components/Toolbar/Toolbar.d.ts +46 -0
- package/dist/components/Toolbar/Toolbar.d.ts.map +1 -0
- package/dist/components/Toolbar/Toolbar.js +102 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/components/Toolbar/index.d.ts +3 -0
- package/dist/components/Toolbar/index.d.ts.map +1 -0
- package/dist/components/Tooltip/Tooltip.css +86 -0
- package/dist/components/Tooltip/Tooltip.css.d.ts +1 -0
- package/dist/components/Tooltip/Tooltip.d.ts +22 -0
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip/Tooltip.js +92 -0
- package/dist/components/Tooltip/Tooltip.js.map +1 -0
- package/dist/components/Tooltip/index.d.ts +3 -0
- package/dist/components/Tooltip/index.d.ts.map +1 -0
- package/dist/components/TreeView/TreeView.css +191 -0
- package/dist/components/TreeView/TreeView.css.d.ts +1 -0
- package/dist/components/TreeView/TreeView.d.ts +46 -0
- package/dist/components/TreeView/TreeView.d.ts.map +1 -0
- package/dist/components/TreeView/TreeView.js +331 -0
- package/dist/components/TreeView/TreeView.js.map +1 -0
- package/dist/components/TreeView/index.d.ts +3 -0
- package/dist/components/TreeView/index.d.ts.map +1 -0
- package/dist/components/Window/Window.css +218 -0
- package/dist/components/Window/Window.css.d.ts +1 -0
- package/dist/components/Window/Window.d.ts +30 -0
- package/dist/components/Window/Window.d.ts.map +1 -0
- package/dist/components/Window/Window.js +156 -0
- package/dist/components/Window/Window.js.map +1 -0
- package/dist/components/Window/index.d.ts +3 -0
- package/dist/components/Window/index.d.ts.map +1 -0
- package/dist/index.d.ts +91 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -2
- package/dist/styles/scrollbars.css +123 -0
- package/dist/styles/scrollbars.css.d.ts +1 -0
- package/dist/styles/theme.css +167 -122
- package/dist/styles/tokens.css +202 -0
- package/dist/styles/tokens.css.d.ts +1 -0
- package/dist/test/a11y.d.ts +3 -0
- package/dist/test/a11y.d.ts.map +1 -0
- package/dist/test/index.d.ts +2 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/package.json +23 -14
- package/dist/components/GreenButton/GreenButton.css +0 -47
- package/dist/components/GreenButton/GreenButton.d.ts +0 -8
- package/dist/components/GreenButton/GreenButton.d.ts.map +0 -1
- package/dist/components/GreenButton/GreenButton.js +0 -20
- package/dist/components/GreenButton/GreenButton.js.map +0 -1
- package/dist/components/GreenButton/index.d.ts +0 -3
- /package/dist/components/{GreenButton/GreenButton.css.d.ts → Avatar/Avatar.css.d.ts} +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Menu } from "../Menu/Menu.js";
|
|
2
|
+
import { forwardRef, useId, useRef, useState } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import "./MenuBar.css";
|
|
5
|
+
//#region src/components/MenuBar/MenuBar.tsx
|
|
6
|
+
/** A strip of top-level words, each opening a {@link Menu} — `role="menubar"`.
|
|
7
|
+
*
|
|
8
|
+
* One documented departure from the VGUI original: the resting word colour is
|
|
9
|
+
* `--vgui-text-strong` rather than `TitleDimText` (`#889180`), which is only
|
|
10
|
+
* 2.31:1 on a `GreenBG` panel. The state ladder is therefore two steps,
|
|
11
|
+
* white → maize, instead of dim → white → maize.
|
|
12
|
+
*/
|
|
13
|
+
var MenuBar = forwardRef(function MenuBar({ items, orientation = "horizontal", onOpenChange, className, ...rest }, ref) {
|
|
14
|
+
const baseId = useId();
|
|
15
|
+
const itemRefs = useRef([]);
|
|
16
|
+
const isTabKey = useRef(false);
|
|
17
|
+
const [openId, setOpenId] = useState(void 0);
|
|
18
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
19
|
+
const horizontal = orientation !== "vertical";
|
|
20
|
+
const openKey = horizontal ? "ArrowDown" : "ArrowRight";
|
|
21
|
+
const moveKeys = horizontal ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"];
|
|
22
|
+
const firstEnabled = items.findIndex((item) => !item.disabled);
|
|
23
|
+
const current = items[activeIndex] && !items[activeIndex].disabled ? activeIndex : firstEnabled;
|
|
24
|
+
const openIndex = items.findIndex((item) => item.id === openId);
|
|
25
|
+
const openWord = (index) => {
|
|
26
|
+
const spec = items[index];
|
|
27
|
+
if (!spec || spec.disabled) return;
|
|
28
|
+
setActiveIndex(index);
|
|
29
|
+
if (spec.items.length === 0) {
|
|
30
|
+
setOpenId(void 0);
|
|
31
|
+
onOpenChange?.(void 0);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
setOpenId(spec.id);
|
|
35
|
+
onOpenChange?.(spec.id);
|
|
36
|
+
};
|
|
37
|
+
const closeMenu = (index) => {
|
|
38
|
+
setOpenId(void 0);
|
|
39
|
+
onOpenChange?.(void 0);
|
|
40
|
+
if (!isTabKey.current) itemRefs.current[index]?.focus();
|
|
41
|
+
};
|
|
42
|
+
const moveWord = (delta) => {
|
|
43
|
+
const enabled = items.map((item, index) => ({
|
|
44
|
+
item,
|
|
45
|
+
index
|
|
46
|
+
})).filter(({ item }) => !item.disabled);
|
|
47
|
+
if (enabled.length === 0) return;
|
|
48
|
+
const position = enabled.findIndex(({ index }) => index === current);
|
|
49
|
+
const next = enabled[(position === -1 ? 0 : position + delta + enabled.length) % enabled.length];
|
|
50
|
+
setActiveIndex(next.index);
|
|
51
|
+
itemRefs.current[next.index]?.focus();
|
|
52
|
+
if (openId !== void 0) openWord(next.index);
|
|
53
|
+
};
|
|
54
|
+
const onKeyDownCapture = (event) => {
|
|
55
|
+
isTabKey.current = event.key === "Tab";
|
|
56
|
+
};
|
|
57
|
+
const onKeyDown = (event) => {
|
|
58
|
+
const { key } = event;
|
|
59
|
+
const index = openIndex === -1 ? current : openIndex;
|
|
60
|
+
if (key === openKey) {
|
|
61
|
+
if (openId === void 0) openWord(index);
|
|
62
|
+
else return;
|
|
63
|
+
} else if (moveKeys.includes(key)) moveWord(key === moveKeys[0] ? -1 : 1);
|
|
64
|
+
else if (key === "Escape") {
|
|
65
|
+
if (openId === void 0) return;
|
|
66
|
+
closeMenu(index);
|
|
67
|
+
} else if ((key === "Enter" || key === " ") && openId === void 0) openWord(index);
|
|
68
|
+
else return;
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
};
|
|
71
|
+
return /* @__PURE__ */ jsx("div", {
|
|
72
|
+
...rest,
|
|
73
|
+
ref,
|
|
74
|
+
role: "menubar",
|
|
75
|
+
"aria-orientation": orientation,
|
|
76
|
+
className: [
|
|
77
|
+
"vgui-menubar",
|
|
78
|
+
"vgui-menu-bar",
|
|
79
|
+
horizontal ? null : "vgui-menubar--vertical",
|
|
80
|
+
className
|
|
81
|
+
].filter(Boolean).join(" "),
|
|
82
|
+
onKeyDownCapture,
|
|
83
|
+
onKeyDown,
|
|
84
|
+
children: items.map((spec, index) => {
|
|
85
|
+
const isOpen = openId === spec.id;
|
|
86
|
+
const wordId = `${baseId}-menubar-item-${spec.id}`;
|
|
87
|
+
const menuId = `${baseId}-menu-${spec.id}`;
|
|
88
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
89
|
+
className: "vgui-menubar__group",
|
|
90
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
91
|
+
id: wordId,
|
|
92
|
+
ref: (node) => {
|
|
93
|
+
itemRefs.current[index] = node;
|
|
94
|
+
},
|
|
95
|
+
type: "button",
|
|
96
|
+
role: "menuitem",
|
|
97
|
+
className: "vgui-menubar__item",
|
|
98
|
+
"aria-haspopup": "menu",
|
|
99
|
+
"aria-expanded": isOpen,
|
|
100
|
+
"aria-controls": menuId,
|
|
101
|
+
accessKey: spec.accessKey,
|
|
102
|
+
disabled: spec.disabled,
|
|
103
|
+
tabIndex: current === index ? 0 : -1,
|
|
104
|
+
onFocus: () => setActiveIndex(index),
|
|
105
|
+
onClick: () => {
|
|
106
|
+
if (isOpen) closeMenu(index);
|
|
107
|
+
else openWord(index);
|
|
108
|
+
},
|
|
109
|
+
onMouseEnter: () => {
|
|
110
|
+
if (openId !== void 0 && !isOpen) openWord(index);
|
|
111
|
+
},
|
|
112
|
+
children: spec.label
|
|
113
|
+
}), isOpen ? /* @__PURE__ */ jsx(Menu, {
|
|
114
|
+
id: menuId,
|
|
115
|
+
className: "vgui-menubar__menu",
|
|
116
|
+
items: spec.items,
|
|
117
|
+
labelledBy: wordId,
|
|
118
|
+
orientation: horizontal ? "auto" : "vertical",
|
|
119
|
+
onClose: () => closeMenu(index)
|
|
120
|
+
}) : null]
|
|
121
|
+
}, spec.id);
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
MenuBar.displayName = "MenuBar";
|
|
126
|
+
//#endregion
|
|
127
|
+
export { MenuBar };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=MenuBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MenuBar.js","names":[],"sources":["../../../src/components/MenuBar/MenuBar.tsx"],"sourcesContent":["import { forwardRef, useId, useRef, useState } from 'react'\nimport type { HTMLAttributes, KeyboardEvent as ReactKeyboardEvent } from 'react'\n\nimport { Menu } from '../Menu/Menu'\nimport type { MenuItemSpec } from '../Menu/Menu'\n\nimport './MenuBar.css'\n\n/** One top-level word in a {@link MenuBar}. */\nexport interface MenuBarItemSpec {\n /** Stable identity and React key. */\n id: string\n /** The visible word, e.g. \"File\". */\n label: string\n /** Access key, announced and bound to Alt+<key>. */\n accessKey?: string\n /** Menu contents; see Menu's MenuItemSpec[]. */\n items: MenuItemSpec[]\n /** Renders a disabled, unopenable word. */\n disabled?: boolean\n}\n\n/** Props of {@link MenuBar}. */\nexport interface MenuBarProps extends HTMLAttributes<HTMLDivElement> {\n /** Top-level words, in order. */\n items: MenuBarItemSpec[]\n /** Layout direction. Menu bars are horizontal; vertical exists for side panes. */\n orientation?: 'horizontal' | 'vertical'\n /** Called with the open item's id, or undefined when everything closed. */\n onOpenChange?: (openId: string | undefined) => void\n}\n\n/** A strip of top-level words, each opening a {@link Menu} — `role=\"menubar\"`.\n *\n * One documented departure from the VGUI original: the resting word colour is\n * `--vgui-text-strong` rather than `TitleDimText` (`#889180`), which is only\n * 2.31:1 on a `GreenBG` panel. The state ladder is therefore two steps,\n * white → maize, instead of dim → white → maize.\n */\nexport const MenuBar = forwardRef<HTMLDivElement, MenuBarProps>(function MenuBar(\n { items, orientation = 'horizontal', onOpenChange, className, ...rest },\n ref,\n) {\n const baseId = useId()\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([])\n // Set by the capture handler, so the Tab-close path does not pull focus back.\n const isTabKey = useRef(false)\n\n const [openId, setOpenId] = useState<string | undefined>(undefined)\n const [activeIndex, setActiveIndex] = useState(0)\n\n const horizontal = orientation !== 'vertical'\n const openKey = horizontal ? 'ArrowDown' : 'ArrowRight'\n const moveKeys = horizontal ? ['ArrowLeft', 'ArrowRight'] : ['ArrowUp', 'ArrowDown']\n\n const firstEnabled = items.findIndex((item) => !item.disabled)\n const current =\n items[activeIndex] && !items[activeIndex].disabled ? activeIndex : firstEnabled\n const openIndex = items.findIndex((item) => item.id === openId)\n\n const openWord = (index: number) => {\n const spec = items[index]\n\n if (!spec || spec.disabled) return\n\n setActiveIndex(index)\n\n // A word with no rows cannot open; moving onto it closes what was open.\n if (spec.items.length === 0) {\n setOpenId(undefined)\n onOpenChange?.(undefined)\n return\n }\n\n setOpenId(spec.id)\n onOpenChange?.(spec.id)\n }\n\n const closeMenu = (index: number) => {\n setOpenId(undefined)\n onOpenChange?.(undefined)\n\n // Escape and activation hand focus back to the word the menu hung off.\n // Tab must not: the user asked to leave.\n if (!isTabKey.current) itemRefs.current[index]?.focus()\n }\n\n const moveWord = (delta: number) => {\n const enabled = items\n .map((item, index) => ({ item, index }))\n .filter(({ item }) => !item.disabled)\n\n if (enabled.length === 0) return\n\n const position = enabled.findIndex(({ index }) => index === current)\n const next = enabled[(position === -1 ? 0 : position + delta + enabled.length) % enabled.length]\n\n setActiveIndex(next.index)\n itemRefs.current[next.index]?.focus()\n\n // Menu switching: while a menu is open, arrowing to a sibling opens its menu.\n if (openId !== undefined) openWord(next.index)\n }\n\n const onKeyDownCapture = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n isTabKey.current = event.key === 'Tab'\n }\n\n const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const { key } = event\n const index = openIndex === -1 ? current : openIndex\n\n if (key === openKey) {\n // While a menu is open the Menu itself consumes Up/Down, so nothing here.\n if (openId === undefined) openWord(index)\n else return\n } else if (moveKeys.includes(key)) {\n moveWord(key === moveKeys[0] ? -1 : 1)\n } else if (key === 'Escape') {\n if (openId === undefined) return\n closeMenu(index)\n } else if ((key === 'Enter' || key === ' ') && openId === undefined) {\n openWord(index)\n } else {\n return\n }\n\n event.preventDefault()\n }\n\n return (\n <div\n {...rest}\n ref={ref}\n role=\"menubar\"\n aria-orientation={orientation}\n className={[\n 'vgui-menubar',\n // The spec names the root `.vgui-menubar`; the library's kebab-case\n // convention would make it `.vgui-menu-bar`. Both are applied.\n 'vgui-menu-bar',\n horizontal ? null : 'vgui-menubar--vertical',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n onKeyDownCapture={onKeyDownCapture}\n onKeyDown={onKeyDown}\n >\n {items.map((spec, index) => {\n const isOpen = openId === spec.id\n const wordId = `${baseId}-menubar-item-${spec.id}`\n const menuId = `${baseId}-menu-${spec.id}`\n\n return (\n <div key={spec.id} className=\"vgui-menubar__group\">\n <button\n id={wordId}\n ref={(node) => {\n itemRefs.current[index] = node\n }}\n type=\"button\"\n role=\"menuitem\"\n className=\"vgui-menubar__item\"\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n aria-controls={menuId}\n accessKey={spec.accessKey}\n disabled={spec.disabled}\n tabIndex={current === index ? 0 : -1}\n onFocus={() => setActiveIndex(index)}\n onClick={() => {\n if (isOpen) closeMenu(index)\n else openWord(index)\n }}\n onMouseEnter={() => {\n // \"Menu switching\": a menu already open follows the pointer.\n if (openId !== undefined && !isOpen) openWord(index)\n }}\n >\n {spec.label}\n </button>\n {isOpen ? (\n <Menu\n id={menuId}\n className=\"vgui-menubar__menu\"\n items={spec.items}\n labelledBy={wordId}\n orientation={horizontal ? 'auto' : 'vertical'}\n onClose={() => closeMenu(index)}\n />\n ) : null}\n </div>\n )\n })}\n </div>\n )\n})\n\nMenuBar.displayName = 'MenuBar'\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/MenuBar/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* NavBar — the client's application-level navigation strip and its dropdowns.
|
|
3
|
+
* Spec: docs/components/NavBar.md. Metrics, the bevel primitive and the a11y
|
|
4
|
+
* baseline come from docs/foundations.md.
|
|
5
|
+
*
|
|
6
|
+
* Every selector is written twice: the spec names the strip `.vgui-navbar*`,
|
|
7
|
+
* while the library's convention is `.vgui-<kebab-name>` → `.vgui-nav-bar*`.
|
|
8
|
+
* Both class names are applied to the elements and both selectors declared
|
|
9
|
+
* here, so either name is a supported hook.
|
|
10
|
+
*
|
|
11
|
+
* Three deliberate departures from the VGUI original, each an accessibility fix
|
|
12
|
+
* rather than a style choice:
|
|
13
|
+
* 1. the active item carries `aria-current` *and* a 1px underline, because
|
|
14
|
+
* `White` → `Text` is invisible to anyone with a colour deficiency;
|
|
15
|
+
* 2. `SuperNavMenuItem:selected {}` is empty in the corpus, so the maize
|
|
16
|
+
* highlight is promoted from `:hover` to selection and `:focus-visible`;
|
|
17
|
+
* 3. maize takes `Down` dark text — white is inherited in the corpus and
|
|
18
|
+
* lands at 3.70:1, a fail (foundations.md §10).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
.vgui-navbar,
|
|
22
|
+
.vgui-nav-bar {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
/* MainNav: font-size 21, font-weight 700, uppercase, bgcolor = none.
|
|
25
|
+
The strip paints nothing at all — it is not droppable on a white page. */
|
|
26
|
+
font-family: var(--vgui-font);
|
|
27
|
+
font-size: var(--vgui-nav-font-size, 21px);
|
|
28
|
+
font-weight: var(--vgui-nav-font-weight, 700);
|
|
29
|
+
line-height: var(--vgui-nav-line-height, 21px);
|
|
30
|
+
/* The original adds no tracking; 21px bold uppercase loses its shape cues
|
|
31
|
+
if letter-spacing is invented. It must stay normal. */
|
|
32
|
+
letter-spacing: normal;
|
|
33
|
+
text-transform: uppercase;
|
|
34
|
+
background: none;
|
|
35
|
+
border: 0;
|
|
36
|
+
color: var(--vgui-nav-text, var(--vgui-text-strong));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.vgui-navbar__list,
|
|
40
|
+
.vgui-nav-bar__list {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: stretch;
|
|
43
|
+
gap: 0;
|
|
44
|
+
margin: 0;
|
|
45
|
+
padding: 0;
|
|
46
|
+
list-style: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.vgui-navbar__item,
|
|
50
|
+
.vgui-nav-bar__item {
|
|
51
|
+
position: relative;
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: stretch;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.vgui-navbar__button,
|
|
57
|
+
.vgui-nav-bar__button {
|
|
58
|
+
display: block;
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
padding: 0 8px;
|
|
61
|
+
border: 0;
|
|
62
|
+
background: none;
|
|
63
|
+
color: var(--vgui-nav-text, var(--vgui-text-strong));
|
|
64
|
+
font: inherit;
|
|
65
|
+
line-height: var(--vgui-nav-line-height, 21px);
|
|
66
|
+
letter-spacing: inherit;
|
|
67
|
+
text-decoration: none;
|
|
68
|
+
text-transform: inherit;
|
|
69
|
+
/* VGUI navigation is not a link cursor. */
|
|
70
|
+
cursor: default;
|
|
71
|
+
/* No transition: VGUI has none. */
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* The active item is DIMMER, not brighter — do not invert this to match a
|
|
75
|
+
modern nav bar. The direction of emphasis is the style, and the underline
|
|
76
|
+
is what keeps the state visible without colour. */
|
|
77
|
+
.vgui-navbar__item--active > .vgui-navbar__button,
|
|
78
|
+
.vgui-nav-bar__item--active > .vgui-nav-bar__button {
|
|
79
|
+
color: var(--vgui-nav-text-active, var(--vgui-text-muted));
|
|
80
|
+
text-decoration: underline;
|
|
81
|
+
text-decoration-thickness: 1px;
|
|
82
|
+
text-underline-offset: 2px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* MainNav has no hover state; the port's invented `nav a:hover` yellow is
|
|
86
|
+
used, and it measures 5.52:1 on GreenBG. */
|
|
87
|
+
.vgui-navbar__button:hover:not(:disabled),
|
|
88
|
+
.vgui-nav-bar__button:hover:not([aria-disabled='true']) {
|
|
89
|
+
color: var(--vgui-text-hover);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* The strip is transparent, so the ring has to be drawn inside the label box
|
|
93
|
+
or a frame's parent can clip it — hence the negative offset token. */
|
|
94
|
+
.vgui-navbar__button:focus-visible,
|
|
95
|
+
.vgui-nav-bar__button:focus-visible {
|
|
96
|
+
outline: var(--vgui-focus-ring);
|
|
97
|
+
outline-offset: var(--vgui-focus-ring-offset);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.vgui-navbar__button:disabled,
|
|
101
|
+
.vgui-navbar__button[aria-disabled='true'],
|
|
102
|
+
.vgui-nav-bar__button:disabled,
|
|
103
|
+
.vgui-nav-bar__button[aria-disabled='true'] {
|
|
104
|
+
color: var(--vgui-text-disabled);
|
|
105
|
+
text-shadow: 1px 1px 0 var(--vgui-text-disabled-shadow);
|
|
106
|
+
text-decoration: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* The strip divider between items (`dividers`). Decorative: the original's
|
|
110
|
+
SuperDivider is 1.07:1 against the panel, so it is a hint, not information. */
|
|
111
|
+
.vgui-navbar__divider,
|
|
112
|
+
.vgui-nav-bar__divider {
|
|
113
|
+
align-self: stretch;
|
|
114
|
+
width: 1px;
|
|
115
|
+
margin: 0;
|
|
116
|
+
background-color: var(--vgui-menu-divider, var(--vgui-surface));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ---- Dropdown: SuperNavMenu, reusing Menu's geometry -------------------
|
|
120
|
+
The descendant prefix is load-bearing: the panel also carries `vgui-menu`,
|
|
121
|
+
whose container rule is the same specificity as a bare `.vgui-navbar__
|
|
122
|
+
dropdown`, so import order would otherwise decide who wins. */
|
|
123
|
+
|
|
124
|
+
.vgui-navbar .vgui-navbar__dropdown,
|
|
125
|
+
.vgui-nav-bar .vgui-nav-bar__dropdown {
|
|
126
|
+
position: absolute;
|
|
127
|
+
top: var(--vgui-nav-dropdown-offset, 23px);
|
|
128
|
+
left: 0;
|
|
129
|
+
z-index: 1;
|
|
130
|
+
min-width: var(--vgui-menu-min-width, 150px);
|
|
131
|
+
padding: 1px 0 1px 1px; /* inset = "1 0 1 1" */
|
|
132
|
+
background-color: var(--vgui-menu-bg, var(--vgui-surface-dark));
|
|
133
|
+
border: 1px solid var(--vgui-menu-border, var(--vgui-surface-light));
|
|
134
|
+
/* No drop shadow anywhere in the corpus: a flat outline on a barely lighter
|
|
135
|
+
panel is what makes this read 2003 rather than 2015. */
|
|
136
|
+
box-shadow: none;
|
|
137
|
+
/* Paint over the seam instead of shortening the box with `border-top: 0`,
|
|
138
|
+
which would jitter the strip by a pixel when the menu opens. */
|
|
139
|
+
margin-top: -1px;
|
|
140
|
+
/* The strip is 21px/700/uppercase; the rows are 14px body text. */
|
|
141
|
+
font-size: var(--vgui-font-size);
|
|
142
|
+
font-weight: 400;
|
|
143
|
+
letter-spacing: normal;
|
|
144
|
+
text-transform: none;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.vgui-navbar .vgui-navbar__dropdown .vgui-menu__item,
|
|
148
|
+
.vgui-nav-bar .vgui-nav-bar__dropdown .vgui-menu__item {
|
|
149
|
+
color: var(--vgui-nav-text, var(--vgui-text-strong));
|
|
150
|
+
line-height: var(--vgui-nav-line-height, 21px);
|
|
151
|
+
padding: 0 6px;
|
|
152
|
+
cursor: default;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Hover, roving focus and selection are one highlight. The corpus leaves
|
|
156
|
+
`:selected` empty, which leaves keyboard users navigating blind. */
|
|
157
|
+
.vgui-navbar .vgui-navbar__dropdown .vgui-menu__item:hover,
|
|
158
|
+
.vgui-navbar .vgui-navbar__dropdown .vgui-menu__item:focus-visible,
|
|
159
|
+
.vgui-navbar .vgui-navbar__dropdown .vgui-menu__item[data-active='true'],
|
|
160
|
+
.vgui-nav-bar .vgui-nav-bar__dropdown .vgui-menu__item:hover,
|
|
161
|
+
.vgui-nav-bar .vgui-nav-bar__dropdown .vgui-menu__item:focus-visible,
|
|
162
|
+
.vgui-nav-bar .vgui-nav-bar__dropdown .vgui-menu__item[data-active='true'] {
|
|
163
|
+
background-color: var(--vgui-accent-dark);
|
|
164
|
+
/* Dark on maize is 4.22:1; the corpus's inherited white is 3.70:1. */
|
|
165
|
+
color: var(--vgui-text-on-accent, var(--vgui-selection-text));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.vgui-navbar .vgui-navbar__dropdown .vgui-menu__separator,
|
|
169
|
+
.vgui-nav-bar .vgui-nav-bar__dropdown .vgui-menu__separator {
|
|
170
|
+
height: 1px;
|
|
171
|
+
margin: 1px 0;
|
|
172
|
+
background-color: var(--vgui-menu-divider, var(--vgui-surface));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.vgui-crisp .vgui-navbar__button:focus-visible,
|
|
176
|
+
.vgui-crisp .vgui-nav-bar__button:focus-visible {
|
|
177
|
+
outline: var(--vgui-focus-ring-valve);
|
|
178
|
+
outline-offset: var(--vgui-focus-ring-valve-offset);
|
|
179
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import './NavBar.css';
|
|
3
|
+
/** One top-level item in a {@link NavBar}. */
|
|
4
|
+
export interface NavBarItem {
|
|
5
|
+
/** Stable identity; also the React key. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Label text, rendered uppercase by CSS. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Renders the dimmed active state. */
|
|
10
|
+
active?: boolean;
|
|
11
|
+
/** Dropdown content; omit for a plain link with no menu. */
|
|
12
|
+
menu?: ReactNode;
|
|
13
|
+
/** Href when the item navigates rather than opening a menu. */
|
|
14
|
+
href?: string;
|
|
15
|
+
/** Renders disabled and unclickable. */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Props of {@link NavBar}. */
|
|
19
|
+
export interface NavBarProps extends HTMLAttributes<HTMLElement> {
|
|
20
|
+
/** Items in display order. */
|
|
21
|
+
items: NavBarItem[];
|
|
22
|
+
/** Called with the item's id when it is activated. */
|
|
23
|
+
onItemClick?: (id: string) => void;
|
|
24
|
+
/** Accessible name for the nav landmark. */
|
|
25
|
+
label?: string;
|
|
26
|
+
/** Renders a 1px SuperNavMenuDivider between items. */
|
|
27
|
+
dividers?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** The client's primary application navigation: a strip of large uppercase
|
|
30
|
+
* labels, each optionally opening a flat-outlined dropdown menu.
|
|
31
|
+
*
|
|
32
|
+
* Three documented departures from the VGUI original, all accessibility fixes:
|
|
33
|
+
* the active item carries `aria-current` and an underline as well as its
|
|
34
|
+
* (dimmer) colour; the dropdown's empty `:selected {}` block is replaced by the
|
|
35
|
+
* maize highlight; and maize surfaces take dark text instead of the inherited
|
|
36
|
+
* white, which is only 3.70:1.
|
|
37
|
+
*/
|
|
38
|
+
export declare const NavBar: import("react").ForwardRefExoticComponent<NavBarProps & import("react").RefAttributes<HTMLElement>>;
|
|
39
|
+
//# sourceMappingURL=NavBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavBar.d.ts","sourceRoot":"","sources":["../../../src/components/NavBar/NavBar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAuC,SAAS,EAAE,MAAM,OAAO,CAAA;AAE3F,OAAO,cAAc,CAAA;AAErB,8CAA8C;AAC9C,MAAM,WAAW,UAAU;IACzB,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,+BAA+B;AAC/B,MAAM,WAAW,WAAY,SAAQ,cAAc,CAAC,WAAW,CAAC;IAC9D,8BAA8B;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,sDAAsD;IACtD,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,qGAkPjB,CAAA"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Fragment, forwardRef, useEffect, useId, useRef, useState } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import "./NavBar.css";
|
|
4
|
+
//#region src/components/NavBar/NavBar.tsx
|
|
5
|
+
/** Everything a dropdown row can be. The dropdown is filled with arbitrary
|
|
6
|
+
* nodes, so its keyboard model works off this role set rather than props. */
|
|
7
|
+
var ROW_SELECTOR = "[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"]";
|
|
8
|
+
/** The client's primary application navigation: a strip of large uppercase
|
|
9
|
+
* labels, each optionally opening a flat-outlined dropdown menu.
|
|
10
|
+
*
|
|
11
|
+
* Three documented departures from the VGUI original, all accessibility fixes:
|
|
12
|
+
* the active item carries `aria-current` and an underline as well as its
|
|
13
|
+
* (dimmer) colour; the dropdown's empty `:selected {}` block is replaced by the
|
|
14
|
+
* maize highlight; and maize surfaces take dark text instead of the inherited
|
|
15
|
+
* white, which is only 3.70:1.
|
|
16
|
+
*/
|
|
17
|
+
var NavBar = forwardRef(function NavBar({ items, onItemClick, label = "Main", dividers = false, className, ...rest }, ref) {
|
|
18
|
+
const baseId = useId();
|
|
19
|
+
const triggers = useRef({});
|
|
20
|
+
const panels = useRef({});
|
|
21
|
+
/** Row to focus once a freshly opened dropdown has mounted; -1 means last. */
|
|
22
|
+
const focusOnOpen = useRef(0);
|
|
23
|
+
const [openId, setOpenId] = useState(null);
|
|
24
|
+
const triggerId = (id) => `${baseId}-navbar-trigger-${id}`;
|
|
25
|
+
const panelId = (id) => `${baseId}-navbar-menu-${id}`;
|
|
26
|
+
const rows = (id) => {
|
|
27
|
+
const panel = panels.current[id];
|
|
28
|
+
if (!panel) return [];
|
|
29
|
+
return Array.from(panel.querySelectorAll(ROW_SELECTOR)).filter((row) => !row.hasAttribute("disabled") && row.getAttribute("aria-disabled") !== "true");
|
|
30
|
+
};
|
|
31
|
+
const focusRow = (id, position) => {
|
|
32
|
+
const list = rows(id);
|
|
33
|
+
if (list.length === 0) return;
|
|
34
|
+
const row = list[position < 0 ? list.length - 1 : position % list.length];
|
|
35
|
+
if (!row.hasAttribute("tabindex")) row.setAttribute("tabindex", "-1");
|
|
36
|
+
row.focus();
|
|
37
|
+
};
|
|
38
|
+
const openMenu = (id, position = 0) => {
|
|
39
|
+
focusOnOpen.current = position;
|
|
40
|
+
setOpenId(id);
|
|
41
|
+
onItemClick?.(id);
|
|
42
|
+
};
|
|
43
|
+
const closeMenu = (id, restoreFocus) => {
|
|
44
|
+
setOpenId(null);
|
|
45
|
+
if (restoreFocus) triggers.current[id]?.focus();
|
|
46
|
+
};
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (openId === null) return;
|
|
49
|
+
focusRow(openId, focusOnOpen.current);
|
|
50
|
+
}, [openId]);
|
|
51
|
+
const enabled = items.filter((item) => !item.disabled);
|
|
52
|
+
const moveTopLevel = (fromId, delta) => {
|
|
53
|
+
if (enabled.length === 0) return;
|
|
54
|
+
const position = enabled.findIndex((item) => item.id === fromId);
|
|
55
|
+
const next = enabled[(position + delta + enabled.length) % enabled.length];
|
|
56
|
+
if (!next) return;
|
|
57
|
+
if (openId !== null && next.menu !== void 0) {
|
|
58
|
+
openMenu(next.id);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setOpenId(null);
|
|
62
|
+
triggers.current[next.id]?.focus();
|
|
63
|
+
};
|
|
64
|
+
const onListKeyDown = (event) => {
|
|
65
|
+
const { key } = event;
|
|
66
|
+
const focused = document.activeElement;
|
|
67
|
+
const owner = items.find((item) => {
|
|
68
|
+
const trigger = triggers.current[item.id];
|
|
69
|
+
const panel = panels.current[item.id];
|
|
70
|
+
return trigger !== null && trigger !== void 0 && trigger.contains(focused) || panel !== null && panel !== void 0 && panel.contains(focused);
|
|
71
|
+
});
|
|
72
|
+
if (!owner) return;
|
|
73
|
+
if (key === "ArrowLeft" || key === "ArrowRight") moveTopLevel(owner.id, key === "ArrowLeft" ? -1 : 1);
|
|
74
|
+
else if ((key === "ArrowDown" || key === "ArrowUp") && owner.menu !== void 0) {
|
|
75
|
+
const position = key === "ArrowDown" ? 0 : -1;
|
|
76
|
+
if (openId === owner.id) focusRow(owner.id, position);
|
|
77
|
+
else openMenu(owner.id, position);
|
|
78
|
+
} else return;
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
};
|
|
81
|
+
const onPanelKeyDown = (event, item) => {
|
|
82
|
+
const { key } = event;
|
|
83
|
+
if (key === "Tab") {
|
|
84
|
+
setOpenId(null);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const list = rows(item.id);
|
|
88
|
+
if (list.length === 0) return;
|
|
89
|
+
const position = list.indexOf(document.activeElement);
|
|
90
|
+
switch (key) {
|
|
91
|
+
case "ArrowDown":
|
|
92
|
+
focusRow(item.id, position + 1);
|
|
93
|
+
break;
|
|
94
|
+
case "ArrowUp":
|
|
95
|
+
focusRow(item.id, position - 1);
|
|
96
|
+
break;
|
|
97
|
+
case "Home":
|
|
98
|
+
focusRow(item.id, 0);
|
|
99
|
+
break;
|
|
100
|
+
case "End":
|
|
101
|
+
focusRow(item.id, -1);
|
|
102
|
+
break;
|
|
103
|
+
case "Escape":
|
|
104
|
+
closeMenu(item.id, true);
|
|
105
|
+
break;
|
|
106
|
+
default: return;
|
|
107
|
+
}
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
event.stopPropagation();
|
|
110
|
+
};
|
|
111
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
112
|
+
...rest,
|
|
113
|
+
ref,
|
|
114
|
+
"aria-label": label,
|
|
115
|
+
className: [
|
|
116
|
+
"vgui-navbar",
|
|
117
|
+
"vgui-nav-bar",
|
|
118
|
+
className
|
|
119
|
+
].filter(Boolean).join(" "),
|
|
120
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
121
|
+
className: "vgui-navbar__list vgui-nav-bar__list",
|
|
122
|
+
onKeyDown: onListKeyDown,
|
|
123
|
+
children: items.map((item, index) => {
|
|
124
|
+
const isOpen = openId === item.id;
|
|
125
|
+
const active = item.active === true;
|
|
126
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [dividers && index > 0 ? /* @__PURE__ */ jsx("li", {
|
|
127
|
+
className: "vgui-navbar__divider vgui-nav-bar__divider",
|
|
128
|
+
"aria-hidden": "true"
|
|
129
|
+
}) : null, /* @__PURE__ */ jsxs("li", {
|
|
130
|
+
className: [
|
|
131
|
+
"vgui-navbar__item",
|
|
132
|
+
"vgui-nav-bar__item",
|
|
133
|
+
active ? "vgui-navbar__item--active vgui-nav-bar__item--active" : null
|
|
134
|
+
].filter(Boolean).join(" "),
|
|
135
|
+
children: [item.href !== void 0 && item.menu === void 0 ? /* @__PURE__ */ jsx("a", {
|
|
136
|
+
id: triggerId(item.id),
|
|
137
|
+
ref: (node) => {
|
|
138
|
+
triggers.current[item.id] = node;
|
|
139
|
+
},
|
|
140
|
+
className: "vgui-navbar__button vgui-nav-bar__button",
|
|
141
|
+
href: item.disabled ? void 0 : item.href,
|
|
142
|
+
"aria-current": active ? "page" : void 0,
|
|
143
|
+
"aria-disabled": item.disabled ? true : void 0,
|
|
144
|
+
tabIndex: item.disabled ? -1 : void 0,
|
|
145
|
+
onClick: (event) => {
|
|
146
|
+
if (item.disabled) {
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
onItemClick?.(item.id);
|
|
151
|
+
},
|
|
152
|
+
children: item.label
|
|
153
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
154
|
+
id: triggerId(item.id),
|
|
155
|
+
ref: (node) => {
|
|
156
|
+
triggers.current[item.id] = node;
|
|
157
|
+
},
|
|
158
|
+
type: "button",
|
|
159
|
+
className: "vgui-navbar__button vgui-nav-bar__button",
|
|
160
|
+
"aria-current": active ? "page" : void 0,
|
|
161
|
+
"aria-haspopup": item.menu !== void 0 ? "menu" : void 0,
|
|
162
|
+
"aria-expanded": item.menu !== void 0 ? isOpen : void 0,
|
|
163
|
+
"aria-controls": item.menu !== void 0 ? panelId(item.id) : void 0,
|
|
164
|
+
disabled: item.disabled,
|
|
165
|
+
onClick: () => {
|
|
166
|
+
if (item.menu === void 0) {
|
|
167
|
+
onItemClick?.(item.id);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (isOpen) closeMenu(item.id, false);
|
|
171
|
+
else openMenu(item.id);
|
|
172
|
+
},
|
|
173
|
+
onMouseEnter: () => {
|
|
174
|
+
if (openId !== null && !isOpen && item.menu !== void 0) openMenu(item.id);
|
|
175
|
+
},
|
|
176
|
+
children: item.label
|
|
177
|
+
}), isOpen && item.menu !== void 0 ? /* @__PURE__ */ jsx("div", {
|
|
178
|
+
id: panelId(item.id),
|
|
179
|
+
ref: (node) => {
|
|
180
|
+
panels.current[item.id] = node;
|
|
181
|
+
},
|
|
182
|
+
role: "menu",
|
|
183
|
+
"aria-labelledby": triggerId(item.id),
|
|
184
|
+
"aria-orientation": "vertical",
|
|
185
|
+
className: "vgui-navbar__dropdown vgui-nav-bar__dropdown vgui-menu",
|
|
186
|
+
onKeyDown: (event) => onPanelKeyDown(event, item),
|
|
187
|
+
children: item.menu
|
|
188
|
+
}) : null]
|
|
189
|
+
})] }, item.id);
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
NavBar.displayName = "NavBar";
|
|
195
|
+
//#endregion
|
|
196
|
+
export { NavBar };
|
|
197
|
+
|
|
198
|
+
//# sourceMappingURL=NavBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavBar.js","names":[],"sources":["../../../src/components/NavBar/NavBar.tsx"],"sourcesContent":["import { Fragment, forwardRef, useEffect, useId, useRef, useState } from 'react'\nimport type { HTMLAttributes, KeyboardEvent as ReactKeyboardEvent, ReactNode } from 'react'\n\nimport './NavBar.css'\n\n/** One top-level item in a {@link NavBar}. */\nexport interface NavBarItem {\n /** Stable identity; also the React key. */\n id: string\n /** Label text, rendered uppercase by CSS. */\n label: string\n /** Renders the dimmed active state. */\n active?: boolean\n /** Dropdown content; omit for a plain link with no menu. */\n menu?: ReactNode\n /** Href when the item navigates rather than opening a menu. */\n href?: string\n /** Renders disabled and unclickable. */\n disabled?: boolean\n}\n\n/** Props of {@link NavBar}. */\nexport interface NavBarProps extends HTMLAttributes<HTMLElement> {\n /** Items in display order. */\n items: NavBarItem[]\n /** Called with the item's id when it is activated. */\n onItemClick?: (id: string) => void\n /** Accessible name for the nav landmark. */\n label?: string\n /** Renders a 1px SuperNavMenuDivider between items. */\n dividers?: boolean\n}\n\n/** Everything a dropdown row can be. The dropdown is filled with arbitrary\n * nodes, so its keyboard model works off this role set rather than props. */\nconst ROW_SELECTOR = '[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"]'\n\n/** The client's primary application navigation: a strip of large uppercase\n * labels, each optionally opening a flat-outlined dropdown menu.\n *\n * Three documented departures from the VGUI original, all accessibility fixes:\n * the active item carries `aria-current` and an underline as well as its\n * (dimmer) colour; the dropdown's empty `:selected {}` block is replaced by the\n * maize highlight; and maize surfaces take dark text instead of the inherited\n * white, which is only 3.70:1.\n */\nexport const NavBar = forwardRef<HTMLElement, NavBarProps>(function NavBar(\n { items, onItemClick, label = 'Main', dividers = false, className, ...rest },\n ref,\n) {\n const baseId = useId()\n const triggers = useRef<Record<string, HTMLElement | null>>({})\n const panels = useRef<Record<string, HTMLDivElement | null>>({})\n /** Row to focus once a freshly opened dropdown has mounted; -1 means last. */\n const focusOnOpen = useRef(0)\n\n const [openId, setOpenId] = useState<string | null>(null)\n\n const triggerId = (id: string) => `${baseId}-navbar-trigger-${id}`\n const panelId = (id: string) => `${baseId}-navbar-menu-${id}`\n\n const rows = (id: string) => {\n const panel = panels.current[id]\n if (!panel) return []\n return Array.from(panel.querySelectorAll<HTMLElement>(ROW_SELECTOR)).filter(\n (row) => !row.hasAttribute('disabled') && row.getAttribute('aria-disabled') !== 'true',\n )\n }\n\n const focusRow = (id: string, position: number) => {\n const list = rows(id)\n if (list.length === 0) return\n\n const row = list[position < 0 ? list.length - 1 : position % list.length]\n // `menu` is arbitrary markup, so a plain element may need the attribute.\n if (!row.hasAttribute('tabindex')) row.setAttribute('tabindex', '-1')\n row.focus()\n }\n\n const openMenu = (id: string, position = 0) => {\n focusOnOpen.current = position\n setOpenId(id)\n onItemClick?.(id)\n }\n\n const closeMenu = (id: string, restoreFocus: boolean) => {\n setOpenId(null)\n if (restoreFocus) triggers.current[id]?.focus()\n }\n\n useEffect(() => {\n if (openId === null) return\n focusRow(openId, focusOnOpen.current)\n }, [openId])\n\n const enabled = items.filter((item) => !item.disabled)\n\n const moveTopLevel = (fromId: string, delta: number) => {\n if (enabled.length === 0) return\n\n const position = enabled.findIndex((item) => item.id === fromId)\n const next = enabled[(position + delta + enabled.length) % enabled.length]\n if (!next) return\n\n // Menu switching: with a dropdown open, arrowing to a sibling opens its own.\n if (openId !== null && next.menu !== undefined) {\n openMenu(next.id)\n return\n }\n\n setOpenId(null)\n triggers.current[next.id]?.focus()\n }\n\n const onListKeyDown = (event: ReactKeyboardEvent<HTMLUListElement>) => {\n const { key } = event\n const focused = document.activeElement as HTMLElement | null\n const owner = items.find((item) => {\n const trigger = triggers.current[item.id]\n const panel = panels.current[item.id]\n return (\n (trigger !== null && trigger !== undefined && trigger.contains(focused)) ||\n (panel !== null && panel !== undefined && panel.contains(focused))\n )\n })\n\n if (!owner) return\n\n if (key === 'ArrowLeft' || key === 'ArrowRight') {\n moveTopLevel(owner.id, key === 'ArrowLeft' ? -1 : 1)\n } else if ((key === 'ArrowDown' || key === 'ArrowUp') && owner.menu !== undefined) {\n const position = key === 'ArrowDown' ? 0 : -1\n if (openId === owner.id) focusRow(owner.id, position)\n else openMenu(owner.id, position)\n } else {\n // Enter, Space and Tab belong to the focused control.\n return\n }\n\n event.preventDefault()\n }\n\n const onPanelKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>, item: NavBarItem) => {\n const { key } = event\n\n if (key === 'Tab') {\n // Tab closes the dropdown without trapping focus.\n setOpenId(null)\n return\n }\n\n const list = rows(item.id)\n if (list.length === 0) return\n\n const position = list.indexOf(document.activeElement as HTMLElement)\n\n switch (key) {\n case 'ArrowDown':\n focusRow(item.id, position + 1)\n break\n case 'ArrowUp':\n focusRow(item.id, position - 1)\n break\n case 'Home':\n focusRow(item.id, 0)\n break\n case 'End':\n focusRow(item.id, -1)\n break\n case 'Escape':\n // Escape hands focus back to the button that opened the dropdown.\n closeMenu(item.id, true)\n break\n default:\n // Arrow Left/Right are the strip's, and bubbles up to it.\n return\n }\n\n event.preventDefault()\n event.stopPropagation()\n }\n\n return (\n <nav\n {...rest}\n ref={ref}\n aria-label={label}\n className={[\n 'vgui-navbar',\n // The spec names the root `.vgui-navbar`; the library's kebab-case\n // convention would make it `.vgui-nav-bar`. Both are applied.\n 'vgui-nav-bar',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <ul className=\"vgui-navbar__list vgui-nav-bar__list\" onKeyDown={onListKeyDown}>\n {items.map((item, index) => {\n const isOpen = openId === item.id\n const active = item.active === true\n\n return (\n <Fragment key={item.id}>\n {dividers && index > 0 ? (\n <li className=\"vgui-navbar__divider vgui-nav-bar__divider\" aria-hidden=\"true\" />\n ) : null}\n <li\n className={[\n 'vgui-navbar__item',\n 'vgui-nav-bar__item',\n active ? 'vgui-navbar__item--active vgui-nav-bar__item--active' : null,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {item.href !== undefined && item.menu === undefined ? (\n <a\n id={triggerId(item.id)}\n ref={(node) => {\n triggers.current[item.id] = node\n }}\n className=\"vgui-navbar__button vgui-nav-bar__button\"\n href={item.disabled ? undefined : item.href}\n aria-current={active ? 'page' : undefined}\n aria-disabled={item.disabled ? true : undefined}\n tabIndex={item.disabled ? -1 : undefined}\n onClick={(event) => {\n if (item.disabled) {\n event.preventDefault()\n return\n }\n onItemClick?.(item.id)\n }}\n >\n {item.label}\n </a>\n ) : (\n <button\n id={triggerId(item.id)}\n ref={(node) => {\n triggers.current[item.id] = node\n }}\n type=\"button\"\n className=\"vgui-navbar__button vgui-nav-bar__button\"\n aria-current={active ? 'page' : undefined}\n aria-haspopup={item.menu !== undefined ? 'menu' : undefined}\n aria-expanded={item.menu !== undefined ? isOpen : undefined}\n aria-controls={item.menu !== undefined ? panelId(item.id) : undefined}\n disabled={item.disabled}\n onClick={() => {\n if (item.menu === undefined) {\n onItemClick?.(item.id)\n return\n }\n if (isOpen) closeMenu(item.id, false)\n else openMenu(item.id)\n }}\n onMouseEnter={() => {\n // Menu switching: a dropdown already open follows the pointer.\n if (openId !== null && !isOpen && item.menu !== undefined) openMenu(item.id)\n }}\n >\n {item.label}\n </button>\n )}\n {isOpen && item.menu !== undefined ? (\n <div\n id={panelId(item.id)}\n ref={(node) => {\n panels.current[item.id] = node\n }}\n role=\"menu\"\n aria-labelledby={triggerId(item.id)}\n aria-orientation=\"vertical\"\n className=\"vgui-navbar__dropdown vgui-nav-bar__dropdown vgui-menu\"\n onKeyDown={(event) => onPanelKeyDown(event, item)}\n >\n {item.menu}\n </div>\n ) : null}\n </li>\n </Fragment>\n )\n })}\n </ul>\n </nav>\n )\n})\n\nNavBar.displayName = 'NavBar'\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/NavBar/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA"}
|