Skip to content

Commit c872331

Browse files
committed
feat: add ui-avatar and ui-select-enum components
1 parent d044402 commit c872331

35 files changed

+363
-44
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Group, SimpleGrid } from '@mantine/core'
2+
import { UiAvatar, UiCard } from '@pubkey-ui/core'
3+
4+
export function DemoFeatureAvatar() {
5+
return (
6+
<UiCard title="Avatar">
7+
<SimpleGrid cols={6}>
8+
<Group justify="center">
9+
<UiAvatar name="John Doe" tooltipLabel="John Doe" />
10+
<UiAvatar name="Jane Doe" tooltipLabel="Jane Doe" />
11+
<UiAvatar name="Jack Doe" tooltipLabel="Jack Doe" />
12+
<UiAvatar name="Jill Doe" tooltipLabel="Jill Doe" />
13+
<UiAvatar name="Jay Doe" tooltipLabel="Jay Doe" />
14+
<UiAvatar name="Joanne Doe" tooltipLabel="Joanne Doe" />
15+
</Group>
16+
<Group justify="center">
17+
<UiAvatar url={'https://avatars.githubusercontent.com/u/36491?v=4'} />
18+
<UiAvatar size="lg" url={'https://avatars.githubusercontent.com/u/36491?v=4'} />
19+
<UiAvatar
20+
to="https://github.com/beeman"
21+
size="lg"
22+
url={'https://avatars.githubusercontent.com/u/36491?v=4'}
23+
/>
24+
</Group>
25+
</SimpleGrid>
26+
</UiCard>
27+
)
28+
}

apps/web/src/app/features/demo/demo-feature-card.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export function DemoFeatureCard() {
77
<UiCard withBorder title="Card" shadow="lg">
88
CARD CONTENT
99
</UiCard>
10+
<UiCard withBorder title="Empty Card" shadow="lg" />
1011
</UiStack>
1112
)
1213
}

apps/web/src/app/features/demo/demo-feature-grid-routes.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { Badge, SimpleGrid } from '@mantine/core'
22
import { UiCard, UiGridRoutes } from '@pubkey-ui/core'
33
import { IconDashboard } from '@tabler/icons-react'
44

