From ba934b2ab8b6b617443e9548bcef7671023b16aa Mon Sep 17 00:00:00 2001 From: Yuri Haruta Date: Mon, 21 Oct 2024 15:19:54 +0200 Subject: [PATCH 1/3] Implement PlaceholderEmpty component --- src/assets/icons/_icons.ts | 4 + src/assets/icons/file-error.svg | 17 +++ src/assets/icons/file.svg | 15 +++ src/assets/icons/folder.svg | 14 +++ src/assets/icons/graph.svg | 9 ++ .../PlaceholderEmpty.module.scss | 76 +++++++++++++ .../PlaceholderEmpty.stories.tsx | 101 +++++++++++++++++ .../PlaceholderEmpty/PlaceholderEmpty.tsx | 104 ++++++++++++++++++ 8 files changed, 340 insertions(+) create mode 100644 src/assets/icons/file-error.svg create mode 100644 src/assets/icons/file.svg create mode 100644 src/assets/icons/folder.svg create mode 100644 src/assets/icons/graph.svg create mode 100644 src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.module.scss create mode 100644 src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.stories.tsx create mode 100644 src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.tsx diff --git a/src/assets/icons/_icons.ts b/src/assets/icons/_icons.ts index 7ace2d4..6149133 100644 --- a/src/assets/icons/_icons.ts +++ b/src/assets/icons/_icons.ts @@ -24,7 +24,11 @@ export const icons = { 'edit': {}, 'email': {}, 'eye': {}, + 'file': {}, + 'file-error': {}, 'fortanix-ki': {}, + 'folder': {}, + 'graph': {}, 'groups': {}, 'hide': {}, 'home': {}, diff --git a/src/assets/icons/file-error.svg b/src/assets/icons/file-error.svg new file mode 100644 index 0000000..8a71416 --- /dev/null +++ b/src/assets/icons/file-error.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/file.svg b/src/assets/icons/file.svg new file mode 100644 index 0000000..435d6e3 --- /dev/null +++ b/src/assets/icons/file.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/icons/folder.svg b/src/assets/icons/folder.svg new file mode 100644 index 0000000..708f59e --- /dev/null +++ b/src/assets/icons/folder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/icons/graph.svg b/src/assets/icons/graph.svg new file mode 100644 index 0000000..5f5ef50 --- /dev/null +++ b/src/assets/icons/graph.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.module.scss b/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.module.scss new file mode 100644 index 0000000..2aee0ee --- /dev/null +++ b/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.module.scss @@ -0,0 +1,76 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not +|* distributed with this file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +@use '../../../styling/defs.scss' as bk; + +@layer baklava.components { + .bk-placeholder-empty { + @include bk.component-base(bk-placeholder-empty); + display: flex; + align-items: center; + flex-direction: column; + + &.bk-placeholder-empty--large { + gap: bk.$spacing-6; + + .bk-placeholder-empty__icon { + --bk-icon-size: 6rem; + max-height: 5rem; + } + + .bk-placeholder-empty__content { + gap: bk.$spacing-4; + } + + .bk-placeholder-empty__content__title { + font-size: 20px; + line-height: 20px; + } + } + + &.bk-placeholder-empty--small { + gap: bk.$spacing-4; + + .bk-placeholder-empty__icon { + --bk-icon-size: 4rem; + max-height: 3rem; + } + + .bk-placeholder-empty__content { + gap: bk.$spacing-2; + } + + .bk-placeholder-empty__content__title { + font-size: 14px; + line-height: 14px; + } + } + + .bk-placeholder-empty__icon { + color: bk.$theme-empty-state-stoke-default; + } + + .bk-placeholder-empty__content { + display: flex; + align-items: center; + flex-direction: column; + } + + .bk-placeholder-empty__content__title { + color: bk.$theme-text-heading-default; + font-family: bk.$font-family-display; + } + + .bk-placeholder-empty__content__subtitle { + max-width: 50%; + text-align: center; + } + + .bk-placeholder-empty__content__action { + display: flex; + align-items: center; + gap: bk.$spacing-4; + } + } +} diff --git a/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.stories.tsx b/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.stories.tsx new file mode 100644 index 0000000..ba460e7 --- /dev/null +++ b/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.stories.tsx @@ -0,0 +1,101 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not +|* distributed with this file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +import type { Meta, StoryObj } from '@storybook/react'; + +import * as React from 'react'; + +import { PlaceholderEmpty, PlaceholderEmptyAction } from './PlaceholderEmpty.tsx'; +import { Button } from '../../actions/Button/Button.tsx'; +import { Link } from '../../actions/Link/Link.tsx'; + + +type PlaceholderEmptyArgs = React.ComponentProps; +type Story = StoryObj; + +export default { + component: PlaceholderEmpty, + parameters: { + layout: 'padded', + }, + tags: ['autodocs'], + argTypes: { + }, + args: { + title: 'No data to display', + }, + render: (args) => , +} satisfies Meta; + +const actions = ( + + + + +); + +const subtitle = ( + <> + In case there is a secondary text to be added withIn case + there is a secondary text to be added with Link + +); + +export const Standard: Story = { + name: 'Standard', + args: {}, +}; + +export const StandardWithSubtitle: Story = { + name: 'Standard with subtitle', + args: { + subtitle, + }, +}; + +export const StandardWithButtons: Story = { + name: 'Standard with buttons', + args: { + actions, + }, +}; + +export const StandardWithSubtitleAndButtons: Story = { + name: 'Standard with subtitle and buttons', + args: { + subtitle, + actions, + }, +}; + +export const Small: Story = { + name: 'Small', + args: { + size: 'small', + }, +}; + +export const SmallWithFolderIcon: Story = { + name: 'Small with folder icon', + args: { + size: 'small', + iconType: 'folder', + }, +}; + +export const SmallWithFileIcon: Story = { + name: 'Small with file icon', + args: { + size: 'small', + iconType: 'file', + }, +}; + +export const SmallWithFileErrorIcon: Story = { + name: 'Small with file error icon', + args: { + size: 'small', + iconType: 'file-error', + }, +}; diff --git a/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.tsx b/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.tsx new file mode 100644 index 0000000..beca1a3 --- /dev/null +++ b/src/components/graphics/PlaceholderEmpty/PlaceholderEmpty.tsx @@ -0,0 +1,104 @@ + +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not +|* distributed with this file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react'; +import { classNames as cx, type ComponentProps } from '../../../util/componentUtil.ts'; +import { assertUnreachable } from '../../../util/types.ts'; + +import { Icon } from '../Icon/Icon.tsx'; + +import cl from './PlaceholderEmpty.module.scss'; + +export { cl as PlaceholderEmptyClassNames }; + +export type PlaceholderEmptyIconType = 'graph' | 'folder' | 'file' | 'file-error'; + +export type PlaceholderEmptySize = 'large' | 'small'; + +export type PlaceholderEmptyProps = React.PropsWithChildren & { + /** Whether this component should be unstyled. */ + unstyled?: undefined | boolean, + + /** A size of this component. */ + size?: undefined | PlaceholderEmptySize, + + /** An icon type of this component. */ + iconType?: undefined | PlaceholderEmptyIconType, + + /** A custom icon of this component. */ + customIcon?: undefined | React.ReactNode, + + /** A title of this component. Mandatory. */ + title: string, + + /** A sub message to be displayed next to the title. */ + subtitle?: undefined | string | React.ReactNode, + + /** Any additional actions to be shown in this component, such as a button. */ + actions?: undefined | React.ReactNode, +}>; +/** + * A way to display the absence of data. + */ +export const PlaceholderEmpty = (props: PlaceholderEmptyProps) => { + const { + unstyled = false, + size = 'large', + iconType = 'graph', + customIcon = null, + title = '', + subtitle = '', + actions = null, + children, + ...propsRest + } = props; + + const icon = ((): PlaceholderEmptyIconType => { + switch (iconType) { + case 'graph': return 'graph'; + case 'folder': return 'folder'; + case 'file': return 'file'; + case 'file-error': return 'file-error'; + default: throw assertUnreachable(iconType); + } + })(); + + return ( +
+ {customIcon || } + +
+ {title && ( + {title} + )} + {subtitle && ( + {subtitle} + )} + {actions} +
+
+ ); +}; + +export type PlaceholderEmptyActionProps = React.PropsWithChildren>; +/** + * A wrapper component, intended to easily add some styling to children's `