diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Button.css | 3 | ||||
| -rw-r--r-- | src/components/ButtonMenu.css | 33 | ||||
| -rw-r--r-- | src/components/ButtonMenu.tsx | 42 | ||||
| -rw-r--r-- | src/components/ContainedList.css | 1 | ||||
| -rw-r--r-- | src/components/Menu.css | 15 | ||||
| -rw-r--r-- | src/components/Menu.tsx | 20 | ||||
| -rw-r--r-- | src/components/MenuItem.css | 6 | ||||
| -rw-r--r-- | src/components/MenuItem.tsx | 17 | ||||
| -rw-r--r-- | src/components/TextInput.css | 3 | ||||
| -rw-r--r-- | src/components/Toolbar.css | 24 | ||||
| -rw-r--r-- | src/components/Toolbar.tsx | 23 | ||||
| -rw-r--r-- | src/components/index.ts | 14 |
12 files changed, 193 insertions, 8 deletions
diff --git a/src/components/Button.css b/src/components/Button.css index 210d131..595f053 100644 --- a/src/components/Button.css +++ b/src/components/Button.css @@ -2,7 +2,7 @@ :scope { width: 100%; min-width: fit-content; - height: 3rem; + height: var(--button-height, 2.5rem); padding: 0 1rem; /* Necessary for <a> */ @@ -26,6 +26,7 @@ &:focus { outline: 2px solid var(--primary); + outline-offset: -2px; border-color: var(--base); background-color: var(--button-focus, var(--button-base)); color: var(--button-focus-text, var(--button-text)); diff --git a/src/components/ButtonMenu.css b/src/components/ButtonMenu.css new file mode 100644 index 0000000..c454849 --- /dev/null +++ b/src/components/ButtonMenu.css @@ -0,0 +1,33 @@ +@scope (.__ButtonMenu) { + :scope { + > button { + position: relative; + padding-left: 2.5rem; + + &::before { + content: ''; + width: 1rem; + height: 1rem; + left: 0.75rem; + position: absolute; + background: url('../icons/chevron-right.svg'); + background-position: left 2px center; + background-repeat: no-repeat; + transition: transform 0.2s; + } + &:has(+ :popover-open)::before { + transform: rotate(90deg); + } + } + + [popover] { + inset: unset; + top: 0; + left: var(--anchor-x); + + &:popover-open { + top: var(--anchor-y); + } + } + } +}
\ No newline at end of file diff --git a/src/components/ButtonMenu.tsx b/src/components/ButtonMenu.tsx new file mode 100644 index 0000000..6a2b73e --- /dev/null +++ b/src/components/ButtonMenu.tsx @@ -0,0 +1,42 @@ +import type { ComponentChildren } from 'preact'; +import { useEffect, useRef, useId } from 'preact/hooks'; +import { useSignal, batch } from '@preact/signals'; +import Button from './Button.tsx'; +import Menu from './Menu.tsx'; +import './ButtonMenu.css'; + +export interface ButtonMenuProps { + children: ComponentChildren; + label: string; +} + +const ButtonMenu = ({ children, label }: ButtonMenuProps) => { + const id = useId(); + const ref = useRef(null); + const x = useSignal(0); + const y = useSignal(0); + + const updateRect = () => batch(() => { + const rect = ref.current?.getBoundingClientRect(); + if (!rect) return; + x.value = rect.x; + y.value = rect.y + rect.height + 1; + }); + useEffect(updateRect, [ref.current]); + + return ( + <div + class="__ButtonMenu" + ref={ref} + onScroll={updateRect} + style={`--anchor-x: ${x}px; --anchor-y: ${y}px;`} + > + <Button kind="ghost" popoverTarget={id}>{label}</Button> + <Menu id={id} popover="auto"> + {children} + </Menu> + </div> + ); +}; + +export default ButtonMenu; diff --git a/src/components/ContainedList.css b/src/components/ContainedList.css index 97c98cc..4cdf2f5 100644 --- a/src/components/ContainedList.css +++ b/src/components/ContainedList.css @@ -12,6 +12,7 @@ border-bottom: var(--border); } height: 3rem; + --button-height: 100%; display: flex; > * { flex-grow: 1; diff --git a/src/components/Menu.css b/src/components/Menu.css new file mode 100644 index 0000000..b999704 --- /dev/null +++ b/src/components/Menu.css @@ -0,0 +1,15 @@ +@scope (.__Menu) { + :scope { + padding: 0; + width: 15rem; + list-style: none; + background-color: var(--surface); + border: none; /* popover */ + + --shadow-color: 0deg 0% 0%; + box-shadow: + 0.1px 0.4px 0.6px hsl(var(--shadow-color) / 0.04), + 0.5px 3.3px 4.8px -0.2px hsl(var(--shadow-color) / 0.15), + 1.4px 10.2px 14.7px -0.5px hsl(var(--shadow-color) / 0.27); + } +}
\ No newline at end of file diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx new file mode 100644 index 0000000..e8d37cc --- /dev/null +++ b/src/components/Menu.tsx @@ -0,0 +1,20 @@ +import type { ComponentChildren } from 'preact'; +import './Menu.css'; + +export interface MenuProps { + children: ComponentChildren; + id?: string; + popover?: string; +} + +const Menu = ({ id, popover, children }: MenuProps) => { + return ( + <> + <menu id={id} class="__Menu" popover={popover}> + {children} + </menu> + </> + ); +}; + +export default Menu; diff --git a/src/components/MenuItem.css b/src/components/MenuItem.css new file mode 100644 index 0000000..14a156e --- /dev/null +++ b/src/components/MenuItem.css @@ -0,0 +1,6 @@ +@scope (.__MenuItem) { + :scope { + --button-height: 2rem; + --surface: var(--overlay); + } +}
\ No newline at end of file diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx new file mode 100644 index 0000000..04c04c7 --- /dev/null +++ b/src/components/MenuItem.tsx @@ -0,0 +1,17 @@ +import Button from './Button.tsx'; +import './MenuItem.css'; + +export interface MenuItemProps { + label: string; + onClick?: (event: PointerEvent) => void; +} + +const MenuItem = ({ label, onClick }: MenuItemProps) => { + return ( + <li class="__MenuItem"> + <Button kind="ghost" onClick={onClick}>{label}</Button> + </li> + ); +}; + +export default MenuItem;
\ No newline at end of file diff --git a/src/components/TextInput.css b/src/components/TextInput.css index bb04415..08ab76b 100644 --- a/src/components/TextInput.css +++ b/src/components/TextInput.css @@ -10,8 +10,7 @@ &:focus { outline: 2px solid var(--primary); - border-bottom: none; - margin-bottom: 2px; + outline-offset: -2px; } } }
\ No newline at end of file diff --git a/src/components/Toolbar.css b/src/components/Toolbar.css new file mode 100644 index 0000000..02477ea --- /dev/null +++ b/src/components/Toolbar.css @@ -0,0 +1,24 @@ +@scope (.__Toolbar) { + :scope { + > * { + flex-grow: 1; + display: flex; + justify-content: center; + } + &:first-child, &:last-child { flex-basis: 0 } + &:first-child { justify-content: flex-start } + &:last-child { justify-content: flex-end } + + .actions { + margin: 0; + padding: 0; + list-style: none; + display: flex; + } + + .title { + font-size: 1.5rem; + width: fit-content; + } + } +}
\ No newline at end of file diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx new file mode 100644 index 0000000..1c985c1 --- /dev/null +++ b/src/components/Toolbar.tsx @@ -0,0 +1,23 @@ +import type { ComponentChildren } from 'preact'; +import Header from './Header.tsx'; +import Button from './Button.tsx'; +import './Toolbar.css'; + +export interface ToolbarProps { + children: ComponentChildren; + title: string; +} + +const Toolbar = ({ children, title }: ToolbarProps) => { + return ( + <Header class="__Toolbar"> + <menu class="actions"> + {children} + </menu> + <Button kind="ghost" class="title">{title}</Button> + <div></div> + </Header> + ); +}; + +export default Toolbar;
\ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index 1beca4d..c9c3625 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,8 +1,12 @@ -export { default as Header } from './Header.tsx'; -export { default as TextInput } from './TextInput.tsx'; -export { default as Button } from './Button.tsx'; export { default as ArrowButton } from './ArrowButton.tsx'; +export { default as Button } from './Button.tsx'; +export { default as ButtonMenu } from './ButtonMenu.tsx'; export { default as ContainedList } from './ContainedList.tsx'; -export { default as FormLabel } from './FormLabel.tsx'; export { default as Content } from './Content.tsx'; -export { default as Form } from './Form.tsx';
\ No newline at end of file +export { default as Form } from './Form.tsx'; +export { default as FormLabel } from './FormLabel.tsx'; +export { default as Header } from './Header.tsx'; +export { default as Menu } from './Menu.tsx'; +export { default as MenuItem } from './MenuItem.tsx'; +export { default as TextInput } from './TextInput.tsx'; +export { default as Toolbar } from './Toolbar.tsx'; |
