From f62c3d60ff2d4975b114997fbdd55a02b2ce41d0 Mon Sep 17 00:00:00 2001 From: Eelco Wiersma Date: Sun, 24 Nov 2024 16:50:04 +0000 Subject: [PATCH] feat: export components from root --- .changeset/dry-berries-report.md | 5 + .../src/components/avatar/avatar.tsx | 75 ++++++++++ .../src/components/avatar/index.ts | 1 + .../src/components/menu/index.ts | 1 + .../src/components/menu/menu.tsx | 129 ++++++++++++++++++ .../src/components/segmented-control/index.ts | 0 .../segmented-control/segmented-control.tsx | 48 +++++++ .../src/components/snackbar/index.ts | 2 + .../components/snackbar/snackbar.context.ts | 10 ++ .../src/components/snackbar/snackbar.tsx | 61 +++++++++ .../src/components/tooltip/index.ts | 1 + .../tooltip}/tooltip.tsx | 5 +- .../src/compositions/empty-state.tsx | 34 ----- .../saas-ui-react/src/compositions/menu.tsx | 108 --------------- .../saas-ui-react/src/compositions/radio.tsx | 24 ---- packages/saas-ui-react/src/index.ts | 33 ++++- 16 files changed, 367 insertions(+), 170 deletions(-) create mode 100644 .changeset/dry-berries-report.md create mode 100644 packages/saas-ui-react/src/components/avatar/avatar.tsx create mode 100644 packages/saas-ui-react/src/components/avatar/index.ts create mode 100644 packages/saas-ui-react/src/components/menu/index.ts create mode 100644 packages/saas-ui-react/src/components/menu/menu.tsx create mode 100644 packages/saas-ui-react/src/components/segmented-control/index.ts create mode 100644 packages/saas-ui-react/src/components/segmented-control/segmented-control.tsx create mode 100644 packages/saas-ui-react/src/components/snackbar/index.ts create mode 100644 packages/saas-ui-react/src/components/snackbar/snackbar.context.ts create mode 100644 packages/saas-ui-react/src/components/snackbar/snackbar.tsx create mode 100644 packages/saas-ui-react/src/components/tooltip/index.ts rename packages/saas-ui-react/src/{compositions => components/tooltip}/tooltip.tsx (91%) delete mode 100644 packages/saas-ui-react/src/compositions/empty-state.tsx delete mode 100644 packages/saas-ui-react/src/compositions/menu.tsx delete mode 100644 packages/saas-ui-react/src/compositions/radio.tsx diff --git a/.changeset/dry-berries-report.md b/.changeset/dry-berries-report.md new file mode 100644 index 000000000..3fd0984ef --- /dev/null +++ b/.changeset/dry-berries-report.md @@ -0,0 +1,5 @@ +--- +'@saas-ui/react': minor +--- + +Export all components from root barrel file diff --git a/packages/saas-ui-react/src/components/avatar/avatar.tsx b/packages/saas-ui-react/src/components/avatar/avatar.tsx new file mode 100644 index 000000000..42b776dde --- /dev/null +++ b/packages/saas-ui-react/src/components/avatar/avatar.tsx @@ -0,0 +1,75 @@ +'use client' + +import { forwardRef } from 'react' + +import type { GroupProps, SlotRecipeProps } from '@chakra-ui/react' +import { Avatar as ChakraAvatar, Group } from '@chakra-ui/react' + +type ImageProps = React.ImgHTMLAttributes + +export interface AvatarProps extends ChakraAvatar.RootProps { + name?: string + src?: string + srcSet?: string + loading?: ImageProps['loading'] + icon?: React.ReactElement + fallback?: React.ReactNode +} + +export const Avatar = forwardRef( + function Avatar(props, ref) { + const { name, src, srcSet, loading, icon, fallback, children, ...rest } = + props + return ( + + + {fallback} + + + {children} + + ) + }, +) + +interface AvatarFallbackProps extends ChakraAvatar.FallbackProps { + name?: string + icon?: React.ReactElement +} + +const AvatarFallback = forwardRef( + function AvatarFallback(props, ref) { + const { name, icon, children, ...rest } = props + return ( + + {children} + {name != null && children == null && <>{getInitials(name)}} + {name == null && children == null && ( + {icon} + )} + + ) + }, +) + +function getInitials(name: string) { + const names = name.trim().split(' ') + const firstName = names[0] != null ? names[0] : '' + const lastName = names.length > 1 ? names[names.length - 1] : '' + return firstName && lastName + ? `${firstName.charAt(0)}${lastName.charAt(0)}` + : firstName.charAt(0) +} + +interface AvatarGroupProps extends GroupProps, SlotRecipeProps<'avatar'> {} + +export const AvatarGroup = forwardRef( + function AvatarGroup(props, ref) { + const { size, variant, borderless, ...rest } = props + return ( + + + + ) + }, +) diff --git a/packages/saas-ui-react/src/components/avatar/index.ts b/packages/saas-ui-react/src/components/avatar/index.ts new file mode 100644 index 000000000..8086dccb4 --- /dev/null +++ b/packages/saas-ui-react/src/components/avatar/index.ts @@ -0,0 +1 @@ +export * from './avatar.tsx' diff --git a/packages/saas-ui-react/src/components/menu/index.ts b/packages/saas-ui-react/src/components/menu/index.ts new file mode 100644 index 000000000..b4d161675 --- /dev/null +++ b/packages/saas-ui-react/src/components/menu/index.ts @@ -0,0 +1 @@ +export * as Menu from './menu.tsx' diff --git a/packages/saas-ui-react/src/components/menu/menu.tsx b/packages/saas-ui-react/src/components/menu/menu.tsx new file mode 100644 index 000000000..bce1261c8 --- /dev/null +++ b/packages/saas-ui-react/src/components/menu/menu.tsx @@ -0,0 +1,129 @@ +'use client' + +import { forwardRef } from 'react' + +import { AbsoluteCenter, Menu as ChakraMenu, Portal } from '@chakra-ui/react' +import { LuCheck, LuChevronRight } from 'react-icons/lu' + +interface MenuContentProps extends ChakraMenu.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +const MenuContent = forwardRef( + function MenuContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + }, +) + +const MenuArrow = forwardRef( + function MenuArrow(props, ref) { + return ( + + + + ) + }, +) + +const MenuCheckboxItem = forwardRef< + HTMLDivElement, + ChakraMenu.CheckboxItemProps +>(function MenuCheckboxItem(props, ref) { + return ( + + + {props.children} + + ) +}) + +const MenuRadioItem = forwardRef( + function MenuRadioItem(props, ref) { + const { children, ...rest } = props + return ( + + + + + + + {children} + + ) + }, +) + +const MenuItemGroup = forwardRef( + function MenuItemGroup(props, ref) { + const { title, children, ...rest } = props + return ( + + {title && ( + + {title} + + )} + {children} + + ) + }, +) + +interface MenuTriggerItemProps extends ChakraMenu.ItemProps { + startIcon?: React.ReactNode +} + +const MenuTriggerItem = forwardRef( + function MenuTriggerItem(props, ref) { + const { startIcon, children, ...rest } = props + return ( + + {startIcon} + {children} + + + ) + }, +) + +const MenuRadioItemGroup = ChakraMenu.RadioItemGroup +const MenuContextTrigger = ChakraMenu.ContextTrigger +const MenuRoot = ChakraMenu.Root +const MenuSeparator = ChakraMenu.Separator + +const MenuItem = ChakraMenu.Item +const MenuItemText = ChakraMenu.ItemText +const MenuItemCommand = ChakraMenu.ItemCommand +const MenuTrigger = ChakraMenu.Trigger + +export { + MenuRoot as Root, + MenuContent as Content, + MenuArrow as Arrow, + MenuCheckboxItem as CheckboxItem, + MenuRadioItem as RadioItem, + MenuItemGroup as ItemGroup, + MenuTriggerItem as TriggerItem, + MenuRadioItemGroup as RadioItemGroup, + MenuContextTrigger as ContextTrigger, + MenuSeparator as Separator, + MenuItem as Item, + MenuItemText as ItemText, + MenuItemCommand as ItemCommand, + MenuTrigger as Trigger, +} + +export type { + MenuContentProps as ContentProps, + MenuTriggerItemProps as TriggerItemProps, +} diff --git a/packages/saas-ui-react/src/components/segmented-control/index.ts b/packages/saas-ui-react/src/components/segmented-control/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/saas-ui-react/src/components/segmented-control/segmented-control.tsx b/packages/saas-ui-react/src/components/segmented-control/segmented-control.tsx new file mode 100644 index 000000000..4e442ad0d --- /dev/null +++ b/packages/saas-ui-react/src/components/segmented-control/segmented-control.tsx @@ -0,0 +1,48 @@ +'use client' + +import { forwardRef, useMemo } from 'react' + +import { For, SegmentGroup } from '@chakra-ui/react' + +interface Item { + value: string + label: React.ReactNode + disabled?: boolean +} + +export interface SegmentedControlProps extends SegmentGroup.RootProps { + items: Array +} + +function normalize(items: Array): Item[] { + return items.map((item) => { + if (typeof item === 'string') return { value: item, label: item } + return item + }) +} + +export const SegmentedControl = forwardRef< + HTMLDivElement, + SegmentedControlProps +>(function SegmentedControl(props, ref) { + const { items, ...rest } = props + const data = useMemo(() => normalize(items), [items]) + + return ( + + + + {(item) => ( + + {item.label} + + + )} + + + ) +}) diff --git a/packages/saas-ui-react/src/components/snackbar/index.ts b/packages/saas-ui-react/src/components/snackbar/index.ts new file mode 100644 index 000000000..0367c1ef2 --- /dev/null +++ b/packages/saas-ui-react/src/components/snackbar/index.ts @@ -0,0 +1,2 @@ +export * from './snackbar.tsx' +export { useSnackbar } from './snackbar.context.ts' diff --git a/packages/saas-ui-react/src/components/snackbar/snackbar.context.ts b/packages/saas-ui-react/src/components/snackbar/snackbar.context.ts new file mode 100644 index 000000000..76eaedbb8 --- /dev/null +++ b/packages/saas-ui-react/src/components/snackbar/snackbar.context.ts @@ -0,0 +1,10 @@ +import { type CreateToasterReturn, createContext } from '@chakra-ui/react' + +export interface SnackbarContextValue { + snackbar: CreateToasterReturn +} + +export const [SnackbarProvider, useSnackbar] = + createContext({ + name: 'SnackbarContext', + }) diff --git a/packages/saas-ui-react/src/components/snackbar/snackbar.tsx b/packages/saas-ui-react/src/components/snackbar/snackbar.tsx new file mode 100644 index 000000000..517bfc949 --- /dev/null +++ b/packages/saas-ui-react/src/components/snackbar/snackbar.tsx @@ -0,0 +1,61 @@ +'use client' + +import { useMemo } from 'react' + +import { + Toaster as ChakraToaster, + CreateToasterReturn, + Portal, + Spinner, + Stack, + Toast, + createToaster, +} from '@chakra-ui/react' + +import { SnackbarProvider } from './snackbar.context.ts' + +export interface SnackbarProps extends CreateToasterReturn { + children: React.ReactNode +} + +export const Snackbar = (props: SnackbarProps) => { + const { children, ...rest } = props + + const toaster = useMemo( + () => + createToaster({ + placement: 'bottom-end', + pauseOnPageIdle: true, + ...rest, + }), + [], + ) + + return ( + + + + {(toast) => ( + + {toast.type === 'loading' ? ( + + ) : ( + + )} + + {toast.title && {toast.title}} + {toast.description && ( + {toast.description} + )} + + {toast.action && ( + {toast.action.label} + )} + {toast.meta?.closable && } + + )} + + + + ) +} diff --git a/packages/saas-ui-react/src/components/tooltip/index.ts b/packages/saas-ui-react/src/components/tooltip/index.ts new file mode 100644 index 000000000..f4c15f52a --- /dev/null +++ b/packages/saas-ui-react/src/components/tooltip/index.ts @@ -0,0 +1 @@ +export * from './tooltip.tsx' diff --git a/packages/saas-ui-react/src/compositions/tooltip.tsx b/packages/saas-ui-react/src/components/tooltip/tooltip.tsx similarity index 91% rename from packages/saas-ui-react/src/compositions/tooltip.tsx rename to packages/saas-ui-react/src/components/tooltip/tooltip.tsx index f3eb53bcb..ffe4f179e 100644 --- a/packages/saas-ui-react/src/compositions/tooltip.tsx +++ b/packages/saas-ui-react/src/components/tooltip/tooltip.tsx @@ -1,5 +1,6 @@ -import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react" -import { forwardRef } from "react" +import { forwardRef } from 'react' + +import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react' export interface TooltipProps extends ChakraTooltip.RootProps { showArrow?: boolean diff --git a/packages/saas-ui-react/src/compositions/empty-state.tsx b/packages/saas-ui-react/src/compositions/empty-state.tsx deleted file mode 100644 index f489fff78..000000000 --- a/packages/saas-ui-react/src/compositions/empty-state.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { EmptyState as ChakraEmptyState, VStack } from "@chakra-ui/react" -import { forwardRef } from "react" - -export interface EmptyStateProps extends ChakraEmptyState.RootProps { - title: string - description?: string - icon?: React.ReactNode -} - -export const EmptyState = forwardRef( - function EmptyState(props, ref) { - const { title, description, icon, children, ...rest } = props - return ( - - - {icon && ( - {icon} - )} - {description ? ( - - {title} - - {description} - - - ) : ( - {title} - )} - {children} - - - ) - }, -) diff --git a/packages/saas-ui-react/src/compositions/menu.tsx b/packages/saas-ui-react/src/compositions/menu.tsx deleted file mode 100644 index 8c42e6554..000000000 --- a/packages/saas-ui-react/src/compositions/menu.tsx +++ /dev/null @@ -1,108 +0,0 @@ -"use client" - -import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react" -import { forwardRef } from "react" -import { LuCheck, LuChevronRight } from "react-icons/lu" - -interface MenuContentProps extends ChakraMenu.ContentProps { - portalled?: boolean - portalRef?: React.RefObject -} - -export const MenuContent = forwardRef( - function MenuContent(props, ref) { - const { portalled = true, portalRef, ...rest } = props - return ( - - - - - - ) - }, -) - -export const MenuArrow = forwardRef( - function MenuArrow(props, ref) { - return ( - - - - ) - }, -) - -export const MenuCheckboxItem = forwardRef< - HTMLDivElement, - ChakraMenu.CheckboxItemProps ->(function MenuCheckboxItem(props, ref) { - return ( - - - {props.children} - - ) -}) - -export const MenuRadioItem = forwardRef< - HTMLDivElement, - ChakraMenu.RadioItemProps ->(function MenuRadioItem(props, ref) { - const { children, ...rest } = props - return ( - - - - - - - {children} - - ) -}) - -export const MenuItemGroup = forwardRef< - HTMLDivElement, - ChakraMenu.ItemGroupProps ->(function MenuItemGroup(props, ref) { - const { title, children, ...rest } = props - return ( - - {title && ( - - {title} - - )} - {children} - - ) -}) - -export interface MenuTriggerItemProps extends ChakraMenu.ItemProps { - startIcon?: React.ReactNode -} - -export const MenuTriggerItem = forwardRef( - function MenuTriggerItem(props, ref) { - const { startIcon, children, ...rest } = props - return ( - - {startIcon} - {children} - - - ) - }, -) - -export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup -export const MenuContextTrigger = ChakraMenu.ContextTrigger -export const MenuRoot = ChakraMenu.Root -export const MenuSeparator = ChakraMenu.Separator - -export const MenuItem = ChakraMenu.Item -export const MenuItemText = ChakraMenu.ItemText -export const MenuItemCommand = ChakraMenu.ItemCommand -export const MenuTrigger = ChakraMenu.Trigger diff --git a/packages/saas-ui-react/src/compositions/radio.tsx b/packages/saas-ui-react/src/compositions/radio.tsx deleted file mode 100644 index 629ccac6c..000000000 --- a/packages/saas-ui-react/src/compositions/radio.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react" -import { forwardRef } from "react" - -export interface RadioProps extends ChakraRadioGroup.ItemProps { - rootRef?: React.Ref - inputProps?: React.InputHTMLAttributes -} - -export const Radio = forwardRef( - function Radio(props, ref) { - const { children, inputProps, rootRef, ...rest } = props - return ( - - - - {children && ( - {children} - )} - - ) - }, -) - -export const RadioGroup = ChakraRadioGroup.Root diff --git a/packages/saas-ui-react/src/index.ts b/packages/saas-ui-react/src/index.ts index f50d47a1e..b3c5b650b 100644 --- a/packages/saas-ui-react/src/index.ts +++ b/packages/saas-ui-react/src/index.ts @@ -1,7 +1,36 @@ -export * from '@saas-ui/core' - export { defaultSystem, defaultConfig } from './preset.ts' export { createSystem } from '@chakra-ui/react' export { SuiProvider, SuiContext, useLink, useSui } from './provider/index.ts' export type { SuiContextValue, SuiProviderProps } from './provider/index.ts' + +export * from './components/app-shell/index.ts' +export * from './components/avatar/index.ts' +export * from './components/breadcrumbs/index.ts' +export * from './components/button/index.ts' +export * from './components/checkbox/index.ts' +export * from './components/close-button/index.ts' +export * from './components/command/index.ts' +export * from './components/dialog/index.ts' +export * from './components/drawer/index.ts' +export * from './components/empty-state/index.ts' +export * from './components/grid-list/index.ts' +export * from './components/icon-badge/index.ts' +export * from './components/input-group/index.ts' +export * from './components/link/index.ts' +export * from './components/loading-overlay/index.ts' +export * from './components/navbar/index.ts' +export * from './components/number-input/index.ts' +export * from './components/password-input/index.ts' +export * from './components/persona/index.ts' +export * from './components/pin-input/index.ts' +export * from './components/radio/index.ts' +export * from './components/search-input/index.ts' +export * from './components/select/index.ts' +export * from './components/segmented-control/index.ts' +export * from './components/sidebar/index.ts' +export * from './components/snackbar/index.ts' +export * from './components/spinner/index.ts' +export * from './components/steps/index.ts' +export * from './components/switch/index.ts' +export * from './components/tooltip/index.ts'