diff --git a/src/components/avatar-button/styles.ts b/src/components/avatar-button/styles.ts index 37c62b04..242fc7d6 100644 --- a/src/components/avatar-button/styles.ts +++ b/src/components/avatar-button/styles.ts @@ -14,11 +14,11 @@ export const ElAvatarButton = styled.button` ${ElAvatar} { box-shadow: 0px 0px 0px 1px #fff, - 0px 0px 0px 4px var(--Colours-Purple-purple-300, #7e9bfa); + 0px 0px 0px 4px var(--purple-300); } } &:hover ${ElAvatar} { - background: var(--fill-colour-fill-action-light, #d6e1ff); + background: var(--fill-action-light); } ` diff --git a/src/components/avatar-rectangle/avatar-rectangle.mdx b/src/components/avatar-rectangle/avatar-rectangle.mdx index c6a97f57..89bc30f7 100644 --- a/src/components/avatar-rectangle/avatar-rectangle.mdx +++ b/src/components/avatar-rectangle/avatar-rectangle.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Controls } from '@storybook/blocks' +import { Meta, Canvas, Controls, Description } from '@storybook/blocks' import { RenderHtmlMarkup } from '../../storybook/render-html-markup' import * as AvatarRectangleStories from './avatar-rectangle.stories' @@ -10,30 +10,24 @@ A versatile component designed to render property image. -## Default Usage - -The default usage use the residential variant and medium size, each can be set using the `data-` attribute. +## Default + +## Avatar Rectangle Variant + + + + ## Using Residential Placeholder -## Using Commercial Variant - - - ## Using Commercial Placeholder - -## React Usage - - - - diff --git a/src/components/avatar-rectangle/avatar-rectangle.stories.tsx b/src/components/avatar-rectangle/avatar-rectangle.stories.tsx index 945652c3..0c2cefd3 100644 --- a/src/components/avatar-rectangle/avatar-rectangle.stories.tsx +++ b/src/components/avatar-rectangle/avatar-rectangle.stories.tsx @@ -1,11 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react' import { AvatarRectangle } from '.' -import { - ElAvatarRectangle, - ElAvatarRectResidentialPlaceholder, - ElAvatarRectBottomImage, - ElAvatarRectCommercialPlaceholder, -} from './styles' +import { ElAvatarRectResidentialPlaceholder, ElAvatarRectCommercialPlaceholder } from './styles' export default { title: 'Components/Avatar Rectangle', @@ -13,6 +8,7 @@ export default { args: { variant: 'residential', size: 'medium', + src: 'https://picsum.photos/id/206/100/100', }, argTypes: { variant: { @@ -26,36 +22,23 @@ export default { }, } as Meta -const exampleImageUrl = 'https://picsum.photos/id/206/100/100' +type Story = StoryObj -export const DefaultUsage = { - render: ({}) => ( - - example - - ), -} +/** + * The default usage use the residential variant and medium size, each can be set using the `data-` attribute. + */ +export const DefaultUsage: Story = {} -export const UsingResidentialPlaceholder = { - render: ({}) => , +export const AvatarRectangleVariant: Story = { + args: { + variant: 'commercial', + }, } -export const UsingCommercialVariant = { - render: ({}) => ( - - example - - ), +export const UsingResidentialPlaceholder = { + render: () => , } export const UsingCommercialPlaceholder = { - render: ({}) => , -} - -export const ReactUsage: StoryObj = { - args: { - src: exampleImageUrl, - }, - render: (props) => , + render: () => , } diff --git a/src/components/avatar-rectangle/styles.ts b/src/components/avatar-rectangle/styles.ts index 5f42b9bd..be2e8022 100644 --- a/src/components/avatar-rectangle/styles.ts +++ b/src/components/avatar-rectangle/styles.ts @@ -7,23 +7,23 @@ import CommercialBottomImage from './icons/bottom-commercial-image.svg?react' import CommercialSmallBottomImage from './icons/bottom-commercial-image-small.svg?react' const baseAvatarRectMediumSize = ` - width: var(--size-size-12, 72px); + width: var(--size-18); height: 54px; ` const baseAvatarRectSmallSize = ` - width: var(--size-size-11, 64px); - height: var(--size-size-9, 48px); + width: var(--size-16); + height: var(--size-12); ` // The commercial consists of two sections: the commercial image and the bottom placeholder. const baseCommercialMediumImagesSize = ` width: 54px; - height: var(--size-size-8, 40px); + height: var(--size-10); ` const baseCommercialBottomSmallPlaceholderSize = ` - width: var(--size-size-9, 48px); + width: var(--size-12); height: 36px; ` diff --git a/src/components/avatar/avatar.mdx b/src/components/avatar/avatar.mdx index f8cd7f69..53c26bf5 100644 --- a/src/components/avatar/avatar.mdx +++ b/src/components/avatar/avatar.mdx @@ -1,4 +1,5 @@ -import { Meta, Story, Canvas, Controls } from '@storybook/blocks' +import { Meta, Story, Canvas, Controls, Description +} from '@storybook/blocks' import { RenderHtmlMarkup } from '../../storybook/render-html-markup' import * as AvatarStories from './avatar.stories' @@ -6,42 +7,37 @@ import * as AvatarStories from './avatar.stories' # Avatar -An avatar component to be used typically with a `Card` or `Nav` component. The default variant renders a circle with children. + -There are multiple variants that can be used by utilizing the data props, such as size, colour, and shape (see the HTML version of React usage). In addition to text, it also accepts an Icon as a child. - -## Style Only Usage - - + - +## Default - + - + -## With Colour +## Avatar with Icon - + - + -## With Square Shape - +## Avatar Colour - + -## With Small Size + - +## Avatar Shape - + -## React Usage + - +## Avatar Size - + -WithColourWithColour + diff --git a/src/components/avatar/avatar.stories.tsx b/src/components/avatar/avatar.stories.tsx index f4ded079..bc9a6d1d 100644 --- a/src/components/avatar/avatar.stories.tsx +++ b/src/components/avatar/avatar.stories.tsx @@ -1,49 +1,49 @@ -import type { StoryObj } from '@storybook/react' -import { Avatar, ElAvatar } from '.' +import type { Meta, StoryObj } from '@storybook/react' +import { Avatar } from '.' import { Icon } from '../icon' export default { title: 'Components/Avatar', component: Avatar, -} + args: { + children: 'AD', + }, +} as Meta -export const StyleOnlyUsage = { - render: ({}) => AD, -} +type Story = StoryObj + +/** + * An avatar component to be used typically with a `Card` or `Nav` component. + * The default variant renders a circle with children. + * + * There are multiple variants that can be used by utilizing the `data-` props + * such as `size`, `colour`, and `shape` (see the HTML version of each version). + * In addition to text, it also accepts an Icon as a child. + */ +export const Default: Story = {} -export const WithIconUsage = { - render: ({}) => ( - +export const AvatarWithIcon: Story = { + render: (props) => ( + - + ), } -export const WithColour = { - render: ({}) => AD, - name: 'Colour: Purple', -} - -export const WithSquareShape = { - render: ({}) => AD, - name: 'Shape: Square', +export const AvatarColour: Story = { + args: { + colour: 'purple', + }, } -export const WithSmallSize = { - render: ({}) => ( - - - - ), - name: 'Size: Small', +export const AvatarShape: Story = { + args: { + shape: 'square', + }, } -export const ReactUsage: StoryObj = { +export const AvatarSize: Story = { args: { - shape: 'circle', - size: 'medium', - colour: 'default', - children: 'AD', + size: 'small', }, - render: (props) => {props.children}, } diff --git a/src/components/avatar/styles.ts b/src/components/avatar/styles.ts index d017e0bf..f1d6ca2d 100644 --- a/src/components/avatar/styles.ts +++ b/src/components/avatar/styles.ts @@ -1,24 +1,24 @@ import { styled } from '@linaria/react' const baseCircleStyle = ` - border-radius: var(--corner-3xl, 24px); + border-radius: var(--corner-3xl); ` const baseMediumSizeStyle = ` - width: var(--size-8, 40px); - height: var(--size-8, 40px); - font-size: var(--font-size-base, 15px); - line-height: var(--line-height-base, 24px); - letter-spacing: var(--letter-spacing-base, -0.15px); + width: var(--size-10); + height: var(--size-10); + font-size: var(--font-size-base); + line-height: var(--line-height-base); + letter-spacing: var(--letter-spacing-base); ` const baseColourDefaultStyle = ` - background: var(--fill-default-medium, #9faebc); - color: var(--text-white, #fff); + background: var(--fill-default-medium); + color: var(--text-white); /* override Icon element colour */ svg { - color: var(--text-white, #fff); + color: var(--text-white); } ` @@ -28,7 +28,7 @@ export const ElAvatar = styled.span` justify-content: center; align-items: center; text-align: center; - font-family: var(--font-family, Inter); + font-family: var(--font-family); font-style: normal; font-weight: 600; @@ -37,23 +37,23 @@ export const ElAvatar = styled.span` ${baseMediumSizeStyle} &[data-shape='square'] { - border-radius: var(--corner-lg, 8px); + border-radius: var(--corner-lg); } &[data-colour='purple'] { - background: var(--fill-action-lightest, #ecf3ff); - color: var(--text-action, #4e56ea); + background: var(--fill-action-lightest); + color: var(--text-action); /* override Icon element colour */ svg { - color: var(--text-action, #4e56ea); + color: var(--text-action); } } &[data-size='small'] { - width: var(--size-7, 32px); - height: var(--size-7, 32px); - font-size: var(--font-size-2xs, 12px); - line-height: var(--line-height-2xs, 16px); - letter-spacing: var(--letter-spacing-2xs, -0.12px); + width: var(--size-8); + height: var(--size-8); + font-size: var(--font-size-2xs); + line-height: var(--line-height-2xs); + letter-spacing: var(--letter-spacing-2xs); } ` diff --git a/src/components/badge/__tests__/__snapshots__/badge.test.tsx.snap b/src/components/badge/__tests__/__snapshots__/badge.test.tsx.snap deleted file mode 100644 index 73bb47e1..00000000 --- a/src/components/badge/__tests__/__snapshots__/badge.test.tsx.snap +++ /dev/null @@ -1,114 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Badge component > should match a snapshot 1`] = ` - - - 50% - - -`; - -exports[`Badge component > should match a snapshot for intent 1`] = ` - - - Some Content - - -`; - -exports[`Badge component > should match a snapshot for intent 2`] = ` - - - Some Content - - -`; - -exports[`Badge component > should match a snapshot for intent 3`] = ` - - - Some Content - - -`; - -exports[`Badge component > should match a snapshot for intent 4`] = ` - - - Some Content - - -`; - -exports[`Badge component > should match a snapshot for intent 5`] = ` - - - Some Content - - -`; - -exports[`Badge component > should match a snapshot for intent 6`] = ` - - - Some Content - - -`; - -exports[`Badge component > should match a snapshot for intent 7`] = ` - - - Some Content - - -`; - -exports[`BadgeGroup component > should match a snapshot 1`] = ` - -
-
- - Some Content - - - Some Content - -
-
-
-`; diff --git a/src/components/badge/__tests__/badge.test.tsx b/src/components/badge/__tests__/badge.test.tsx deleted file mode 100644 index 0b018aad..00000000 --- a/src/components/badge/__tests__/badge.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { render } from '@testing-library/react' -import { Badge, BadgeGroup } from '..' - -describe('Badge component', () => { - it('should match a snapshot', () => { - const wrapper = render(50%) - expect(wrapper.asFragment()).toMatchSnapshot() - }) - - it('should match a snapshot for intent', () => { - const wrapper = render(Some Content) - expect(wrapper.asFragment()).toMatchSnapshot() - }) - - it('should match a snapshot for intent', () => { - const wrapper = render(Some Content) - expect(wrapper.asFragment()).toMatchSnapshot() - }) - - it('should match a snapshot for intent', () => { - const wrapper = render(Some Content) - expect(wrapper.asFragment()).toMatchSnapshot() - }) - - it('should match a snapshot for intent', () => { - const wrapper = render(Some Content) - expect(wrapper.asFragment()).toMatchSnapshot() - }) - - it('should match a snapshot for intent', () => { - const wrapper = render(Some Content) - expect(wrapper.asFragment()).toMatchSnapshot() - }) - - it('should match a snapshot for intent', () => { - const wrapper = render(Some Content) - expect(wrapper.asFragment()).toMatchSnapshot() - }) - - it('should match a snapshot for intent', () => { - const wrapper = render(Some Content) - expect(wrapper.asFragment()).toMatchSnapshot() - }) -}) - -describe('BadgeGroup component', () => { - it('should match a snapshot', () => { - const wrapper = render( - - Some Content - Some Content - , - ) - expect(wrapper.asFragment()).toMatchSnapshot() - }) -}) diff --git a/src/components/badge/badge.stories.tsx b/src/components/badge/badge.stories.tsx deleted file mode 100644 index 6a1adb6b..00000000 --- a/src/components/badge/badge.stories.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Badge, BadgeGroup } from '.' - -export default { - title: 'Badge', - component: Badge, -} - -export const BasicUsage = { - render: ({}) => ( - - 100 - - ), -} - -export const WithIntent = { - render: ({}) => ( - - primary - neutral - success - pending - warning - danger - default - - ), -} diff --git a/src/components/badge/badge.tsx b/src/components/badge/badge.tsx deleted file mode 100644 index 0cd9cce0..00000000 --- a/src/components/badge/badge.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { FC, HTMLAttributes } from 'react' -import { cx } from '@linaria/core' -import { ElBadge, ElBadgeGroup, ElBadgeGroupInner } from './__styles__' -import { Intent, getIntentClassName } from '../../helpers/intent' - -export interface BadgeProps extends HTMLAttributes { - intent?: Intent -} - -export const Badge: FC = ({ children, intent, className, ...rest }) => ( - - {children} - -) - -export const BadgeGroup: FC> = ({ children, className, ...rest }) => ( - - {children} - -) diff --git a/src/components/bottom-bar-item/bottom-bar-item.mdx b/src/components/bottom-bar-item/bottom-bar-item.mdx index 28f485ae..16358b8f 100644 --- a/src/components/bottom-bar-item/bottom-bar-item.mdx +++ b/src/components/bottom-bar-item/bottom-bar-item.mdx @@ -12,52 +12,36 @@ A versatile component designed to render navigation icon items within a `BottomB -## Default State +## Default The default appearance of the `BottomBarItem` -## Active State +## Active The visual state of the `BottomBarItem` when it's currently selected or active - + -## Badge State +## With Badge The visual state of the `BottomBarItem` when it's currently selected or active with a badge - + -## Style Anchor Usage - -Demonstrates how to use the `BottomBarItem` using a standard `HTMLAnchorElement` - - - - - -## Style Button Usage - -Demonstrates how to use the `BottomBarItem` using a standard `HTMLButtonElement` - - - - - -## React Anchor Usage +## With Href Demonstrates how to render the `BottomBarItem` using a anchor version - + - + -## React Button Usage +## With OnClick Demonstrates how to render the `BottomBarItem` using a button version - + - + diff --git a/src/components/bottom-bar-item/bottom-bar-item.stories.tsx b/src/components/bottom-bar-item/bottom-bar-item.stories.tsx index a69a78e5..9fe81ea6 100644 --- a/src/components/bottom-bar-item/bottom-bar-item.stories.tsx +++ b/src/components/bottom-bar-item/bottom-bar-item.stories.tsx @@ -1,17 +1,9 @@ -import type { Meta, StoryObj } from '@storybook/react' import { action } from '@storybook/addon-actions' +import type { Meta, StoryObj } from '@storybook/react' -import { BottomBarItem } from './bottom-bar-item' -import { FlexContainer } from '../layout' import { Icon } from '../icon' -import { - ElAnchorBottomBarItemContainer, - ElBottomBarItemBadge, - ElBottomBarItemContent, - ElBottomBarItemIcon, - ElBottomBarItemLabel, - ElButtonBottomBarItemContainer, -} from './styles' +import { FlexContainer } from '../layout' +import { BottomBarItem } from './bottom-bar-item' const meta = { title: 'Components/Bottom Bar Item', @@ -62,7 +54,7 @@ export const Default: Story = { }, } -export const ActiveState: Story = { +export const Active: Story = { args: { isActive: true, children: 'Label', @@ -70,75 +62,15 @@ export const ActiveState: Story = { }, } -export const BadgeState: Story = { +export const WithBadge: Story = { args: { children: 'Label', icon: , hasBadge: true, }, } -export const StyleAnchorUsage: Story = { - args: { - href: '#', - children: 'Label', - icon: , - }, - render: (args) => { - return ( - - - {args?.icon} - {args?.children} - - - - {args?.icon} - {args?.children} - - - - - {args?.icon} - - - {args?.children} - - - ) - }, -} - -export const StyleButtonUsage: Story = { - args: { - children: 'Label', - icon: , - }, - render: (args) => { - return ( - - - {args?.icon} - {args?.children} - - - - {args?.icon} - {args?.children} - - - - - {args?.icon} - - - {args?.children}{' '} - - - ) - }, -} -export const ReactAnchorUsage: Story = { +export const WithHref: Story = { args: { href: '#', icon: , @@ -155,7 +87,8 @@ export const ReactAnchorUsage: Story = { }, } -export const ReactButtonUsage: Story = { +export const WithOnClick: Story = { + name: 'With OnClick', args: { onClick: action('handleClick'), icon: , diff --git a/src/components/bottom-bar-item/styles.ts b/src/components/bottom-bar-item/styles.ts index d52dc753..234360d4 100644 --- a/src/components/bottom-bar-item/styles.ts +++ b/src/components/bottom-bar-item/styles.ts @@ -3,40 +3,40 @@ import { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react' import { ElIcon } from '../icon' export const ElBottomBarItemIcon = styled.div` - width: var(--icon-default, 24px); - height: var(--icon-default, 24px); + width: var(--icon-default); + height: var(--icon-default); color: inherit; ` export const ElBottomBarItemLabel = styled.span` color: inherit; text-align: center; - font-family: var(--font-family, Inter); - font-size: var(--font-size-2xs, 12px); + font-family: var(--font-family); + font-size: var(--font-size-3xs); font-style: normal; - font-weight: var(--font-weight-regular, Regular); - line-height: var(--line-height-3xs, 12px); - letter-spacing: var(--letter-spacing-2xs, 0px); + font-weight: var(--font-weight-regular); + line-height: var(--line-height-3xs); + letter-spacing: var(--letter-spacing-2xs); ` const baseStyles = ` - background-color: var(--fill-white, white); + background-color: var(--fill-white); outline: none; width: 44px; - border: var(--border-none, 0); + border: var(--border-none); display: flex; cursor: pointer; - padding: var(--space-half, 2px) var(--space-none, 0px); + padding: var(--spacing-half) var(--spacing-none); flex-direction: column; align-items: center; - gap: var(--space-half, 2px); + gap: var(--spacing-half); position: relative; flex: 1 0 0; - color: var(--icon-secondary, #607890); + color: var(--icon-secondary); &:active, &[aria-current="true"], &[aria-current="page"] { - color: var(--icon-action, #4e56ea) + color: var(--icon-action) } ` @@ -48,9 +48,9 @@ export const ElBottomBarItemBadge = styled.span` position: absolute; top: -3px; right: -3px; - width: var(--size-2, 8px); - height: var(--size-2, 8px); - background-color: var(--icon-error, #f01830); + width: var(--size-2); + height: var(--size-2); + background-color: var(--icon-error); border-radius: 100%; ` diff --git a/src/components/bottom-bar/__tests__/__snapshots__/bottom-bar.test.tsx.snap b/src/components/bottom-bar/__tests__/__snapshots__/bottom-bar.test.tsx.snap new file mode 100644 index 00000000..5028fa8f --- /dev/null +++ b/src/components/bottom-bar/__tests__/__snapshots__/bottom-bar.test.tsx.snap @@ -0,0 +1,260 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BottomBar > can render 4 items and an overflow menu 1`] = ` + +
+ + + + +
+ +
+
+
+`; + +exports[`BottomBar > can render 5 items 1`] = ` + +
+ + + + + +
+
+`; diff --git a/src/components/bottom-bar/__tests__/bottom-bar.test.tsx b/src/components/bottom-bar/__tests__/bottom-bar.test.tsx new file mode 100644 index 00000000..fd4c6ff9 --- /dev/null +++ b/src/components/bottom-bar/__tests__/bottom-bar.test.tsx @@ -0,0 +1,35 @@ +import { render } from '@testing-library/react' +import { BottomBar } from '../bottom-bar' + +describe('BottomBar', () => { + it('can render 5 items', () => { + expect( + render( + + mock icon}>Menu 1 + mock icon}>Menu 2 + mock icon}>Menu 3 + mock icon}>Menu 4 + mock icon}>Menu 5 + , + ).asFragment(), + ).toMatchSnapshot() + }) + + it('can render 4 items and an overflow menu', () => { + expect( + render( + + mock icon}>Menu 1 + mock icon}>Menu 2 + mock icon}>Menu 3 + mock icon}>Menu 4 + + Menu 5 + Menu 6 + + , + ).asFragment(), + ).toMatchSnapshot() + }) +}) diff --git a/src/components/bottom-bar/__tests__/use-bottom-bar-visibility.test.ts b/src/components/bottom-bar/__tests__/use-bottom-bar-visibility.test.ts new file mode 100644 index 00000000..81bba8bd --- /dev/null +++ b/src/components/bottom-bar/__tests__/use-bottom-bar-visibility.test.ts @@ -0,0 +1,54 @@ +import { RefObject } from 'react' +import { handleChangeBottomBarVisibility, useBottomBarVisibility } from '../use-bottom-bar-visibility' +import { renderHook } from '@testing-library/react-hooks' +import { act } from '@testing-library/react' + +describe('handleChangeBottomBarVisibility', () => { + afterEach(() => { + vi.clearAllMocks() + }) + + it('should call the setScrollStates function while acting to scroll down', () => { + const mockSetScrollStates = vi.fn().mockImplementation(() => ({ previousTopPosition: 0 })) + + handleChangeBottomBarVisibility({ scrollTop: 10 } as any, mockSetScrollStates) + + expect(mockSetScrollStates).toHaveBeenCalledWith(expect.any(Function)) + }) + + it('should call the setScrollStates function while acting to scroll up', () => { + const mockSetScrollStates = vi.fn().mockImplementation(() => ({ previousTopPosition: 10 })) + + handleChangeBottomBarVisibility({ scrollTop: 0 } as any, mockSetScrollStates) + + expect(mockSetScrollStates).toHaveBeenCalledWith(expect.any(Function)) + }) +}) + +describe('useBottomBarVisibility', () => { + it('should trigger the scroll event listener', () => { + const abort = vi.fn() + vi.spyOn(AbortController.prototype, 'abort').mockImplementation(abort) as any + + const mockUseRef = { + current: { + scrollTop: 0, + addEventListener: vi.fn(), + }, + } as unknown as RefObject + + const render = renderHook(() => useBottomBarVisibility(mockUseRef)) + + expect(mockUseRef.current?.addEventListener).toHaveBeenCalledWith('scroll', expect.any(Function), { + signal: expect.any(AbortSignal), + }) + + expect(abort).not.toHaveBeenCalled() + + act(() => { + render.unmount() + }) + + expect(abort).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/components/bottom-bar/bottom-bar.atoms.tsx b/src/components/bottom-bar/bottom-bar.atoms.tsx new file mode 100644 index 00000000..3539ed8c --- /dev/null +++ b/src/components/bottom-bar/bottom-bar.atoms.tsx @@ -0,0 +1,31 @@ +import type { FC, ReactNode } from 'react' + +import { Menu, MenuItemProps } from '../menu' +import { BottomBarItem } from '../bottom-bar-item' +import { Icon } from '../icon' + +export interface BottomBarMoreMenuProps { + children: ReactNode +} + +export const BottomBarMoreMenu: FC = ({ children }) => { + return ( + + + {({ getTriggerProps }) => ( + }> + More + + )} + + + {children} + + + ) +} + +export type BottomBarMoreMenuItemProps = MenuItemProps +export const BottomBarMoreMenuItem: FC = (args) => { + return +} diff --git a/src/components/bottom-bar/bottom-bar.mdx b/src/components/bottom-bar/bottom-bar.mdx new file mode 100644 index 00000000..8d767f42 --- /dev/null +++ b/src/components/bottom-bar/bottom-bar.mdx @@ -0,0 +1,34 @@ +import { Meta, Canvas, Controls } from '@storybook/blocks' +import { RenderHtmlMarkup } from '../../storybook/render-html-markup' +import * as BottomBarItemStories from './bottom-bar.stories' + + + +# Bottom Bar + +A component designed to render a navigation bar at the bottom of the screen + +This component is also capable of sliding up and down as the user scrolls the parent element + +`BottomBar` have additional export that can be used to render the following components: + +- `Item`: Renders a single item +- `MoreMenu`: Renders an expandable menu +- `MoreMenuItem`: Renders an expandable menu item + +There are two variants of the `BottomBar` component: + +- Fixed: The `BottomBar` component that only contains less or equal to 5 items +- Expandable: The `BottomBar` component is expandable and can contain more than 5 items + +## Fixed Usage + +Demonstrates how to render the fixed usage of the `BottomBar` component that contains 5 items + + + +## Expandable Usage + +Demonstrates how to render the expandable usage of the `BottomBar` component that contains more than 5 items + + diff --git a/src/components/bottom-bar/bottom-bar.stories.tsx b/src/components/bottom-bar/bottom-bar.stories.tsx new file mode 100644 index 00000000..ebc450f3 --- /dev/null +++ b/src/components/bottom-bar/bottom-bar.stories.tsx @@ -0,0 +1,85 @@ +import { action } from '@storybook/addon-actions' +import type { Meta, StoryObj } from '@storybook/react' +import { useRef } from 'react' +import { Icon } from '../icon' +import { BottomBar } from './bottom-bar' + +const meta = { + title: 'Components/Bottom Bar', + component: BottomBar, + parameters: { + layout: 'fullscreen', + }, + args: { + children: null, + parentRef: null, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Standard: Story = { + render: () => { + const ref = useRef(null) + + return ( +
+
+
long content
+
short content
+ + } isActive onClick={action('clicked')}> + Menu 1 + + } onClick={action('clicked')} hasBadge> + Menu 2 + + } onClick={action('clicked')}> + Menu 3 + + } onClick={action('clicked')}> + Menu 4 + + } onClick={action('clicked')}> + Menu 5 + + +
+
+ ) + }, +} + +export const WithOverflowMenu: Story = { + render: () => { + const ref = useRef(null) + + return ( +
+
+
long content
+
short content
+ + } isActive onClick={action('clicked')}> + Menu 1 + + } onClick={action('clicked')} hasBadge> + Menu 2 + + } onClick={action('clicked')}> + Menu 3 + + } onClick={action('clicked')}> + Menu 4 + + + Menu 5 + Menu 6 + + +
+
+ ) + }, +} diff --git a/src/components/bottom-bar/bottom-bar.tsx b/src/components/bottom-bar/bottom-bar.tsx new file mode 100644 index 00000000..247a9689 --- /dev/null +++ b/src/components/bottom-bar/bottom-bar.tsx @@ -0,0 +1,43 @@ +import type { FC, HTMLAttributes, ReactNode, RefObject } from 'react' + +import { BottomBarItem } from '../bottom-bar-item' + +import { BottomBarMoreMenu, BottomBarMoreMenuItem } from './bottom-bar.atoms' +import { ElBottomBar } from './styles' +import { useBottomBarVisibility } from './use-bottom-bar-visibility' + +export interface BottomBarProps extends HTMLAttributes { + /** + * The children of the bottom bar + **/ + children: ReactNode + + /** + * The reference of the parent element that the bottom bar is attached to + * + * @description see the story for an example + */ + parentRef: RefObject | null +} + +type BottomBarFC = FC & { + Item: typeof BottomBarItem + MoreMenu: typeof BottomBarMoreMenu + MoreMenuItem: typeof BottomBarMoreMenuItem +} + +const BottomBar: BottomBarFC = ({ children, parentRef, ...rest }) => { + const { isOpen } = useBottomBarVisibility(parentRef) + + return ( + + {children} + + ) +} + +BottomBar.Item = BottomBarItem +BottomBar.MoreMenu = BottomBarMoreMenu +BottomBar.MoreMenuItem = BottomBarMoreMenuItem + +export { BottomBar } diff --git a/src/components/bottom-bar/index.ts b/src/components/bottom-bar/index.ts new file mode 100644 index 00000000..a590b73e --- /dev/null +++ b/src/components/bottom-bar/index.ts @@ -0,0 +1,4 @@ +export * from './bottom-bar' +export * from './bottom-bar.atoms' +export * from './styles' +export * from './use-bottom-bar-visibility' diff --git a/src/components/bottom-bar/styles.ts b/src/components/bottom-bar/styles.ts new file mode 100644 index 00000000..bf572f64 --- /dev/null +++ b/src/components/bottom-bar/styles.ts @@ -0,0 +1,33 @@ +import { styled } from '@linaria/react' + +export const ElBottomBar = styled.div` + display: flex; + padding: var(--spacing-2, 8px); + justify-content: center; + align-items: center; + align-self: stretch; + border-top: var(--border-default, 1px) solid var(--outline-default, #e5e9ed); + background: var(--fill-white, #fff); + + position: absolute; + bottom: 0; + left: 0; + z-index: 5; + overflow: visible; + margin-top: auto; + width: 100%; + + transition: + transform 0.3s ease-in-out, + visibility 0.3s ease-in-out; + + &[data-is-open='true'] { + transform: translateY(0); + visibility: visible; + } + + &[data-is-open='false'] { + transform: translateY(100%); + visibility: hidden; + } +` diff --git a/src/components/bottom-bar/use-bottom-bar-visibility.ts b/src/components/bottom-bar/use-bottom-bar-visibility.ts new file mode 100644 index 00000000..0a4d39ba --- /dev/null +++ b/src/components/bottom-bar/use-bottom-bar-visibility.ts @@ -0,0 +1,47 @@ +import { Dispatch, RefObject, SetStateAction, useEffect, useState } from 'react' + +type BottomBarVisibility = { + isOpen: boolean | undefined + previousTopPosition: number +} + +export const handleChangeBottomBarVisibility = ( + element: HTMLElement, + setScrollStates: Dispatch>, +) => { + const { scrollTop } = element ?? {} + + setScrollStates((state) => ({ + isOpen: state.previousTopPosition > scrollTop, + previousTopPosition: scrollTop, + })) +} + +/** + * Hook to get the vertical scroll direction of the parent element + */ +export const useBottomBarVisibility = (ref: RefObject | null): BottomBarVisibility => { + const [scrollStates, setScrollStates] = useState({ + isOpen: undefined, + previousTopPosition: 0, + }) + + useEffect( + function handleScrollListenerRegistry() { + const element = ref?.current + if (!element) return + const abortController = new AbortController() + + element.addEventListener('scroll', () => handleChangeBottomBarVisibility(element, setScrollStates), { + signal: abortController.signal, + }) + + return () => { + abortController.abort() + } + }, + [ref], + ) + + return scrollStates +} diff --git a/src/components/button/button.mdx b/src/components/button/button.mdx index 13a99914..dad2fc04 100644 --- a/src/components/button/button.mdx +++ b/src/components/button/button.mdx @@ -58,6 +58,14 @@ With href prop, link can be displayed as button. Anchor attributes such as `targ +## Button With hasNoPadding + +The `hasNoPadding` prop is used with the `tertiary` variant of the button to demonstrate how removing padding and resetting the height can create a more minimalistic, compact button style. +This is useful when you need a button with no extra spacing or fixed height, while maintaining the button's core functionality and appearance. The prop is designed exclusively for the tertiary variant and will not affect other button variant types. + + + + ## Other attributes All other standard HTML attributes for ` + )} + + + + + 25 + 50 + 100 + + + + + } + /> + + ), +} diff --git a/src/components/table/table-container/table-container.tsx b/src/components/table/table-container/table-container.tsx new file mode 100644 index 00000000..17685b58 --- /dev/null +++ b/src/components/table/table-container/table-container.tsx @@ -0,0 +1,10 @@ +import { HTMLAttributes, ReactNode } from 'react' +import { ElTableContainer } from './styles' + +interface TableContainerProps extends HTMLAttributes { + children: ReactNode +} + +export const TableContainer: React.FC = ({ children, ...rest }) => { + return {children} +} diff --git a/src/components/table/table-toolbar/__tests__/__snapshots__/table-toolbar.test.tsx.snap b/src/components/table/table-toolbar/__tests__/__snapshots__/table-toolbar.test.tsx.snap new file mode 100644 index 00000000..0b971cc2 --- /dev/null +++ b/src/components/table/table-toolbar/__tests__/__snapshots__/table-toolbar.test.tsx.snap @@ -0,0 +1,20 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Table Toolbar > should match snapshot 1`] = ` + +
+
+ 125 Properties +
+
+ Page Size menu +
+
+
+`; diff --git a/src/components/table/table-toolbar/__tests__/table-toolbar.test.tsx b/src/components/table/table-toolbar/__tests__/table-toolbar.test.tsx new file mode 100644 index 00000000..f59d614f --- /dev/null +++ b/src/components/table/table-toolbar/__tests__/table-toolbar.test.tsx @@ -0,0 +1,9 @@ +import { render } from '@testing-library/react' +import { TableToolbar } from '../index' + +describe('Table Toolbar', () => { + test('should match snapshot', () => { + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + }) +}) diff --git a/src/components/table/table-toolbar/index.ts b/src/components/table/table-toolbar/index.ts new file mode 100644 index 00000000..ebf191de --- /dev/null +++ b/src/components/table/table-toolbar/index.ts @@ -0,0 +1,2 @@ +export * from './styles' +export * from './table-toolbar' diff --git a/src/components/table/table-toolbar/styles.ts b/src/components/table/table-toolbar/styles.ts new file mode 100644 index 00000000..8a113ca2 --- /dev/null +++ b/src/components/table/table-toolbar/styles.ts @@ -0,0 +1,24 @@ +import { styled } from '@linaria/react' + +export const ElTableToolbar = styled.div` + display: flex; + width: 100%; + background: var(--fill-white); + padding: var(--spacing-2) 0px var(--spacing-2) 0px; + gap: 0px; + justify-content: space-between; + align-items: center; +` + +export const ElTableToolbarDescription = styled.div` + font-family: var(--font-family); + font-size: var(--font-size-sm); + font-weight: 400; + color: var(--text-colour-text-primary); + line-height: var(--line-height-sm); + letter-spacing: var(--letter-spacing-sm); + text-align: left; + gap: var(--spacing-1); +` + +export const ElTableToolbarActions = styled.div`` diff --git a/src/components/table/table-toolbar/table-toolbar.mdx b/src/components/table/table-toolbar/table-toolbar.mdx new file mode 100644 index 00000000..08105321 --- /dev/null +++ b/src/components/table/table-toolbar/table-toolbar.mdx @@ -0,0 +1,33 @@ +import { Meta, Canvas, Controls, Description } from '@storybook/blocks' +import { RenderHtmlMarkup } from '../../../storybook/render-html-markup' +import * as TableToolbarStories from './table-toolbar.stories' + + + +# Table Toolbar + + + +## Basic Usage + + + + + + + +## With Bulk Actions + + + + + + + +## Toolbar Skeleton + + + + + + diff --git a/src/components/table/table-toolbar/table-toolbar.stories.tsx b/src/components/table/table-toolbar/table-toolbar.stories.tsx new file mode 100644 index 00000000..d64b3fdf --- /dev/null +++ b/src/components/table/table-toolbar/table-toolbar.stories.tsx @@ -0,0 +1,128 @@ +import { Meta } from '@storybook/react' +// import { figmaDesignUrls } from '../../storybook/figma' +import { TableToolbar } from './table-toolbar.js' +import { Button } from '#src/components/button/button' +import { Icon } from '#src/components/icon/icon-component' +import { Menu } from '#src/components/menu/menu' +import { MenuPopover, MenuTrigger } from '#src/components/menu/menu-popover' +import { MenuItem, MenuItemGroup, MenuList } from '#src/components/menu/menu.atoms' +import { ButtonGroup } from '#src/components/button-group/button-group' +import { Skeleton } from '#src/components/skeleton/skeleton' + +const meta: Meta = { + title: 'Components/TableToolbar', + component: TableToolbar, +} + +export default meta + +/** A simple toolbar for tables. + * When no items are selected, it displays the total item count and default actions. + */ + +export const BasicUsage = { + render: ({}) => ( + + + {({ getTriggerProps }) => ( + // To do: Once Button component is update with more props for no-padding, please make updates here + + )} + + + + + 25 + 50 + 100 + + + + + } + /> + ), +} + +/** + * In tables with batch actions, when one or more items have been selected, + * the toolbar changes to display the number of selected items and the available actions + */ +export const WithBulkActions = { + render: ({}) => ( + + + + + + + {({ getTriggerProps }) => ( + + + } + /> + ), +} + +/** Skeleton state for the table toolbar + * To display until the data is retrieved and rendered in tabel + */ + +export const ToolbarSkeleton = { + render: ({}) => ( + } + actions={ + + + {({ getTriggerProps }) => ( + // To do: Once Button component is update with more props for no-padding, please make updates here + + )} + + + + + 25 + 50 + 100 + + + + + } + /> + ), +} diff --git a/src/components/table/table-toolbar/table-toolbar.tsx b/src/components/table/table-toolbar/table-toolbar.tsx new file mode 100644 index 00000000..7333923e --- /dev/null +++ b/src/components/table/table-toolbar/table-toolbar.tsx @@ -0,0 +1,16 @@ +import { HTMLAttributes, ReactNode } from 'react' +import { ElTableToolbar, ElTableToolbarActions, ElTableToolbarDescription } from './styles' + +interface TableToolbarProps extends HTMLAttributes { + description?: ReactNode + actions?: ReactNode +} + +export const TableToolbar: React.FC = ({ description, actions }) => { + return ( + + {description} + {actions} + + ) +} diff --git a/src/components/top-bar/__test__/__snapshots__/top-bar.test.tsx.snap b/src/components/top-bar/__test__/__snapshots__/top-bar.test.tsx.snap index 1fb79a21..ef11a0ab 100644 --- a/src/components/top-bar/__test__/__snapshots__/top-bar.test.tsx.snap +++ b/src/components/top-bar/__test__/__snapshots__/top-bar.test.tsx.snap @@ -4,45 +4,50 @@ exports[`TopBar Snapshot > should match snapshot 1`] = `