5-
export function DemoFeatureGridRoutes() {
5+
export function DemoFeatureGridRoutes({ basePath = '/demo/grid-routes' }: { basePath?: string }) {
66
return (
77
<UiCard title="Grid Routes">
88
<UiGridRoutes
9-
basePath="/demo/grid-routes"
9+
basePath={basePath}
1010
routes={[
1111
{
1212
path: 'dashboard',
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { SimpleGrid } from '@mantine/core'
2+
import { getEnumOptions, UiCard, UiDebug, UiMultiSelectEnum, UiSelectEnum, UiStack } from '@pubkey-ui/core'
3+
import { useState } from 'react'
4+
5+
enum DemoEnum {
6+
One = 'One',
7+
Two = 'Two',
8+
Three = 'Three',
9+
}
10+
export function DemoFeatureSelectEnum() {
11+
const [value, setValue] = useState<DemoEnum | undefined>(undefined)
12+
const [values, setValues] = useState<DemoEnum[] | undefined>(undefined)
13+
return (
14+
<UiCard title="SelectEnum">
15+
<UiStack>
16+
<SimpleGrid cols={2}>
17+
<UiSelectEnum<DemoEnum> value={value} setValue={setValue} options={getEnumOptions(DemoEnum)} />
18+
<UiDebug data={{ value }} open />
19+
<UiMultiSelectEnum<DemoEnum> values={values} setValues={setValues} options={getEnumOptions(DemoEnum)} />
20+
<UiDebug data={{ values }} open />
21+
</SimpleGrid>
22+
</UiStack>
23+
</UiCard>
24+
)
25+
}

apps/web/src/app/features/demo/demo-feature-tab-routes.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { SimpleGrid } from '@mantine/core'
22
import { UiCard, UiTabRoutes } from '@pubkey-ui/core'
33

4-
export function DemoFeatureTabRoutes() {
4+
export function DemoFeatureTabRoutes({ basePath = '/demo/tab-routes' }: { basePath?: string }) {
55
return (
66
<UiCard title="Tab Routes">
77
<UiTabRoutes
8-
basePath="/demo/tab-routes"
8+
basePath={basePath}
99
tabs={[
1010
{
11-
path: 'overview',
12-
label: 'Overview',
11+
path: 'dashboard',
12+
label: 'Dashboard',
1313
element: (
1414
<SimpleGrid cols={2} spacing="md">
15-
<UiCard title="Overview">Overview</UiCard>
15+
<UiCard title="Dashboard">Dashboard</UiCard>
1616
</SimpleGrid>
1717
),
1818
},

apps/web/src/app/features/demo/demo-feature-theme-select.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, Group } from '@mantine/core'
2-
import { UiCard, UiDebugModal, UiStack, UiThemeSelect, useUiThemeSelect } from '@pubkey-ui/core'
2+
import { UiCard, UiStack, UiThemeSelect, useUiThemeSelect } from '@pubkey-ui/core'
33

44
export function DemoFeatureThemeSelect() {
55
const { themes, selected, selectTheme } = useUiThemeSelect()
@@ -17,7 +17,6 @@ export function DemoFeatureThemeSelect() {
1717
))}
1818
</Group>
1919
</UiCard>
20-
<UiDebugModal data={{ themes, selected }} />
2120
</UiStack>
2221
)
2322
}

apps/web/src/app/features/demo/demo-feature.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { UiGridRoutes, UiPage } from '@pubkey-ui/core'
22
import { ReactNode } from 'react'
33
import { DemoFeatureAlerts } from './demo-feature-alerts'
44
import { DemoFeatureAnchor } from './demo-feature-anchor'
5+
import { DemoFeatureAvatar } from './demo-feature-avatar'
56
import { DemoFeatureBack } from './demo-feature-back'
67
import { DemoFeatureCard } from './demo-feature-card'
78
import { DemoFeatureCopy } from './demo-feature-copy'
@@ -17,6 +18,7 @@ import { DemoFeatureMenu } from './demo-feature-menu'
1718
import { DemoFeatureNotFound } from './demo-feature-not-found'
1819
import { DemoFeaturePage } from './demo-feature-page'
1920
import { DemoFeatureSearchInput } from './demo-feature-search-input'
21+
import { DemoFeatureSelectEnum } from './demo-feature-select-enum'
2022
import { DemoFeatureStack } from './demo-feature-stack'
2123
import { DemoFeatureTabRoutes } from './demo-feature-tab-routes'
2224
import { DemoFeatureThemeSelect } from './demo-feature-theme-select'
@@ -31,6 +33,7 @@ export function DemoFeature() {
3133
}[] = [
3234
{ path: 'alerts', label: 'Alerts', element: <DemoFeatureAlerts /> },
3335
{ path: 'anchor', label: 'Anchor', element: <DemoFeatureAnchor /> },
36+
{ path: 'avatar', label: 'Avatar', element: <DemoFeatureAvatar /> },
3437
{ path: 'back', label: 'Back', element: <DemoFeatureBack /> },
3538
{ path: 'card', label: 'Card', element: <DemoFeatureCard /> },
3639
{ path: 'copy', label: 'Copy', element: <DemoFeatureCopy /> },
@@ -46,6 +49,7 @@ export function DemoFeature() {
4649
{ path: 'not-found', label: 'Not Found', element: <DemoFeatureNotFound /> },
4750
{ path: 'page', label: 'Page', element: <DemoFeaturePage /> },
4851
{ path: 'search-input', label: 'Search Input', element: <DemoFeatureSearchInput /> },
52+
{ path: 'select-enum', label: 'Select Enum', element: <DemoFeatureSelectEnum /> },
4953
{ path: 'stack', label: 'Stack', element: <DemoFeatureStack /> },
5054
{ path: 'tab-routes', label: 'Tab Routes', element: <DemoFeatureTabRoutes /> },
5155
{ path: 'theme-select', label: 'Theme Select', element: <DemoFeatureThemeSelect /> },

packages/core/src/lib/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './ui-alert'
22
export * from './ui-anchor'
3+
export * from './ui-avatar'
34
export * from './ui-back'
45
export * from './ui-card'
56
export * from './ui-container'
@@ -10,13 +11,15 @@ export * from './ui-form'
1011
export * from './ui-grid-routes'
1112
export * from './ui-group'
1213
export * from './ui-header'
14+
export * from './ui-helpers'
1315
export * from './ui-layout'
1416
export * from './ui-loader'
1517
export * from './ui-logo'
1618
export * from './ui-menu'
1719
export * from './ui-not-found'
1820
export * from './ui-page'
1921
export * from './ui-search-input'
22+
export * from './ui-select-enum'
2023
export * from './ui-stack'
2124
export * from './ui-tab-routes'
2225
export * from './ui-theme'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ui-avatar'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Avatar, AvatarProps, Tooltip } from '@mantine/core'
2+
import { getColorByIndex, getIntFromString } from '../ui-helpers'
3+
import { UiAnchor } from '../ui-anchor'
4+
5+
export type UiAvatarProps = Omit<AvatarProps, 'src'> & {
6+
url?: string | null
7+
name?: string | null
8+
to?: string
9+
tooltipLabel?: string
10+
}
11+
12+
export function UiAvatar({ url, name, to, tooltipLabel, ...props }: UiAvatarProps) {
13+
const firstLetter = name?.charAt(0) ?? '?'
14+
15+
const content = url?.length ? (
16+
<Avatar radius={100} src={url} alt={`${name} avatar`} {...props} />
17+
) : (
18+
<Avatar radius={100} color={getColorByIndex(getIntFromString(name ?? ''))} {...props}>
19+
{firstLetter?.toUpperCase()}
20+
</Avatar>
21+
)
22+
23+
const anchor = <UiAnchor to={to}>{content}</UiAnchor>
24+
25+
return tooltipLabel ? (
26+
<Tooltip label={tooltipLabel} withArrow>
27+
{anchor}
28+
</Tooltip>
29+
) : (
30+
anchor
31+
)
32+
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Box, Paper, PaperProps, Skeleton } from '@mantine/core'
1+
import { Box, Paper, PaperProps, Skeleton, Stack } from '@mantine/core'
22
import { useUiBreakpoints } from '../ui-theme'
33
import { ReactNode } from 'react'
44
import { UiCardTitle } from './ui-card-title'
55

66
interface UiCardProps extends PaperProps {
7-
children: ReactNode
7+
children?: ReactNode
88
loading?: boolean
99
title?: ReactNode
1010
}
@@ -14,10 +14,10 @@ export function UiCard({ loading, title, ...props }: UiCardProps) {
1414

1515
return (
1616
<Paper p={isSm ? 'xs' : 'md'} withBorder {...props}>
17-
{title ? (
18-
<Box mb={isSm ? 'xs' : 'md'}>{typeof title === 'string' ? <UiCardTitle>{title}</UiCardTitle> : title}</Box>
19-
) : null}
20-
{loading ? <Skeleton visible={loading}>{props.children}</Skeleton> : props.children}
17+
<Stack gap={isSm ? 'xs' : 'md'}>
18+
{title ? <Box>{typeof title === 'string' ? <UiCardTitle>{title}</UiCardTitle> : title}</Box> : null}
19+
{props.children ? loading ? <Skeleton visible={loading}>{props.children}</Skeleton> : props.children : null}
20+
</Stack>
2121
</Paper>
2222
)
2323
}

packages/core/src/lib/ui-dashboard-grid/ui-dashboard-grid.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import { SimpleGrid, Text, UnstyledButton, useMantineTheme } from '@mantine/core'
2-
import { useUiTheme } from '../ui-theme'
32
import { ComponentType } from 'react'
4-
3+
import { getColorByIndex } from '../ui-helpers'
4+
import { useUiTheme } from '../ui-theme'
55
import classes from './ui-dashboard-grid.module.css'
66

7-
const linkColors = ['violet', 'indigo', 'blue', 'green', 'teal', 'cyan', 'pink', 'red', 'orange']
8-
9-
export function getColorByIndex(index: number) {
10-
return linkColors[index % linkColors.length]
11-
}
12-
137
export interface UiDashboardItem {
148
icon: ComponentType<{ color?: string; size: number | string }>
159
label: string
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const colorByIndex = ['violet', 'indigo', 'blue', 'green', 'teal', 'cyan', 'pink', 'red', 'orange']
2+
export function getColorByIndex(index: number, colors: string[] = colorByIndex) {
3+
return colors[index % colors.length]
4+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function getIntFromString(str: string) {
2+
let hash = 0
3+
if (str.length == 0) {
4+
return hash
5+
}
6+
for (let i = 0; i < str.length; i++) {
7+
const char = str.charCodeAt(i)
8+
hash = (hash << 5) - hash + char
9+
hash = hash & hash // Convert to 32bit integer
10+
}
11+
return Math.abs(hash)
12+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './get-color-by-index'
2+
export * from './get-int-from-string'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ui-select-enum'
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { MultiSelect, MultiSelectProps, Select, SelectProps } from '@mantine/core'
2+
3+
export function UiMultiSelectEnum<T>({
4+
values,
5+
setValues,
6+
options,
7+
...props
8+
}: MultiSelectProps & {
9+
values: T[] | undefined
10+
setValues: (values: T[] | undefined) => void
11+
options: { value: string; label: string }[]
12+
}) {
13+
return (
14+
<MultiSelect
15+
value={values?.map((v) => `${v}`) ?? []}
16+
onChange={(values) => setValues(values.map((v) => v as T))}
17+
data={options}
18+
{...props}
19+
/>
20+
)
21+
}
22+
23+
export function UiSelectEnum<T>({
24+
value,
25+
setValue,
26+
options,
27+
...props
28+
}: SelectProps & {
29+
value: T | undefined
30+
setValue: (value: T | undefined) => void
31+
options: { value: string; label: string }[]
32+
}) {
33+
return (
34+
<Select
35+
value={value?.toString() ?? ''}
36+
onChange={(value) => setValue(value === '' ? undefined : (value as T))}
37+
data={options}
38+
{...props}
39+
/>
40+
)
41+
}
42+
43+
export function getEnumOptions<T extends Record<string, string>>(
44+
enumObject: T,
45+
): { label: string; value: T[keyof T] }[] {
46+
return Object.keys(enumObject).map((key: string) => ({
47+
label: key,
48+
value: enumObject[key as keyof T],
49+
}))
50+
}

packages/generators/src/generators/component/component-generator-schema.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface ComponentGeneratorSchema {
1616
type:
1717
| 'alert'
1818
| 'anchor'
19+
| 'avatar'
1920
| 'back'
2021
| 'card'
2122
| 'container'
@@ -26,13 +27,15 @@ export interface ComponentGeneratorSchema {
2627
| 'grid-routes'
2728
| 'group'
2829
| 'header'
30+
| 'helpers'
2931
| 'layout'
3032
| 'loader'
3133
| 'logo'
3234
| 'menu'
3335
| 'not-found'
3436
| 'page'
3537
| 'search-input'
38+
| 'select-enum'
3639
| 'stack'
3740
| 'tab-routes'
3841
| 'theme'

packages/generators/src/generators/component/component-generator-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"enum": [
1919
"alert",
2020
"anchor",
21+
"avatar",
2122
"back",
2223
"card",
2324
"container",
@@ -28,13 +29,15 @@
2829
"grid-routes",
2930
"group",
3031
"header",
32+
"helpers",
3133
"layout",
3234
"loader",
3335
"logo",
3436
"menu",
3537
"not-found",
3638
"page",
3739
"search-input",
40+
"select-enum",
3841
"stack",
3942
"tab-routes",
4043
"theme",

packages/generators/src/generators/component/component-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ComponentGeneratorSchema } from './component-generator-schema'
33
export const componentTypes: ComponentGeneratorSchema['type'][] = [
44
'alert',
55
'anchor',
6+
'avatar',
67
'back',
78
'card',
89
'container',
@@ -13,13 +14,15 @@ export const componentTypes: ComponentGeneratorSchema['type'][] = [
1314
'grid-routes',
1415
'group',
1516
'header',
17+
'helpers',
1618
'layout',
1719
'loader',
1820
'logo',
1921
'menu',
2022
'not-found',
2123
'page',
2224
'search-input',
25+
'select-enum',
2326
'stack',
2427
'tab-routes',
2528
'theme',

0 commit comments

Comments
 (0)