From a1913f15431486f2530905b7df74127976b5fba9 Mon Sep 17 00:00:00 2001 From: Igor Bari Date: Fri, 3 May 2024 19:09:12 +0000 Subject: [PATCH] add avatar with dropdown menu --- src/Avatar/Avatar.stories.ts | 36 +++++++++ src/Avatar/Avatar.ts | 26 +++++++ src/Docs.mdx | 15 ++++ src/DropdownMenu/DropdownMenu.stories.ts | 25 ++++++ src/DropdownMenu/DropdownMenu.ts | 88 ++++++++++++++++++++++ src/Header/Header.stories.ts | 7 +- src/Header/Header.ts | 2 +- src/Heading/Heading.ts | 2 +- src/Main/Main.stories.ts | 3 + src/Main/Main.ts | 3 +- src/Notifications/Notifications.stories.ts | 5 +- src/Popover/Popover.stories.ts | 20 +++++ src/Popover/Popover.ts | 83 ++++++++++++++++++++ src/index.ts | 5 +- 14 files changed, 310 insertions(+), 10 deletions(-) create mode 100644 src/Avatar/Avatar.stories.ts create mode 100644 src/Avatar/Avatar.ts create mode 100644 src/DropdownMenu/DropdownMenu.stories.ts create mode 100644 src/DropdownMenu/DropdownMenu.ts create mode 100644 src/Popover/Popover.stories.ts create mode 100644 src/Popover/Popover.ts diff --git a/src/Avatar/Avatar.stories.ts b/src/Avatar/Avatar.stories.ts new file mode 100644 index 0000000..14df905 --- /dev/null +++ b/src/Avatar/Avatar.stories.ts @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import { BTAvatarProps } from './Avatar'; + +const meta: Meta = { + title: 'BTAvatar', + render: () => + html`
+ +
+
`, + parameters: { + layout: 'fullscreen', + }, +}; +type Story = StoryObj; + +export default meta; + +export const Default: Story = {}; diff --git a/src/Avatar/Avatar.ts b/src/Avatar/Avatar.ts new file mode 100644 index 0000000..4f41d66 --- /dev/null +++ b/src/Avatar/Avatar.ts @@ -0,0 +1,26 @@ +import { css } from 'lit'; +import { customElement } from '../utils'; + +export type BTAvatarProps = {}; + +@customElement({ + name: 'bt-avatar', + extends: 'button', + styles: css` + :host { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + overflow: hidden; + background-color: hsl(215, 14%, 34%); + border-radius: 9999px; + border: none; + color: hsl(216, 12%, 84%); + cursor: pointer; + } + `, +}) +export class BTAvatar extends HTMLButtonElement {} diff --git a/src/Docs.mdx b/src/Docs.mdx index 235b569..38f2f69 100644 --- a/src/Docs.mdx +++ b/src/Docs.mdx @@ -1,13 +1,16 @@ import { Canvas, Meta } from '@storybook/blocks'; +import * as AvatarStories from './Avatar/Avatar.stories'; import * as HeadingStories from './Heading/Heading.stories'; import * as BadgeStories from './Badge/Badge.stories'; import * as ButtonStories from './Button/Button.stories'; +import * as DropdownMenuStories from './DropdownMenu/DropdownMenu.stories' import * as HeaderStories from './Header/Header.stories'; import * as InputLabelStories from './InputLabel/InputLabel.stories'; import * as LoaderStories from './Loader/Loader.stories'; import * as MainStories from './Main/Main.stories'; import * as NotificationStories from './Notifications/Notifications.stories'; +import * as PopoverStories from './Popover/Popover.stories'; import * as TableStories from './Table/Table.stories'; @@ -33,6 +36,10 @@ body { } ``` +# Avatar + + + # Badge @@ -44,6 +51,10 @@ body { +# DropdownMenu + + + # Header @@ -87,6 +98,10 @@ document.dispatchEvent(new SuccessNotificationEvent('Completed')); document.dispatchEvent(new ErrorNotificationEvent('Failure')); ``` +# Popover + + + # Table diff --git a/src/DropdownMenu/DropdownMenu.stories.ts b/src/DropdownMenu/DropdownMenu.stories.ts new file mode 100644 index 0000000..73aa719 --- /dev/null +++ b/src/DropdownMenu/DropdownMenu.stories.ts @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import { BTDropdownMenuProps } from './DropdownMenu'; + +const meta: Meta = { + title: 'BTDropdownMenu', + render: () => + html`
+ +
`, +}; +type Story = StoryObj; + +export default meta; + +export const Default: Story = {}; diff --git a/src/DropdownMenu/DropdownMenu.ts b/src/DropdownMenu/DropdownMenu.ts new file mode 100644 index 0000000..619961d --- /dev/null +++ b/src/DropdownMenu/DropdownMenu.ts @@ -0,0 +1,88 @@ +import { css } from 'lit'; +import { customElement } from '../utils'; + +export type BTDropdownMenuProps = {}; + +@customElement({ + name: 'bt-dropdown-menu', + extends: 'ul', + styles: css` + :host { + list-style-type: unset; + margin: unset; + padding: unset; + + li { + display: block; + font-size: 0.875rem; + color: hsl(220, 13%, 91%); + --padding: var(--padding-top, 0.5rem) 1rem var(--padding-bottom, 0.5rem); + --separator-gap: calc(0.3rem + 1px); + + &:has(a, button):hover { + background-color: hsl(215, 14%, 34%); + color: var(--bt-strong-text-color); + } + + &:not(:has(a, button)) { + padding: var(--padding); + } + + &:first-child { + --padding-top: 0.75rem; + } + + &:last-child { + --padding-bottom: 0.75rem; + } + + &[separated] { + position: relative; + margin-bottom: var(--separator-gap); + + &:after { + position: absolute; + height: 0.3rem; + bottom: calc(-0.15rem - 1px); + left: 0; + right: 0; + border-bottom: 1px solid hsla(215, 14%, 34%); + content: ''; + display: block; + } + } + + [separated] + & { + margin-top: var(--separator-gap); + } + } + + a, + button { + padding: var(--padding); + } + + a { + display: block; + text-decoration: unset; + color: inherit; + } + + button { + width: 100%; + border: unset; + background-color: unset; + font: inherit; + cursor: pointer; + text-align: left; + color: inherit; + } + + p { + margin: unset; + padding: 0.2rem 0; + } + } + `, +}) +export class BTDropdownMenu extends HTMLUListElement {} diff --git a/src/Header/Header.stories.ts b/src/Header/Header.stories.ts index 1e48fd3..49a5936 100644 --- a/src/Header/Header.stories.ts +++ b/src/Header/Header.stories.ts @@ -11,9 +11,10 @@ const meta: Meta = { -
-

main text

-
`, +
`, + parameters: { + layout: 'fullscreen', + }, }; type Story = StoryObj; diff --git a/src/Header/Header.ts b/src/Header/Header.ts index 780cebb..c1edebc 100644 --- a/src/Header/Header.ts +++ b/src/Header/Header.ts @@ -19,7 +19,7 @@ export type BTHeaderProps = { font-family: var(--bt-font-family); > * { - padding: 18px 1rem 19px; + padding: 0.75rem 1rem; max-width: 90rem; margin: 0 auto; display: flex; diff --git a/src/Heading/Heading.ts b/src/Heading/Heading.ts index 756364c..33eaed4 100644 --- a/src/Heading/Heading.ts +++ b/src/Heading/Heading.ts @@ -19,7 +19,7 @@ export type BTHeadingProps = {}; } a:has(&):hover { - --heading-color: var(--bt-text-color); + --heading-color: var(--bt-link-hover-color); } } `, diff --git a/src/Main/Main.stories.ts b/src/Main/Main.stories.ts index 03018b3..42b67a6 100644 --- a/src/Main/Main.stories.ts +++ b/src/Main/Main.stories.ts @@ -5,6 +5,9 @@ import { BTMainProps } from './Main'; const meta: Meta = { title: 'BTMain', render: () => html`

Heading in main

`, + parameters: { + layout: 'fullscreen', + } }; type Story = StoryObj; diff --git a/src/Main/Main.ts b/src/Main/Main.ts index 559a09d..041f543 100644 --- a/src/Main/Main.ts +++ b/src/Main/Main.ts @@ -8,10 +8,11 @@ export type BTMainProps = {}; extends: 'main', styles: css` :root { - --bt-background-color: hsl(222, 47%, 11%); + --bt-background-color: hsl(221, 39%, 11%); --bt-text-color: hsl(218, 11%, 65%); --bt-strong-text-color: white; --bt-font-family: system-ui; + --bt-link-hover-color: hsl(218, 93%, 61%); } :host { diff --git a/src/Notifications/Notifications.stories.ts b/src/Notifications/Notifications.stories.ts index c93cba8..6af81ba 100644 --- a/src/Notifications/Notifications.stories.ts +++ b/src/Notifications/Notifications.stories.ts @@ -41,8 +41,7 @@ export default meta; export const Default: Story = { render: () => - html`
- + html`
-
`, +
`, }; diff --git a/src/Popover/Popover.stories.ts b/src/Popover/Popover.stories.ts new file mode 100644 index 0000000..4dbb4b4 --- /dev/null +++ b/src/Popover/Popover.stories.ts @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import { BTPopoverProps } from './Popover'; + +const meta: Meta = { + title: 'BTPopover', + render: () => + html` +
+
Content text
+
+
`, +}; +type Story = StoryObj; + +export default meta; + +export const Default: Story = {}; diff --git a/src/Popover/Popover.ts b/src/Popover/Popover.ts new file mode 100644 index 0000000..a708b33 --- /dev/null +++ b/src/Popover/Popover.ts @@ -0,0 +1,83 @@ +import { css } from 'lit'; +import { customElement } from '../utils'; + +export type BTPopoverProps = {}; + +@customElement({ + name: 'bt-popover', + extends: 'div', + styles: css` + :host { + margin: unset; + padding: unset; + white-space: nowrap; + top: calc(var(--trigger-bottom) + 0.5rem); + background-color: hsl(217, 19%, 27%); + border-radius: 0.5rem; + min-width: 11rem; + border: none; + color: var(--bt-strong-text-color); + overflow: hidden; + + &.left { + left: var(--trigger-left); + } + + &.right { + left: var(--trigger-right); + transform: translateX(-100%); + } + + &.center { + left: var(--trigger-center); + transform: translateX(-50%); + } + } + `, +}) +export class BTPopover extends HTMLDivElement { + connectedCallback() { + this.addEventListener('beforetoggle', () => { + this.style.opacity = '0'; + }); + this.addEventListener('toggle', () => { + const trigger = document.querySelector( + `[popovertarget="${this.id}"]`, + ) as HTMLElement; + this.style.setProperty( + '--trigger-bottom', + `${trigger.getBoundingClientRect().bottom}px`, + ); + this.style.setProperty( + '--trigger-right', + `${trigger.getBoundingClientRect().right}px`, + ); + this.style.setProperty( + '--trigger-left', + `${trigger.getBoundingClientRect().left}px`, + ); + this.style.setProperty( + '--trigger-center', + `${(trigger.getBoundingClientRect().right - trigger.getBoundingClientRect().left) / 2}px`, + ); + const overflows = ['left', 'right', 'center'].map((position) => { + this.classList.add(position); + const measure = { + position, + overflow: Math.max( + -Math.min(this.getBoundingClientRect().left, 0), + -Math.min( + document.body.clientWidth - this.getBoundingClientRect().right, + 0, + ), + ), + }; + this.classList.remove('left', 'right', 'center'); + return measure; + }); + overflows.sort((a, b) => a.overflow - b.overflow); + this.classList.add(overflows[0].position); + this.style.opacity = '1'; + }); + } +} diff --git a/src/index.ts b/src/index.ts index aeb57ae..df4bbfd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ -export * from './Button/Button'; +export * from './Avatar/Avatar'; export * from './Badge/Badge'; +export * from './Button/Button'; +export * from './DropdownMenu/DropdownMenu'; export * from './Header/Header'; export * from './Heading/Heading'; export * from './InputLabel/InputLabel'; @@ -7,6 +9,7 @@ export * from './Loader/Loader'; export * from './Main/Main'; export * from './Notifications/Notification'; export * from './Notifications/Notifications'; +export * from './Popover/Popover'; export * from './Table/RowSelector'; export * from './Table/Table'; export * from './utils';