-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add one more level to header dropdown (#107)
* Add popout menu choice to sidebar, default to without it. * Sidebar was mislabeled * Update to use react-icons/fa to match previous, * Add popout menu documentation and use it in the sidebar page * Add example without popout menu. Fix container to not have "fluid" in example * Fix badge text to match example (remove fluid) * Create Dropdown Component (undocumented yet) for use with sidebar mobile. Use tailwindui+tailwindcss/forms * Expose ref for usacebox (ref is not an attribute) * Create custom hook for determining if in mobile state * Bring everything together in the previous commits to dynamically change the sidebar to turn into a dropdown when below the `md` breakpoint used for hiding sidebar. Do this in such a way that end users do not have to update their source. (i.e. use javascript to target parent elements) * # Make it so nested links are more obvious in the dropdown on mobile. Add extra children to example links for sidebar docs. Create flattenlinks utility to recursively place all links in the same parent array and provide a path/level for them. * Formatting * Add nesting to popout menus and sidebar without popout. * Limit popout nesting to 2 menus + 1 (with errors) * Add id attribute for #94 * Add one more level in the dropdown for the header
- Loading branch information
Showing
15 changed files
with
870 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; | ||
import { useEffect, useState } from "react"; | ||
import { | ||
VscChevronRight, | ||
VscChevronLeft, | ||
VscChevronUp, | ||
VscChevronDown, | ||
} from "react-icons/vsc"; | ||
|
||
const AVAIL_DIRECTIONS = ["right", "left", "top", "bottom"]; | ||
|
||
function PopoutMenu({ title, children, direction = "right", className }) { | ||
direction = direction.toLowerCase(); | ||
|
||
const [isOpen, setIsOpen] = useState(false); | ||
|
||
// TODO: This does not handle clicking other popout buttons | ||
function handleClickOutside() { | ||
// Handle when a user clicks outside the popout menu | ||
setIsOpen(false); | ||
} | ||
|
||
useEffect(() => { | ||
if (isOpen) { | ||
document.addEventListener("click", handleClickOutside); | ||
} else { | ||
document.removeEventListener("click", handleClickOutside); | ||
} | ||
|
||
return () => { | ||
document.removeEventListener("click", handleClickOutside); | ||
}; | ||
}, [isOpen]); | ||
|
||
if (!AVAIL_DIRECTIONS.includes(direction)) { | ||
throw new Error( | ||
`Invalid direction in component <Popover direction="${direction}". Valid directions are: ${AVAIL_DIRECTIONS.join( | ||
", " | ||
)}` | ||
); | ||
} | ||
|
||
// Map direction to position classes | ||
const directionClasses = { | ||
right: | ||
"gw-left-full gw-top-1/2 gw-transform gw--translate-y-1/2 gw-translate-x-2", | ||
left: "gw-right-full gw-top-1/2 gw-transform gw--translate-y-1/2 gw--translate-x-2", | ||
top: "gw-bottom-full gw-left-1/2 gw-transform gw--translate-x-1/2 gw--translate-y-2", | ||
bottom: | ||
"gw-top-full gw-left-1/2 gw-transform gw--translate-x-1/2 gw-translate-y-2", | ||
}; | ||
|
||
/// Map the icons to their appropriate directions | ||
const ChevronIcon = { | ||
right: VscChevronRight, | ||
left: VscChevronLeft, | ||
top: VscChevronUp, | ||
bottom: VscChevronDown, | ||
}[direction]; | ||
|
||
// Flip the direction for closure | ||
const ChevronIconOpposite = { | ||
right: VscChevronLeft, | ||
left: VscChevronRight, | ||
top: VscChevronDown, | ||
bottom: VscChevronUp, | ||
}[direction]; | ||
|
||
return ( | ||
<Popover | ||
name="gw-popout-menu" | ||
className={`gw-relative gw-cursor-not-allowed gw-select-none ${className}`} | ||
onClose={() => setIsOpen(false)} | ||
> | ||
<PopoverButton | ||
className="gw-inline-flex gw-w-full gw-items-center gw-justify-between gw-text-sm gw-leading-6 gw-ps-1 focus:gw-outline-none" | ||
onClick={() => { | ||
setIsOpen(!isOpen); | ||
}} | ||
> | ||
<span>{title}</span> | ||
{isOpen ? ( | ||
<ChevronIconOpposite | ||
aria-hidden="true" | ||
className="gw-h-5 gw-w-5" | ||
size={16} | ||
/> | ||
) : ( | ||
<ChevronIcon aria-hidden="true" className="gw-h-5 gw-w-5" size={12} /> | ||
)} | ||
</PopoverButton> | ||
|
||
<PopoverPanel | ||
transition="true" | ||
className={`gw-absolute gw-ms-2 gw-pt-1 ${directionClasses[direction]} gw-z-10 gw-mt-2 gw-w-56 gw-shrink gw-rounded-xl gw-bg-white gw-text-sm gw-leading-6 gw-text-gray-900 gw-shadow-lg gw-ring-1 gw-ring-gray-900/5`} | ||
> | ||
{children} | ||
</PopoverPanel> | ||
</Popover> | ||
); | ||
} | ||
|
||
export default PopoutMenu; | ||
export { PopoutMenu }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { useState } from "react"; | ||
|
||
export default function Dropdown({ | ||
label, | ||
options, | ||
labelClassName, | ||
className, | ||
...props | ||
}) { | ||
const [selectedValue, setSelectedValue] = useState(options[0].props.value); | ||
if (!options || options.length === 0) { | ||
throw new Error(`Dropdown ${label} must have at least one option`); | ||
} | ||
return ( | ||
<> | ||
<label | ||
htmlFor={label + "-id"} | ||
className={`block text-sm font-medium leading-6 text-gray-900 ${labelClassName}`} | ||
> | ||
{label} | ||
</label> | ||
<select | ||
id={label ? label + "-id" : undefined} | ||
name={label ? label + "-name" : undefined} | ||
onChange={(e) => { | ||
setSelectedValue(e.target.value); | ||
props.onChange(e); | ||
}} | ||
className={`mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 ${className}`} | ||
value={selectedValue || options[0].props.value} | ||
{...props} | ||
> | ||
{options} | ||
</select> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.