@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,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Menu — the dropdown panel and its rows.
|
|
3
|
+
* Spec: docs/components/Menu.md. Metrics, bevels and the a11y baseline come
|
|
4
|
+
* from docs/foundations.md.
|
|
5
|
+
*
|
|
6
|
+
* The border is a flat 1px `LightGreenBG` outline on all four sides, not a
|
|
7
|
+
* bevel, and there is deliberately no box-shadow: nothing in steam.styles
|
|
8
|
+
* draws one, and a modern elevation shadow breaks the period look.
|
|
9
|
+
* `inset = "1 0 1 1"` becomes `padding: 0 1px 1px` so the hover fill never
|
|
10
|
+
* paints over the outline.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.vgui-menu {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
min-width: var(--vgui-menu-min-width, 150px);
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0 1px 1px;
|
|
20
|
+
background-color: var(--vgui-menu-bg, var(--vgui-surface-dark));
|
|
21
|
+
border: 1px solid var(--vgui-menu-border, var(--vgui-surface-light));
|
|
22
|
+
color: var(--vgui-text-strong);
|
|
23
|
+
font-family: var(--vgui-font);
|
|
24
|
+
font-size: var(--vgui-font-size);
|
|
25
|
+
line-height: var(--vgui-line-height);
|
|
26
|
+
/* No box-shadow. See docs/components/Menu.md, "Four things to extract". */
|
|
27
|
+
box-shadow: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Submenu levels are painted adjacent to their parent row and overlap its
|
|
31
|
+
outline by a pixel rather than having the seam removed with `border-*: 0`,
|
|
32
|
+
which would shorten the box. See docs/components/Tabs.md. */
|
|
33
|
+
.vgui-menu--submenu {
|
|
34
|
+
position: absolute;
|
|
35
|
+
top: -1px;
|
|
36
|
+
left: calc(100% - 1px);
|
|
37
|
+
z-index: 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.vgui-menu__row {
|
|
41
|
+
position: relative;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.vgui-menu__item {
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 4px;
|
|
51
|
+
width: 100%;
|
|
52
|
+
min-height: var(--vgui-menu-item-height, 24px);
|
|
53
|
+
padding: 2px 8px 2px 4px;
|
|
54
|
+
border: 0;
|
|
55
|
+
background: transparent;
|
|
56
|
+
color: var(--vgui-text-strong);
|
|
57
|
+
font-family: inherit;
|
|
58
|
+
font-size: var(--vgui-font-size);
|
|
59
|
+
text-align: left;
|
|
60
|
+
/* VGUI menus are not link cursors. */
|
|
61
|
+
cursor: default;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Hover and focus-visible are the same state for a mouse and a keyboard user. */
|
|
65
|
+
.vgui-menu__item:hover,
|
|
66
|
+
.vgui-menu__item:focus-visible {
|
|
67
|
+
background-color: var(--vgui-accent-dark);
|
|
68
|
+
/* 4.22:1. White on MaizeBG is 3.70:1 and fails; see foundations.md §10. */
|
|
69
|
+
color: var(--vgui-selection-text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.vgui-menu__item:focus-visible {
|
|
73
|
+
outline: var(--vgui-focus-ring);
|
|
74
|
+
outline-offset: var(--vgui-focus-ring-offset);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.vgui-menu__item:disabled {
|
|
78
|
+
color: var(--vgui-text-disabled);
|
|
79
|
+
text-shadow: 1px 1px 0 var(--vgui-text-disabled-shadow);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* The gutter keeps checked and unchecked rows' text aligned. */
|
|
83
|
+
.vgui-menu__gutter {
|
|
84
|
+
flex: 0 0 16px;
|
|
85
|
+
text-align: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.vgui-menu__text {
|
|
89
|
+
flex: 1 1 auto;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.vgui-menu__arrow {
|
|
93
|
+
flex: 0 0 auto;
|
|
94
|
+
width: 0;
|
|
95
|
+
height: 0;
|
|
96
|
+
border-top: 4px solid transparent;
|
|
97
|
+
border-bottom: 4px solid transparent;
|
|
98
|
+
border-left: 5px solid currentColor;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.vgui-menu__separator {
|
|
102
|
+
height: 1px;
|
|
103
|
+
/* `padding-right = 3`: the rule stops short of the right edge. */
|
|
104
|
+
margin: 1px 3px 1px 0;
|
|
105
|
+
/* `MenuDivider` `#4C4E4B` — a documented token this library does not define
|
|
106
|
+
yet, so it falls back to the nearest existing surface tone. It is
|
|
107
|
+
decorative either way: around 1.03:1 against the panel. */
|
|
108
|
+
background-color: var(--vgui-menu-divider, var(--vgui-surface));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.vgui-menu__label {
|
|
112
|
+
padding: 4px 8px 2px 4px;
|
|
113
|
+
color: var(--vgui-text-muted);
|
|
114
|
+
font-size: var(--vgui-font-size-small);
|
|
115
|
+
letter-spacing: var(--vgui-chrome-tracking);
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.vgui-crisp .vgui-menu__item:focus-visible {
|
|
120
|
+
outline: var(--vgui-focus-ring-valve);
|
|
121
|
+
outline-offset: var(--vgui-focus-ring-valve-offset);
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import './Menu.css';
|
|
3
|
+
/** One row in a {@link Menu}. Separators and labels are entries in the same array. */
|
|
4
|
+
export interface MenuItemSpec {
|
|
5
|
+
/** Stable identity and React key. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Visible label. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Invoked on activation. Omit to render a non-interactive row. */
|
|
10
|
+
onSelect?: () => void;
|
|
11
|
+
/** Renders as a disabled row. */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Renders as `menuitemcheckbox` with a tick in the gutter. */
|
|
14
|
+
checked?: boolean;
|
|
15
|
+
/** Renders an arrow and opens a nested Menu. */
|
|
16
|
+
items?: MenuItemSpec[];
|
|
17
|
+
/** Renders a group label instead of an action row. */
|
|
18
|
+
type?: 'item' | 'label' | 'separator';
|
|
19
|
+
}
|
|
20
|
+
/** Props of {@link Menu}. */
|
|
21
|
+
export interface MenuProps extends HTMLAttributes<HTMLDivElement> {
|
|
22
|
+
/** Rows, in order. Separators and labels are entries in the same array. */
|
|
23
|
+
items: MenuItemSpec[];
|
|
24
|
+
/** id of the element the menu is anchored to; becomes `aria-labelledby`. */
|
|
25
|
+
labelledBy?: string;
|
|
26
|
+
/** Called when the menu should close. */
|
|
27
|
+
onClose?: () => void;
|
|
28
|
+
/** Keyboard behaviour. Defaults to `'auto'`: reaches for a MenuBar ancestor. */
|
|
29
|
+
orientation?: 'auto' | 'vertical';
|
|
30
|
+
}
|
|
31
|
+
/** Props of {@link MenuItem}. */
|
|
32
|
+
export interface MenuItemProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onSelect'> {
|
|
33
|
+
/** Visible row text. */
|
|
34
|
+
label: ReactNode;
|
|
35
|
+
/** Renders as `menuitemcheckbox` and puts a tick in the gutter. */
|
|
36
|
+
checked?: boolean;
|
|
37
|
+
/** Renders the right-aligned arrow and `aria-haspopup="menu"`. */
|
|
38
|
+
hasSubmenu?: boolean;
|
|
39
|
+
/** Invoked on activation, before the menu closes. */
|
|
40
|
+
onSelect?: () => void;
|
|
41
|
+
}
|
|
42
|
+
/** One row of a menu. Always a real `<button>` so activation, `disabled` and the focus ring come for free. */
|
|
43
|
+
export declare const MenuItem: import("react").ForwardRefExoticComponent<MenuItemProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
44
|
+
/** The dropdown panel: a flat-outlined list of {@link MenuItem} rows. */
|
|
45
|
+
export declare const Menu: import("react").ForwardRefExoticComponent<MenuProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
46
|
+
//# sourceMappingURL=Menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../../../src/components/Menu/Menu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EAEd,SAAS,EAEV,MAAM,OAAO,CAAA;AAEd,OAAO,YAAY,CAAA;AAEnB,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gDAAgD;IAChD,KAAK,CAAC,EAAE,YAAY,EAAE,CAAA;IACtB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAA;CACtC;AAED,6BAA6B;AAC7B,MAAM,WAAW,SAAU,SAAQ,cAAc,CAAC,cAAc,CAAC;IAC/D,2EAA2E;IAC3E,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAClC;AAED,iCAAiC;AACjC,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAC9F,wBAAwB;IACxB,KAAK,EAAE,SAAS,CAAA;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kEAAkE;IAClE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,8GAA8G;AAC9G,eAAO,MAAM,QAAQ,6GAqCnB,CAAA;AAwQF,yEAAyE;AACzE,eAAO,MAAM,IAAI,sGAcf,CAAA"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useId, useRef, useState } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import "./Menu.css";
|
|
4
|
+
//#region src/components/Menu/Menu.tsx
|
|
5
|
+
/** One row of a menu. Always a real `<button>` so activation, `disabled` and the focus ring come for free. */
|
|
6
|
+
var MenuItem = forwardRef(function MenuItem({ label, checked, hasSubmenu, onSelect, onClick, className, disabled, ...rest }, ref) {
|
|
7
|
+
const checkable = checked !== void 0;
|
|
8
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
9
|
+
...rest,
|
|
10
|
+
ref,
|
|
11
|
+
type: "button",
|
|
12
|
+
role: checkable ? "menuitemcheckbox" : "menuitem",
|
|
13
|
+
"aria-checked": checkable ? checked : void 0,
|
|
14
|
+
"aria-haspopup": hasSubmenu ? "menu" : void 0,
|
|
15
|
+
"aria-expanded": hasSubmenu ? rest["aria-expanded"] : void 0,
|
|
16
|
+
disabled,
|
|
17
|
+
tabIndex: rest.tabIndex ?? -1,
|
|
18
|
+
className: [
|
|
19
|
+
"vgui-menu__item",
|
|
20
|
+
hasSubmenu ? "vgui-menu__item--has-submenu" : null,
|
|
21
|
+
className
|
|
22
|
+
].filter(Boolean).join(" "),
|
|
23
|
+
onClick: (event) => {
|
|
24
|
+
onClick?.(event);
|
|
25
|
+
if (event.defaultPrevented || disabled) return;
|
|
26
|
+
onSelect?.();
|
|
27
|
+
},
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsx("span", {
|
|
30
|
+
className: "vgui-menu__gutter",
|
|
31
|
+
"aria-hidden": checkable ? true : void 0,
|
|
32
|
+
children: checkable && checked ? "✓" : null
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ jsx("span", {
|
|
35
|
+
className: "vgui-menu__text",
|
|
36
|
+
children: label
|
|
37
|
+
}),
|
|
38
|
+
hasSubmenu ? /* @__PURE__ */ jsx("span", {
|
|
39
|
+
className: "vgui-menu__arrow",
|
|
40
|
+
"aria-hidden": "true"
|
|
41
|
+
}) : null
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
MenuItem.displayName = "MenuItem";
|
|
46
|
+
/** How long the pointer may leave a submenu parent before the submenu closes. */
|
|
47
|
+
var SUBMENU_CLOSE_DELAY = 150;
|
|
48
|
+
/** How long a typeahead buffer survives between keystrokes. */
|
|
49
|
+
var TYPEAHEAD_TIMEOUT = 500;
|
|
50
|
+
var isAction = (spec) => spec !== void 0 && (spec.type ?? "item") === "item";
|
|
51
|
+
var isFocusable = (spec) => isAction(spec) && !spec.disabled;
|
|
52
|
+
function MenuLevel({ items, labelledBy, onClose, orientation = "auto", isSubmenu = false, forwardedRef, ...rest }) {
|
|
53
|
+
const itemRefs = useRef([]);
|
|
54
|
+
const closeTimer = useRef(null);
|
|
55
|
+
const typeahead = useRef({
|
|
56
|
+
text: "",
|
|
57
|
+
at: 0
|
|
58
|
+
});
|
|
59
|
+
const baseId = useId();
|
|
60
|
+
const focusable = items.map((spec, index) => ({
|
|
61
|
+
spec,
|
|
62
|
+
index
|
|
63
|
+
})).filter(({ spec }) => isFocusable(spec));
|
|
64
|
+
const [activeIndex, setActiveIndex] = useState(() => focusable[0]?.index ?? -1);
|
|
65
|
+
const [openSubmenuId, setOpenSubmenuId] = useState(null);
|
|
66
|
+
const firstIndex = focusable[0]?.index ?? -1;
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (firstIndex !== -1) itemRefs.current[firstIndex]?.focus();
|
|
69
|
+
}, [firstIndex]);
|
|
70
|
+
useEffect(() => () => {
|
|
71
|
+
if (closeTimer.current) clearTimeout(closeTimer.current);
|
|
72
|
+
}, []);
|
|
73
|
+
const cancelClose = () => {
|
|
74
|
+
if (closeTimer.current) {
|
|
75
|
+
clearTimeout(closeTimer.current);
|
|
76
|
+
closeTimer.current = null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const focusItem = (index) => {
|
|
80
|
+
setActiveIndex(index);
|
|
81
|
+
itemRefs.current[index]?.focus();
|
|
82
|
+
};
|
|
83
|
+
const move = (delta) => {
|
|
84
|
+
if (focusable.length === 0) return;
|
|
85
|
+
const position = focusable.findIndex(({ index }) => index === activeIndex);
|
|
86
|
+
const next = position === -1 ? delta > 0 ? 0 : focusable.length - 1 : (position + delta + focusable.length) % focusable.length;
|
|
87
|
+
focusItem(focusable[next].index);
|
|
88
|
+
};
|
|
89
|
+
const jump = (position) => {
|
|
90
|
+
const target = focusable[position];
|
|
91
|
+
if (target) focusItem(target.index);
|
|
92
|
+
};
|
|
93
|
+
const typeaheadMove = (key) => {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const state = typeahead.current;
|
|
96
|
+
state.text = now - state.at > TYPEAHEAD_TIMEOUT ? key.toLowerCase() : state.text + key.toLowerCase();
|
|
97
|
+
state.at = now;
|
|
98
|
+
const position = focusable.findIndex(({ index }) => index === activeIndex);
|
|
99
|
+
const ordered = [...focusable.slice(position + 1), ...focusable.slice(0, position + 1)];
|
|
100
|
+
let match = ordered.find(({ spec }) => spec.label.toLowerCase().startsWith(state.text));
|
|
101
|
+
if (!match && state.text.length > 1) {
|
|
102
|
+
state.text = key.toLowerCase();
|
|
103
|
+
match = ordered.find(({ spec }) => spec.label.toLowerCase().startsWith(state.text));
|
|
104
|
+
}
|
|
105
|
+
if (match) focusItem(match.index);
|
|
106
|
+
};
|
|
107
|
+
const closeSubmenu = (index) => {
|
|
108
|
+
cancelClose();
|
|
109
|
+
setOpenSubmenuId(null);
|
|
110
|
+
itemRefs.current[index]?.focus();
|
|
111
|
+
};
|
|
112
|
+
const onKeyDown = (event) => {
|
|
113
|
+
const { key } = event;
|
|
114
|
+
if (key === "Tab") {
|
|
115
|
+
onClose?.();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (key === "Enter" || key === " ") {
|
|
119
|
+
event.stopPropagation();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
switch (key) {
|
|
123
|
+
case "ArrowDown":
|
|
124
|
+
move(1);
|
|
125
|
+
break;
|
|
126
|
+
case "ArrowUp":
|
|
127
|
+
move(-1);
|
|
128
|
+
break;
|
|
129
|
+
case "Home":
|
|
130
|
+
jump(0);
|
|
131
|
+
break;
|
|
132
|
+
case "End":
|
|
133
|
+
jump(focusable.length - 1);
|
|
134
|
+
break;
|
|
135
|
+
case "Escape":
|
|
136
|
+
onClose?.();
|
|
137
|
+
break;
|
|
138
|
+
case "ArrowRight": {
|
|
139
|
+
const spec = items[activeIndex];
|
|
140
|
+
if (spec?.items?.length) {
|
|
141
|
+
setOpenSubmenuId(spec.id);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
case "ArrowLeft":
|
|
147
|
+
if (isSubmenu) {
|
|
148
|
+
onClose?.();
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
default:
|
|
153
|
+
if (key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey) return;
|
|
154
|
+
typeaheadMove(key);
|
|
155
|
+
}
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
event.stopPropagation();
|
|
158
|
+
};
|
|
159
|
+
return /* @__PURE__ */ jsx("div", {
|
|
160
|
+
...rest,
|
|
161
|
+
ref: forwardedRef,
|
|
162
|
+
role: "menu",
|
|
163
|
+
"aria-labelledby": labelledBy,
|
|
164
|
+
"aria-orientation": "vertical",
|
|
165
|
+
className: [
|
|
166
|
+
"vgui-menu",
|
|
167
|
+
isSubmenu ? "vgui-menu--submenu" : null,
|
|
168
|
+
rest.className
|
|
169
|
+
].filter(Boolean).join(" "),
|
|
170
|
+
onKeyDown,
|
|
171
|
+
"data-orientation": orientation,
|
|
172
|
+
children: items.map((spec, index) => {
|
|
173
|
+
const type = spec.type ?? "item";
|
|
174
|
+
if (type === "separator") return /* @__PURE__ */ jsx("div", {
|
|
175
|
+
className: "vgui-menu__separator",
|
|
176
|
+
role: "separator"
|
|
177
|
+
}, spec.id);
|
|
178
|
+
if (type === "label") return /* @__PURE__ */ jsx("div", {
|
|
179
|
+
className: "vgui-menu__label",
|
|
180
|
+
role: "presentation",
|
|
181
|
+
children: spec.label
|
|
182
|
+
}, spec.id);
|
|
183
|
+
const submenuItems = spec.items ?? [];
|
|
184
|
+
const hasSubmenu = submenuItems.length > 0;
|
|
185
|
+
const rowId = `${baseId}-item-${spec.id}`;
|
|
186
|
+
const row = /* @__PURE__ */ jsx(MenuItem, {
|
|
187
|
+
id: rowId,
|
|
188
|
+
ref: (node) => {
|
|
189
|
+
itemRefs.current[index] = node;
|
|
190
|
+
},
|
|
191
|
+
label: spec.label,
|
|
192
|
+
checked: spec.checked,
|
|
193
|
+
hasSubmenu,
|
|
194
|
+
"aria-expanded": hasSubmenu ? openSubmenuId === spec.id : void 0,
|
|
195
|
+
"aria-controls": hasSubmenu ? `${baseId}-submenu-${spec.id}` : void 0,
|
|
196
|
+
disabled: spec.disabled || !hasSubmenu && !spec.onSelect,
|
|
197
|
+
tabIndex: activeIndex === index ? 0 : -1,
|
|
198
|
+
onClick: hasSubmenu ? () => {
|
|
199
|
+
cancelClose();
|
|
200
|
+
setOpenSubmenuId(spec.id);
|
|
201
|
+
} : void 0,
|
|
202
|
+
onSelect: () => {
|
|
203
|
+
if (hasSubmenu) return;
|
|
204
|
+
spec.onSelect?.();
|
|
205
|
+
onClose?.();
|
|
206
|
+
}
|
|
207
|
+
}, hasSubmenu ? void 0 : spec.id);
|
|
208
|
+
if (!hasSubmenu) return row;
|
|
209
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
210
|
+
className: "vgui-menu__row",
|
|
211
|
+
onMouseEnter: () => {
|
|
212
|
+
cancelClose();
|
|
213
|
+
setOpenSubmenuId(spec.id);
|
|
214
|
+
},
|
|
215
|
+
onMouseLeave: () => {
|
|
216
|
+
cancelClose();
|
|
217
|
+
closeTimer.current = setTimeout(() => setOpenSubmenuId(null), SUBMENU_CLOSE_DELAY);
|
|
218
|
+
},
|
|
219
|
+
children: [row, openSubmenuId === spec.id ? /* @__PURE__ */ jsx(MenuLevel, {
|
|
220
|
+
id: `${baseId}-submenu-${spec.id}`,
|
|
221
|
+
items: submenuItems,
|
|
222
|
+
labelledBy: rowId,
|
|
223
|
+
isSubmenu: true,
|
|
224
|
+
orientation,
|
|
225
|
+
onClose: () => closeSubmenu(index)
|
|
226
|
+
}) : null]
|
|
227
|
+
}, spec.id);
|
|
228
|
+
})
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** The dropdown panel: a flat-outlined list of {@link MenuItem} rows. */
|
|
232
|
+
var Menu = forwardRef(function Menu({ items, labelledBy, onClose, orientation = "auto", ...rest }, ref) {
|
|
233
|
+
return /* @__PURE__ */ jsx(MenuLevel, {
|
|
234
|
+
...rest,
|
|
235
|
+
items,
|
|
236
|
+
labelledBy,
|
|
237
|
+
onClose,
|
|
238
|
+
orientation,
|
|
239
|
+
forwardedRef: ref
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
Menu.displayName = "Menu";
|
|
243
|
+
//#endregion
|
|
244
|
+
export { Menu, MenuItem };
|
|
245
|
+
|
|
246
|
+
//# sourceMappingURL=Menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Menu.js","names":[],"sources":["../../../src/components/Menu/Menu.tsx"],"sourcesContent":["import { forwardRef, useEffect, useId, useRef, useState } from 'react'\nimport type {\n ButtonHTMLAttributes,\n HTMLAttributes,\n KeyboardEvent as ReactKeyboardEvent,\n ReactNode,\n Ref,\n} from 'react'\n\nimport './Menu.css'\n\n/** One row in a {@link Menu}. Separators and labels are entries in the same array. */\nexport interface MenuItemSpec {\n /** Stable identity and React key. */\n id: string\n /** Visible label. */\n label: string\n /** Invoked on activation. Omit to render a non-interactive row. */\n onSelect?: () => void\n /** Renders as a disabled row. */\n disabled?: boolean\n /** Renders as `menuitemcheckbox` with a tick in the gutter. */\n checked?: boolean\n /** Renders an arrow and opens a nested Menu. */\n items?: MenuItemSpec[]\n /** Renders a group label instead of an action row. */\n type?: 'item' | 'label' | 'separator'\n}\n\n/** Props of {@link Menu}. */\nexport interface MenuProps extends HTMLAttributes<HTMLDivElement> {\n /** Rows, in order. Separators and labels are entries in the same array. */\n items: MenuItemSpec[]\n /** id of the element the menu is anchored to; becomes `aria-labelledby`. */\n labelledBy?: string\n /** Called when the menu should close. */\n onClose?: () => void\n /** Keyboard behaviour. Defaults to `'auto'`: reaches for a MenuBar ancestor. */\n orientation?: 'auto' | 'vertical'\n}\n\n/** Props of {@link MenuItem}. */\nexport interface MenuItemProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onSelect'> {\n /** Visible row text. */\n label: ReactNode\n /** Renders as `menuitemcheckbox` and puts a tick in the gutter. */\n checked?: boolean\n /** Renders the right-aligned arrow and `aria-haspopup=\"menu\"`. */\n hasSubmenu?: boolean\n /** Invoked on activation, before the menu closes. */\n onSelect?: () => void\n}\n\n/** One row of a menu. Always a real `<button>` so activation, `disabled` and the focus ring come for free. */\nexport const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(function MenuItem(\n { label, checked, hasSubmenu, onSelect, onClick, className, disabled, ...rest },\n ref,\n) {\n const checkable = checked !== undefined\n\n return (\n <button\n {...rest}\n ref={ref}\n type=\"button\"\n role={checkable ? 'menuitemcheckbox' : 'menuitem'}\n aria-checked={checkable ? checked : undefined}\n aria-haspopup={hasSubmenu ? 'menu' : undefined}\n aria-expanded={hasSubmenu ? rest['aria-expanded'] : undefined}\n disabled={disabled}\n tabIndex={rest.tabIndex ?? -1}\n className={[\n 'vgui-menu__item',\n hasSubmenu ? 'vgui-menu__item--has-submenu' : null,\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n onClick={(event) => {\n onClick?.(event)\n if (event.defaultPrevented || disabled) return\n onSelect?.()\n }}\n >\n <span className=\"vgui-menu__gutter\" aria-hidden={checkable ? true : undefined}>\n {checkable && checked ? '✓' : null}\n </span>\n <span className=\"vgui-menu__text\">{label}</span>\n {hasSubmenu ? <span className=\"vgui-menu__arrow\" aria-hidden=\"true\" /> : null}\n </button>\n )\n})\n\nMenuItem.displayName = 'MenuItem'\n\n/** How long the pointer may leave a submenu parent before the submenu closes. */\nconst SUBMENU_CLOSE_DELAY = 150\n\n/** How long a typeahead buffer survives between keystrokes. */\nconst TYPEAHEAD_TIMEOUT = 500\n\nconst isAction = (spec: MenuItemSpec | undefined): spec is MenuItemSpec =>\n spec !== undefined && (spec.type ?? 'item') === 'item'\n\nconst isFocusable = (spec: MenuItemSpec) => isAction(spec) && !spec.disabled\n\ninterface MenuLevelProps extends HTMLAttributes<HTMLDivElement> {\n items: MenuItemSpec[]\n labelledBy?: string\n onClose?: () => void\n orientation?: 'auto' | 'vertical'\n isSubmenu?: boolean\n forwardedRef?: Ref<HTMLDivElement>\n}\n\nfunction MenuLevel({\n items,\n labelledBy,\n onClose,\n orientation = 'auto',\n isSubmenu = false,\n forwardedRef,\n ...rest\n}: MenuLevelProps) {\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([])\n const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const typeahead = useRef({ text: '', at: 0 })\n const baseId = useId()\n\n const focusable = items\n .map((spec, index) => ({ spec, index }))\n .filter(({ spec }) => isFocusable(spec))\n\n const [activeIndex, setActiveIndex] = useState(() => focusable[0]?.index ?? -1)\n const [openSubmenuId, setOpenSubmenuId] = useState<string | null>(null)\n\n // A menu is a composite widget: opening it moves focus into it.\n const firstIndex = focusable[0]?.index ?? -1\n useEffect(() => {\n if (firstIndex !== -1) itemRefs.current[firstIndex]?.focus()\n }, [firstIndex])\n\n useEffect(\n () => () => {\n if (closeTimer.current) clearTimeout(closeTimer.current)\n },\n [],\n )\n\n const cancelClose = () => {\n if (closeTimer.current) {\n clearTimeout(closeTimer.current)\n closeTimer.current = null\n }\n }\n\n const focusItem = (index: number) => {\n setActiveIndex(index)\n itemRefs.current[index]?.focus()\n }\n\n const move = (delta: number) => {\n if (focusable.length === 0) return\n const position = focusable.findIndex(({ index }) => index === activeIndex)\n const next =\n position === -1\n ? delta > 0\n ? 0\n : focusable.length - 1\n : (position + delta + focusable.length) % focusable.length\n focusItem(focusable[next].index)\n }\n\n const jump = (position: number) => {\n const target = focusable[position]\n if (target) focusItem(target.index)\n }\n\n const typeaheadMove = (key: string) => {\n const now = Date.now()\n const state = typeahead.current\n state.text = now - state.at > TYPEAHEAD_TIMEOUT ? key.toLowerCase() : state.text + key.toLowerCase()\n state.at = now\n\n const position = focusable.findIndex(({ index }) => index === activeIndex)\n const ordered = [...focusable.slice(position + 1), ...focusable.slice(0, position + 1)]\n let match = ordered.find(({ spec }) => spec.label.toLowerCase().startsWith(state.text))\n\n if (!match && state.text.length > 1) {\n state.text = key.toLowerCase()\n match = ordered.find(({ spec }) => spec.label.toLowerCase().startsWith(state.text))\n }\n\n if (match) focusItem(match.index)\n }\n\n const closeSubmenu = (index: number) => {\n cancelClose()\n setOpenSubmenuId(null)\n itemRefs.current[index]?.focus()\n }\n\n const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const { key } = event\n\n if (key === 'Tab') {\n // Tab closes the menu without trapping focus.\n onClose?.()\n return\n }\n\n if (key === 'Enter' || key === ' ') {\n // The row is a real <button>, so activation is native. Claim the event\n // first so an ancestor menu bar does not also act on it.\n event.stopPropagation()\n return\n }\n\n switch (key) {\n case 'ArrowDown':\n move(1)\n break\n case 'ArrowUp':\n move(-1)\n break\n case 'Home':\n jump(0)\n break\n case 'End':\n jump(focusable.length - 1)\n break\n case 'Escape':\n onClose?.()\n break\n case 'ArrowRight': {\n const spec = items[activeIndex]\n if (spec?.items?.length) {\n setOpenSubmenuId(spec.id)\n break\n }\n // No submenu here: a MenuBar ancestor claims Right.\n return\n }\n case 'ArrowLeft': {\n if (isSubmenu) {\n onClose?.()\n break\n }\n // `orientation=\"auto\"` hands Left/Right to the MenuBar ancestor.\n return\n }\n default:\n if (key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey) return\n typeaheadMove(key)\n }\n\n event.preventDefault()\n event.stopPropagation()\n }\n\n return (\n <div\n {...rest}\n ref={forwardedRef}\n role=\"menu\"\n aria-labelledby={labelledBy}\n aria-orientation=\"vertical\"\n className={['vgui-menu', isSubmenu ? 'vgui-menu--submenu' : null, rest.className]\n .filter(Boolean)\n .join(' ')}\n onKeyDown={onKeyDown}\n data-orientation={orientation}\n >\n {items.map((spec, index) => {\n const type = spec.type ?? 'item'\n\n if (type === 'separator') {\n return <div key={spec.id} className=\"vgui-menu__separator\" role=\"separator\" />\n }\n\n if (type === 'label') {\n return (\n <div key={spec.id} className=\"vgui-menu__label\" role=\"presentation\">\n {spec.label}\n </div>\n )\n }\n\n const submenuItems = spec.items ?? []\n const hasSubmenu = submenuItems.length > 0\n const rowId = `${baseId}-item-${spec.id}`\n\n const row = (\n <MenuItem\n key={hasSubmenu ? undefined : spec.id}\n id={rowId}\n ref={(node) => {\n itemRefs.current[index] = node\n }}\n label={spec.label}\n checked={spec.checked}\n hasSubmenu={hasSubmenu}\n aria-expanded={hasSubmenu ? openSubmenuId === spec.id : undefined}\n aria-controls={hasSubmenu ? `${baseId}-submenu-${spec.id}` : undefined}\n disabled={spec.disabled || (!hasSubmenu && !spec.onSelect)}\n tabIndex={activeIndex === index ? 0 : -1}\n onClick={\n hasSubmenu\n ? () => {\n cancelClose()\n setOpenSubmenuId(spec.id)\n }\n : undefined\n }\n onSelect={() => {\n if (hasSubmenu) return\n spec.onSelect?.()\n onClose?.()\n }}\n />\n )\n\n if (!hasSubmenu) return row\n\n return (\n <div\n key={spec.id}\n className=\"vgui-menu__row\"\n onMouseEnter={() => {\n cancelClose()\n setOpenSubmenuId(spec.id)\n }}\n onMouseLeave={() => {\n cancelClose()\n closeTimer.current = setTimeout(() => setOpenSubmenuId(null), SUBMENU_CLOSE_DELAY)\n }}\n >\n {row}\n {openSubmenuId === spec.id ? (\n <MenuLevel\n id={`${baseId}-submenu-${spec.id}`}\n items={submenuItems}\n labelledBy={rowId}\n isSubmenu\n orientation={orientation}\n onClose={() => closeSubmenu(index)}\n />\n ) : null}\n </div>\n )\n })}\n </div>\n )\n}\n\n/** The dropdown panel: a flat-outlined list of {@link MenuItem} rows. */\nexport const Menu = forwardRef<HTMLDivElement, MenuProps>(function Menu(\n { items, labelledBy, onClose, orientation = 'auto', ...rest },\n ref,\n) {\n return (\n <MenuLevel\n {...rest}\n items={items}\n labelledBy={labelledBy}\n onClose={onClose}\n orientation={orientation}\n forwardedRef={ref}\n />\n )\n})\n\nMenu.displayName = 'Menu'\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Menu/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AACvC,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MenuBar — the top-level word strip and the dropdowns it opens.
|
|
3
|
+
* Spec: docs/components/MenuBar.md.
|
|
4
|
+
*
|
|
5
|
+
* `MenuBar {}` and `"Page MenuBar" {}` are empty blocks in steam.styles, so the
|
|
6
|
+
* strip deliberately carries no fill, no border and no height of its own: it
|
|
7
|
+
* sits directly on the window body.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.vgui-menubar,
|
|
11
|
+
.vgui-menu-bar {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: stretch;
|
|
15
|
+
gap: 0;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
/* Nothing to reproduce: the strip has no chrome. */
|
|
19
|
+
background: none;
|
|
20
|
+
border: 0;
|
|
21
|
+
color: var(--vgui-text-strong);
|
|
22
|
+
font-family: var(--vgui-font);
|
|
23
|
+
font-size: var(--vgui-font-size);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.vgui-menubar--vertical {
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
align-items: stretch;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.vgui-menubar__group {
|
|
32
|
+
position: relative;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: stretch;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.vgui-menubar__item {
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
/* padding-right = -2 in VGUI has no CSS equivalent, so the words are simply
|
|
40
|
+
packed tightly. The hit area is not shrunk by a negative margin. */
|
|
41
|
+
padding: 2px 4px 2px 2px;
|
|
42
|
+
border: 0;
|
|
43
|
+
background: transparent;
|
|
44
|
+
/* Departure: `TitleDimText` (#889180) is 2.31:1 on a GreenBG panel, so the
|
|
45
|
+
documented "focused window" colour is the resting colour and the ladder is
|
|
46
|
+
white -> maize. Recorded in the component's Storybook notes. */
|
|
47
|
+
color: var(--vgui-text-strong);
|
|
48
|
+
font: inherit;
|
|
49
|
+
font-size: var(--vgui-font-size);
|
|
50
|
+
/* Load-bearing: the hit area is the full height of the strip. */
|
|
51
|
+
line-height: var(--vgui-menubar-height, 24px);
|
|
52
|
+
cursor: default;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* "Focused, hover" in the states table is white on transparent — the original
|
|
56
|
+
never fills — so hover only has to hold the resting colour. */
|
|
57
|
+
.vgui-menubar__item:hover {
|
|
58
|
+
color: var(--vgui-text-strong);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* `Over`: maize text. steam.styles gives the open item no fill at all; the
|
|
62
|
+
library adds the CSS port's `--vgui-surface-light` so the state reads. */
|
|
63
|
+
.vgui-menubar__item[aria-expanded='true'] {
|
|
64
|
+
color: var(--vgui-heading);
|
|
65
|
+
background-color: var(--vgui-surface-light);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.vgui-menubar__item:focus-visible {
|
|
69
|
+
outline: var(--vgui-focus-ring);
|
|
70
|
+
outline-offset: var(--vgui-focus-ring-offset);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.vgui-menubar__item:disabled {
|
|
74
|
+
color: var(--vgui-text-disabled);
|
|
75
|
+
text-shadow: 1px 1px 0 var(--vgui-text-disabled-shadow);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.vgui-menubar--vertical .vgui-menubar__item {
|
|
79
|
+
padding: 2px 8px 2px 4px;
|
|
80
|
+
text-align: left;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* The dropdown hangs off its word. */
|
|
84
|
+
.vgui-menubar__menu {
|
|
85
|
+
position: absolute;
|
|
86
|
+
top: 100%;
|
|
87
|
+
left: 0;
|
|
88
|
+
z-index: 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.vgui-crisp .vgui-menubar__item:focus-visible {
|
|
92
|
+
outline: var(--vgui-focus-ring-valve);
|
|
93
|
+
outline-offset: var(--vgui-focus-ring-valve-offset);
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
import type { MenuItemSpec } from '../Menu/Menu';
|
|
3
|
+
import './MenuBar.css';
|
|
4
|
+
/** One top-level word in a {@link MenuBar}. */
|
|
5
|
+
export interface MenuBarItemSpec {
|
|
6
|
+
/** Stable identity and React key. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The visible word, e.g. "File". */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Access key, announced and bound to Alt+<key>. */
|
|
11
|
+
accessKey?: string;
|
|
12
|
+
/** Menu contents; see Menu's MenuItemSpec[]. */
|
|
13
|
+
items: MenuItemSpec[];
|
|
14
|
+
/** Renders a disabled, unopenable word. */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Props of {@link MenuBar}. */
|
|
18
|
+
export interface MenuBarProps extends HTMLAttributes<HTMLDivElement> {
|
|
19
|
+
/** Top-level words, in order. */
|
|
20
|
+
items: MenuBarItemSpec[];
|
|
21
|
+
/** Layout direction. Menu bars are horizontal; vertical exists for side panes. */
|
|
22
|
+
orientation?: 'horizontal' | 'vertical';
|
|
23
|
+
/** Called with the open item's id, or undefined when everything closed. */
|
|
24
|
+
onOpenChange?: (openId: string | undefined) => void;
|
|
25
|
+
}
|
|
26
|
+
/** A strip of top-level words, each opening a {@link Menu} — `role="menubar"`.
|
|
27
|
+
*
|
|
28
|
+
* One documented departure from the VGUI original: the resting word colour is
|
|
29
|
+
* `--vgui-text-strong` rather than `TitleDimText` (`#889180`), which is only
|
|
30
|
+
* 2.31:1 on a `GreenBG` panel. The state ladder is therefore two steps,
|
|
31
|
+
* white → maize, instead of dim → white → maize.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MenuBar: import("react").ForwardRefExoticComponent<MenuBarProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
34
|
+
//# sourceMappingURL=MenuBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MenuBar.d.ts","sourceRoot":"","sources":["../../../src/components/MenuBar/MenuBar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAuC,MAAM,OAAO,CAAA;AAGhF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,OAAO,eAAe,CAAA;AAEtB,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gDAAgD;IAChD,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,gCAAgC;AAChC,MAAM,WAAW,YAAa,SAAQ,cAAc,CAAC,cAAc,CAAC;IAClE,iCAAiC;IACjC,KAAK,EAAE,eAAe,EAAE,CAAA;IACxB,kFAAkF;IAClF,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAA;CACpD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,yGA8JlB,CAAA"}
|