From c872331c3b44e5cce91aa472f623e01f8a99bde4 Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Sat, 9 Mar 2024 07:31:57 +0000 Subject: [PATCH] feat: add ui-avatar and ui-select-enum components --- .../app/features/demo/demo-feature-avatar.tsx | 28 +++++++++++ .../app/features/demo/demo-feature-card.tsx | 1 + .../demo/demo-feature-grid-routes.tsx | 4 +- .../demo/demo-feature-select-enum.tsx | 25 ++++++++++ .../features/demo/demo-feature-tab-routes.tsx | 10 ++-- .../demo/demo-feature-theme-select.tsx | 3 +- .../src/app/features/demo/demo-feature.tsx | 4 ++ packages/core/src/lib/index.ts | 3 ++ packages/core/src/lib/ui-avatar/index.ts | 1 + packages/core/src/lib/ui-avatar/ui-avatar.tsx | 32 ++++++++++++ packages/core/src/lib/ui-card/ui-card.tsx | 12 ++--- .../ui-dashboard-grid/ui-dashboard-grid.tsx | 10 +--- .../src/lib/ui-helpers/get-color-by-index.ts | 4 ++ .../src/lib/ui-helpers/get-int-from-string.ts | 12 +++++ packages/core/src/lib/ui-helpers/index.ts | 2 + packages/core/src/lib/ui-select-enum/index.ts | 1 + .../src/lib/ui-select-enum/ui-select-enum.tsx | 50 +++++++++++++++++++ .../component/component-generator-schema.d.ts | 3 ++ .../component/component-generator-schema.json | 3 ++ .../generators/component/component-types.ts | 3 ++ .../__prefixFileName__-avatar.tsx.template | 32 ++++++++++++ .../component/files/avatar/index.ts.template | 1 + .../card/__prefixFileName__-card.tsx.template | 12 ++--- ...efixFileName__-dashboard-grid.tsx.template | 10 +--- .../helpers/get-color-by-index.ts.template | 4 ++ .../helpers/get-int-from-string.ts.template | 12 +++++ .../component/files/helpers/index.ts.template | 2 + ..._prefixFileName__-select-enum.tsx.template | 50 +++++++++++++++++++ .../files/select-enum/index.ts.template | 1 + .../demo/demo-feature-avatar.tsx.template | 28 +++++++++++ .../files/demo/demo-feature-card.tsx.template | 1 + .../demo-feature-grid-routes.tsx.template | 4 +- .../demo-feature-select-enum.tsx.template | 25 ++++++++++ .../demo/demo-feature-tab-routes.tsx.template | 10 ++-- .../files/demo/demo-feature.tsx.template | 4 ++ 35 files changed, 363 insertions(+), 44 deletions(-) create mode 100644 apps/web/src/app/features/demo/demo-feature-avatar.tsx create mode 100644 apps/web/src/app/features/demo/demo-feature-select-enum.tsx create mode 100644 packages/core/src/lib/ui-avatar/index.ts create mode 100644 packages/core/src/lib/ui-avatar/ui-avatar.tsx create mode 100644 packages/core/src/lib/ui-helpers/get-color-by-index.ts create mode 100644 packages/core/src/lib/ui-helpers/get-int-from-string.ts create mode 100644 packages/core/src/lib/ui-helpers/index.ts create mode 100644 packages/core/src/lib/ui-select-enum/index.ts create mode 100644 packages/core/src/lib/ui-select-enum/ui-select-enum.tsx create mode 100644 packages/generators/src/generators/component/files/avatar/__prefixFileName__-avatar.tsx.template create mode 100644 packages/generators/src/generators/component/files/avatar/index.ts.template create mode 100644 packages/generators/src/generators/component/files/helpers/get-color-by-index.ts.template create mode 100644 packages/generators/src/generators/component/files/helpers/get-int-from-string.ts.template create mode 100644 packages/generators/src/generators/component/files/helpers/index.ts.template create mode 100644 packages/generators/src/generators/component/files/select-enum/__prefixFileName__-select-enum.tsx.template create mode 100644 packages/generators/src/generators/component/files/select-enum/index.ts.template create mode 100644 packages/generators/src/generators/feature/files/demo/demo-feature-avatar.tsx.template create mode 100644 packages/generators/src/generators/feature/files/demo/demo-feature-select-enum.tsx.template diff --git a/apps/web/src/app/features/demo/demo-feature-avatar.tsx b/apps/web/src/app/features/demo/demo-feature-avatar.tsx new file mode 100644 index 0000000..f9cb572 --- /dev/null +++ b/apps/web/src/app/features/demo/demo-feature-avatar.tsx @@ -0,0 +1,28 @@ +import { Group, SimpleGrid } from '@mantine/core' +import { UiAvatar, UiCard } from '@pubkey-ui/core' + +export function DemoFeatureAvatar() { + return ( + + + + + + + + + + + + + + + + + + ) +} diff --git a/apps/web/src/app/features/demo/demo-feature-card.tsx b/apps/web/src/app/features/demo/demo-feature-card.tsx index bc0c8f8..436f82c 100644 --- a/apps/web/src/app/features/demo/demo-feature-card.tsx +++ b/apps/web/src/app/features/demo/demo-feature-card.tsx @@ -7,6 +7,7 @@ export function DemoFeatureCard() { CARD CONTENT + ) } diff --git a/apps/web/src/app/features/demo/demo-feature-grid-routes.tsx b/apps/web/src/app/features/demo/demo-feature-grid-routes.tsx index f70fc1b..a07d62f 100644 --- a/apps/web/src/app/features/demo/demo-feature-grid-routes.tsx +++ b/apps/web/src/app/features/demo/demo-feature-grid-routes.tsx @@ -2,11 +2,11 @@ import { Badge, SimpleGrid } from '@mantine/core' import { UiCard, UiGridRoutes } from '@pubkey-ui/core' import { IconDashboard } from '@tabler/icons-react' -export function DemoFeatureGridRoutes() { +export function DemoFeatureGridRoutes({ basePath = '/demo/grid-routes' }: { basePath?: string }) { return ( (undefined) + const [values, setValues] = useState(undefined) + return ( + + + + value={value} setValue={setValue} options={getEnumOptions(DemoEnum)} /> + + values={values} setValues={setValues} options={getEnumOptions(DemoEnum)} /> + + + + + ) +} diff --git a/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx b/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx index 5f6de49..fb77930 100644 --- a/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx +++ b/apps/web/src/app/features/demo/demo-feature-tab-routes.tsx @@ -1,18 +1,18 @@ import { SimpleGrid } from '@mantine/core' import { UiCard, UiTabRoutes } from '@pubkey-ui/core' -export function DemoFeatureTabRoutes() { +export function DemoFeatureTabRoutes({ basePath = '/demo/tab-routes' }: { basePath?: string }) { return ( - Overview + Dashboard ), }, diff --git a/apps/web/src/app/features/demo/demo-feature-theme-select.tsx b/apps/web/src/app/features/demo/demo-feature-theme-select.tsx index 56f5640..83df3cf 100644 --- a/apps/web/src/app/features/demo/demo-feature-theme-select.tsx +++ b/apps/web/src/app/features/demo/demo-feature-theme-select.tsx @@ -1,5 +1,5 @@ import { Button, Group } from '@mantine/core' -import { UiCard, UiDebugModal, UiStack, UiThemeSelect, useUiThemeSelect } from '@pubkey-ui/core' +import { UiCard, UiStack, UiThemeSelect, useUiThemeSelect } from '@pubkey-ui/core' export function DemoFeatureThemeSelect() { const { themes, selected, selectTheme } = useUiThemeSelect() @@ -17,7 +17,6 @@ export function DemoFeatureThemeSelect() { ))} - ) } diff --git a/apps/web/src/app/features/demo/demo-feature.tsx b/apps/web/src/app/features/demo/demo-feature.tsx index 99f1de2..d33fe6a 100644 --- a/apps/web/src/app/features/demo/demo-feature.tsx +++ b/apps/web/src/app/features/demo/demo-feature.tsx @@ -2,6 +2,7 @@ import { UiGridRoutes, UiPage } from '@pubkey-ui/core' import { ReactNode } from 'react' import { DemoFeatureAlerts } from './demo-feature-alerts' import { DemoFeatureAnchor } from './demo-feature-anchor' +import { DemoFeatureAvatar } from './demo-feature-avatar' import { DemoFeatureBack } from './demo-feature-back' import { DemoFeatureCard } from './demo-feature-card' import { DemoFeatureCopy } from './demo-feature-copy' @@ -17,6 +18,7 @@ import { DemoFeatureMenu } from './demo-feature-menu' import { DemoFeatureNotFound } from './demo-feature-not-found' import { DemoFeaturePage } from './demo-feature-page' import { DemoFeatureSearchInput } from './demo-feature-search-input' +import { DemoFeatureSelectEnum } from './demo-feature-select-enum' import { DemoFeatureStack } from './demo-feature-stack' import { DemoFeatureTabRoutes } from './demo-feature-tab-routes' import { DemoFeatureThemeSelect } from './demo-feature-theme-select' @@ -31,6 +33,7 @@ export function DemoFeature() { }[] = [ { path: 'alerts', label: 'Alerts', element: }, { path: 'anchor', label: 'Anchor', element: }, + { path: 'avatar', label: 'Avatar', element: }, { path: 'back', label: 'Back', element: }, { path: 'card', label: 'Card', element: }, { path: 'copy', label: 'Copy', element: }, @@ -46,6 +49,7 @@ export function DemoFeature() { { path: 'not-found', label: 'Not Found', element: }, { path: 'page', label: 'Page', element: }, { path: 'search-input', label: 'Search Input', element: }, + { path: 'select-enum', label: 'Select Enum', element: }, { path: 'stack', label: 'Stack', element: }, { path: 'tab-routes', label: 'Tab Routes', element: }, { path: 'theme-select', label: 'Theme Select', element: }, diff --git a/packages/core/src/lib/index.ts b/packages/core/src/lib/index.ts index 3ef0308..8d655b6 100644 --- a/packages/core/src/lib/index.ts +++ b/packages/core/src/lib/index.ts @@ -1,5 +1,6 @@ export * from './ui-alert' export * from './ui-anchor' +export * from './ui-avatar' export * from './ui-back' export * from './ui-card' export * from './ui-container' @@ -10,6 +11,7 @@ export * from './ui-form' export * from './ui-grid-routes' export * from './ui-group' export * from './ui-header' +export * from './ui-helpers' export * from './ui-layout' export * from './ui-loader' export * from './ui-logo' @@ -17,6 +19,7 @@ export * from './ui-menu' export * from './ui-not-found' export * from './ui-page' export * from './ui-search-input' +export * from './ui-select-enum' export * from './ui-stack' export * from './ui-tab-routes' export * from './ui-theme' diff --git a/packages/core/src/lib/ui-avatar/index.ts b/packages/core/src/lib/ui-avatar/index.ts new file mode 100644 index 0000000..7702f6f --- /dev/null +++ b/packages/core/src/lib/ui-avatar/index.ts @@ -0,0 +1 @@ +export * from './ui-avatar' diff --git a/packages/core/src/lib/ui-avatar/ui-avatar.tsx b/packages/core/src/lib/ui-avatar/ui-avatar.tsx new file mode 100644 index 0000000..e1b4244 --- /dev/null +++ b/packages/core/src/lib/ui-avatar/ui-avatar.tsx @@ -0,0 +1,32 @@ +import { Avatar, AvatarProps, Tooltip } from '@mantine/core' +import { getColorByIndex, getIntFromString } from '../ui-helpers' +import { UiAnchor } from '../ui-anchor' + +export type UiAvatarProps = Omit & { + url?: string | null + name?: string | null + to?: string + tooltipLabel?: string +} + +export function UiAvatar({ url, name, to, tooltipLabel, ...props }: UiAvatarProps) { + const firstLetter = name?.charAt(0) ?? '?' + + const content = url?.length ? ( + + ) : ( + + {firstLetter?.toUpperCase()} + + ) + + const anchor = {content} + + return tooltipLabel ? ( + + {anchor} + + ) : ( + anchor + ) +} diff --git a/packages/core/src/lib/ui-card/ui-card.tsx b/packages/core/src/lib/ui-card/ui-card.tsx index 5abeaf0..5eab231 100644 --- a/packages/core/src/lib/ui-card/ui-card.tsx +++ b/packages/core/src/lib/ui-card/ui-card.tsx @@ -1,10 +1,10 @@ -import { Box, Paper, PaperProps, Skeleton } from '@mantine/core' +import { Box, Paper, PaperProps, Skeleton, Stack } from '@mantine/core' import { useUiBreakpoints } from '../ui-theme' import { ReactNode } from 'react' import { UiCardTitle } from './ui-card-title' interface UiCardProps extends PaperProps { - children: ReactNode + children?: ReactNode loading?: boolean title?: ReactNode } @@ -14,10 +14,10 @@ export function UiCard({ loading, title, ...props }: UiCardProps) { return ( - {title ? ( - {typeof title === 'string' ? {title} : title} - ) : null} - {loading ? {props.children} : props.children} + + {title ? {typeof title === 'string' ? {title} : title} : null} + {props.children ? loading ? {props.children} : props.children : null} + ) } diff --git a/packages/core/src/lib/ui-dashboard-grid/ui-dashboard-grid.tsx b/packages/core/src/lib/ui-dashboard-grid/ui-dashboard-grid.tsx index e0a58a2..60aacf1 100644 --- a/packages/core/src/lib/ui-dashboard-grid/ui-dashboard-grid.tsx +++ b/packages/core/src/lib/ui-dashboard-grid/ui-dashboard-grid.tsx @@ -1,15 +1,9 @@ import { SimpleGrid, Text, UnstyledButton, useMantineTheme } from '@mantine/core' -import { useUiTheme } from '../ui-theme' import { ComponentType } from 'react' - +import { getColorByIndex } from '../ui-helpers' +import { useUiTheme } from '../ui-theme' import classes from './ui-dashboard-grid.module.css' -const linkColors = ['violet', 'indigo', 'blue', 'green', 'teal', 'cyan', 'pink', 'red', 'orange'] - -export function getColorByIndex(index: number) { - return linkColors[index % linkColors.length] -} - export interface UiDashboardItem { icon: ComponentType<{ color?: string; size: number | string }> label: string diff --git a/packages/core/src/lib/ui-helpers/get-color-by-index.ts b/packages/core/src/lib/ui-helpers/get-color-by-index.ts new file mode 100644 index 0000000..772d8ab --- /dev/null +++ b/packages/core/src/lib/ui-helpers/get-color-by-index.ts @@ -0,0 +1,4 @@ +export const colorByIndex = ['violet', 'indigo', 'blue', 'green', 'teal', 'cyan', 'pink', 'red', 'orange'] +export function getColorByIndex(index: number, colors: string[] = colorByIndex) { + return colors[index % colors.length] +} diff --git a/packages/core/src/lib/ui-helpers/get-int-from-string.ts b/packages/core/src/lib/ui-helpers/get-int-from-string.ts new file mode 100644 index 0000000..ff39aa4 --- /dev/null +++ b/packages/core/src/lib/ui-helpers/get-int-from-string.ts @@ -0,0 +1,12 @@ +export function getIntFromString(str: string) { + let hash = 0 + if (str.length == 0) { + return hash + } + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i) + hash = (hash << 5) - hash + char + hash = hash & hash // Convert to 32bit integer + } + return Math.abs(hash) +} diff --git a/packages/core/src/lib/ui-helpers/index.ts b/packages/core/src/lib/ui-helpers/index.ts new file mode 100644 index 0000000..2183c73 --- /dev/null +++ b/packages/core/src/lib/ui-helpers/index.ts @@ -0,0 +1,2 @@ +export * from './get-color-by-index' +export * from './get-int-from-string' diff --git a/packages/core/src/lib/ui-select-enum/index.ts b/packages/core/src/lib/ui-select-enum/index.ts new file mode 100644 index 0000000..fe926fe --- /dev/null +++ b/packages/core/src/lib/ui-select-enum/index.ts @@ -0,0 +1 @@ +export * from './ui-select-enum' diff --git a/packages/core/src/lib/ui-select-enum/ui-select-enum.tsx b/packages/core/src/lib/ui-select-enum/ui-select-enum.tsx new file mode 100644 index 0000000..dbe6a22 --- /dev/null +++ b/packages/core/src/lib/ui-select-enum/ui-select-enum.tsx @@ -0,0 +1,50 @@ +import { MultiSelect, MultiSelectProps, Select, SelectProps } from '@mantine/core' + +export function UiMultiSelectEnum({ + values, + setValues, + options, + ...props +}: MultiSelectProps & { + values: T[] | undefined + setValues: (values: T[] | undefined) => void + options: { value: string; label: string }[] +}) { + return ( + `${v}`) ?? []} + onChange={(values) => setValues(values.map((v) => v as T))} + data={options} + {...props} + /> + ) +} + +export function UiSelectEnum({ + value, + setValue, + options, + ...props +}: SelectProps & { + value: T | undefined + setValue: (value: T | undefined) => void + options: { value: string; label: string }[] +}) { + return ( + setValue(value === '' ? undefined : (value as T))} + data={options} + {...props} + /> + ) +} + +export function getEnumOptions>( + enumObject: T, +): { label: string; value: T[keyof T] }[] { + return Object.keys(enumObject).map((key: string) => ({ + label: key, + value: enumObject[key as keyof T], + })) +} diff --git a/packages/generators/src/generators/component/files/select-enum/index.ts.template b/packages/generators/src/generators/component/files/select-enum/index.ts.template new file mode 100644 index 0000000..5dfd8c4 --- /dev/null +++ b/packages/generators/src/generators/component/files/select-enum/index.ts.template @@ -0,0 +1 @@ +export * from './<%= prefixFileName %>-select-enum' diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-avatar.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-avatar.tsx.template new file mode 100644 index 0000000..f9cb572 --- /dev/null +++ b/packages/generators/src/generators/feature/files/demo/demo-feature-avatar.tsx.template @@ -0,0 +1,28 @@ +import { Group, SimpleGrid } from '@mantine/core' +import { UiAvatar, UiCard } from '@pubkey-ui/core' + +export function DemoFeatureAvatar() { + return ( + + + + + + + + + + + + + + + + + + ) +} diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template index 0e139ac..36edb2e 100644 --- a/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template +++ b/packages/generators/src/generators/feature/files/demo/demo-feature-card.tsx.template @@ -7,6 +7,7 @@ export function DemoFeatureCard() { <<%= prefix.className %>Card withBorder title="Card" shadow="lg"> CARD CONTENT Card> + Stack> ) } diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-grid-routes.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-grid-routes.tsx.template index 8905f4d..fc799d7 100644 --- a/packages/generators/src/generators/feature/files/demo/demo-feature-grid-routes.tsx.template +++ b/packages/generators/src/generators/feature/files/demo/demo-feature-grid-routes.tsx.template @@ -2,11 +2,11 @@ import { Badge, SimpleGrid } from '@mantine/core' import { <%= prefix.className %>Card, <%= prefix.className %>GridRoutes } from '<%= uiImport %>' import { IconDashboard } from '@tabler/icons-react' -export function DemoFeatureGridRoutes() { +export function DemoFeatureGridRoutes({ basePath = '/demo/grid-routes' }: { basePath?: string }) { return ( <<%= prefix.className %>Card title="Grid Routes"> <<%= prefix.className %>GridRoutes - basePath="/demo/grid-routes" + basePath={basePath} routes={[ { path: 'dashboard', diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-select-enum.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-select-enum.tsx.template new file mode 100644 index 0000000..e035564 --- /dev/null +++ b/packages/generators/src/generators/feature/files/demo/demo-feature-select-enum.tsx.template @@ -0,0 +1,25 @@ +import { SimpleGrid } from '@mantine/core' +import { getEnumOptions, UiCard, UiDebug, UiMultiSelectEnum, UiSelectEnum, UiStack } from '@pubkey-ui/core' +import { useState } from 'react' + +enum DemoEnum { + One = 'One', + Two = 'Two', + Three = 'Three', +} +export function DemoFeatureSelectEnum() { + const [value, setValue] = useState(undefined) + const [values, setValues] = useState(undefined) + return ( + + + + value={value} setValue={setValue} options={getEnumOptions(DemoEnum)} /> + + values={values} setValues={setValues} options={getEnumOptions(DemoEnum)} /> + + + + + ) +} diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template index 026ed70..d4fad40 100644 --- a/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template +++ b/packages/generators/src/generators/feature/files/demo/demo-feature-tab-routes.tsx.template @@ -1,18 +1,18 @@ import { SimpleGrid } from '@mantine/core' import { <%= prefix.className %>Card, <%= prefix.className %>TabRoutes } from '<%= uiImport %>' -export function DemoFeatureTabRoutes() { +export function DemoFeatureTabRoutes({ basePath = '/demo/tab-routes' }: { basePath?: string }) { return ( <<%= prefix.className %>Card title="Tab Routes"> <<%= prefix.className %>TabRoutes - basePath="/demo/tab-routes" + basePath={basePath} tabs={[ { - path: 'overview', - label: 'Overview', + path: 'dashboard', + label: 'Dashboard', element: ( - <<%= prefix.className %>Card title="Overview">OverviewCard> + <<%= prefix.className %>Card title="Dashboard">DashboardCard> ), }, diff --git a/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template b/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template index 0fb36ac..98a4ad0 100644 --- a/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template +++ b/packages/generators/src/generators/feature/files/demo/demo-feature.tsx.template @@ -2,6 +2,7 @@ import { <%= prefix.className %>GridRoutes, <%= prefix.className %>Page } from ' import { ReactNode } from 'react' import { DemoFeatureAlerts } from './demo-feature-alerts' import { DemoFeatureAnchor } from './demo-feature-anchor' +import { DemoFeatureAvatar } from './demo-feature-avatar' import { DemoFeatureBack } from './demo-feature-back' import { DemoFeatureCard } from './demo-feature-card' import { DemoFeatureCopy } from './demo-feature-copy' @@ -17,6 +18,7 @@ import { DemoFeatureMenu } from './demo-feature-menu' import { DemoFeatureNotFound } from './demo-feature-not-found' import { DemoFeaturePage } from './demo-feature-page' import { DemoFeatureSearchInput } from './demo-feature-search-input' +import { DemoFeatureSelectEnum } from './demo-feature-select-enum' import { DemoFeatureStack } from './demo-feature-stack' import { DemoFeatureTabRoutes } from './demo-feature-tab-routes' import { DemoFeatureThemeSelect } from './demo-feature-theme-select' @@ -31,6 +33,7 @@ export function DemoFeature() { }[] = [ { path: 'alerts', label: 'Alerts', element: }, { path: 'anchor', label: 'Anchor', element: }, + { path: 'avatar', label: 'Avatar', element: }, { path: 'back', label: 'Back', element: }, { path: 'card', label: 'Card', element: }, { path: 'copy', label: 'Copy', element: }, @@ -46,6 +49,7 @@ export function DemoFeature() { { path: 'not-found', label: 'Not Found', element: }, { path: 'page', label: 'Page', element: }, { path: 'search-input', label: 'Search Input', element: }, + { path: 'select-enum', label: 'Select Enum', element: }, { path: 'stack', label: 'Stack', element: }, { path: 'tab-routes', label: 'Tab Routes', element: }, { path: 'theme-select', label: 'Theme Select', element: },