@toolpath/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/index.d.ts +1174 -0
- package/dist/index.js +5934 -0
- package/package.json +68 -0
- package/src/attribution/action.tsx +13 -0
- package/src/attribution/at.tsx +28 -0
- package/src/attribution/attribution.tsx +16 -0
- package/src/attribution/by.tsx +21 -0
- package/src/attribution/format-time.ts +48 -0
- package/src/attribution/index.ts +20 -0
- package/src/badge/badge.tsx +38 -0
- package/src/badge/index.ts +1 -0
- package/src/breadcrumbs/breadcrumbs-context.tsx +26 -0
- package/src/breadcrumbs/breadcrumbs.tsx +24 -0
- package/src/breadcrumbs/index.ts +12 -0
- package/src/breadcrumbs/item.tsx +85 -0
- package/src/button/animated-ellipsis.tsx +44 -0
- package/src/button/button.tsx +168 -0
- package/src/button/index.ts +1 -0
- package/src/callout/callout.tsx +202 -0
- package/src/callout/index.ts +2 -0
- package/src/callout/use-dismissed-callouts.ts +78 -0
- package/src/card/card.tsx +19 -0
- package/src/card/index.ts +2 -0
- package/src/checkbox/checkbox.tsx +76 -0
- package/src/checkbox/index.ts +1 -0
- package/src/combobox/button.tsx +99 -0
- package/src/combobox/combobox-context.tsx +42 -0
- package/src/combobox/combobox.tsx +60 -0
- package/src/combobox/empty.tsx +21 -0
- package/src/combobox/group-label.tsx +20 -0
- package/src/combobox/group.tsx +11 -0
- package/src/combobox/icon.tsx +11 -0
- package/src/combobox/index.tsx +64 -0
- package/src/combobox/input.tsx +77 -0
- package/src/combobox/item-indicator.tsx +16 -0
- package/src/combobox/item.tsx +58 -0
- package/src/combobox/list.tsx +19 -0
- package/src/combobox/popover.tsx +62 -0
- package/src/combobox/trigger.tsx +33 -0
- package/src/combobox/value.tsx +12 -0
- package/src/common/index.ts +1 -0
- package/src/common/svg.tsx +17 -0
- package/src/dialog/actions.tsx +17 -0
- package/src/dialog/confirm-button.tsx +25 -0
- package/src/dialog/dialog-context.tsx +21 -0
- package/src/dialog/dialog.tsx +37 -0
- package/src/dialog/dismiss-button.tsx +29 -0
- package/src/dialog/index.ts +37 -0
- package/src/dialog/message.tsx +20 -0
- package/src/dialog/provider.tsx +102 -0
- package/src/dialog/title.tsx +20 -0
- package/src/editable-cell/editable-cell.tsx +307 -0
- package/src/editable-cell/index.ts +7 -0
- package/src/editable-cell/use-editable-grid.tsx +296 -0
- package/src/field/field.tsx +22 -0
- package/src/field/index.ts +2 -0
- package/src/helpers/cn.ts +6 -0
- package/src/helpers/index.ts +1 -0
- package/src/icon-button/icon-button.tsx +126 -0
- package/src/icon-button/index.ts +2 -0
- package/src/index.ts +32 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.tsx +276 -0
- package/src/link/index.ts +4 -0
- package/src/link/link-context.tsx +33 -0
- package/src/link/link.tsx +24 -0
- package/src/menu/divider.tsx +17 -0
- package/src/menu/index.ts +40 -0
- package/src/menu/item.tsx +149 -0
- package/src/menu/menu-context.tsx +24 -0
- package/src/menu/menu.tsx +14 -0
- package/src/menu/popover.tsx +69 -0
- package/src/menu/submenu-trigger.tsx +48 -0
- package/src/menu/submenu.tsx +20 -0
- package/src/menu/trigger.tsx +43 -0
- package/src/notification/index.ts +16 -0
- package/src/notification/list.tsx +123 -0
- package/src/notification/provider.tsx +75 -0
- package/src/page-header/index.ts +2 -0
- package/src/page-header/page-header.tsx +14 -0
- package/src/pagination/index.ts +1 -0
- package/src/pagination/pagination.tsx +136 -0
- package/src/panels/collapsed.tsx +32 -0
- package/src/panels/group.tsx +27 -0
- package/src/panels/index.ts +57 -0
- package/src/panels/layout-context.ts +5 -0
- package/src/panels/orientation-context.tsx +5 -0
- package/src/panels/panel.tsx +95 -0
- package/src/panels/separator.tsx +44 -0
- package/src/pin/index.ts +1 -0
- package/src/pin/pin.tsx +99 -0
- package/src/progress-bar/index.ts +1 -0
- package/src/progress-bar/progress-bar.tsx +48 -0
- package/src/quantity-input/index.ts +1 -0
- package/src/quantity-input/quantity-input.tsx +158 -0
- package/src/scroll-area/index.ts +1 -0
- package/src/scroll-area/scroll-area.tsx +82 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.tsx +75 -0
- package/src/star/index.ts +1 -0
- package/src/star/star.tsx +99 -0
- package/src/switch/index.ts +1 -0
- package/src/switch/switch.tsx +33 -0
- package/src/table/body.tsx +15 -0
- package/src/table/cell-context.tsx +48 -0
- package/src/table/cell.tsx +83 -0
- package/src/table/group-empty-state.tsx +28 -0
- package/src/table/group-header.tsx +62 -0
- package/src/table/header-cell.tsx +158 -0
- package/src/table/header-row.tsx +28 -0
- package/src/table/index.ts +56 -0
- package/src/table/is-group-empty-state.ts +17 -0
- package/src/table/is-group-header.ts +5 -0
- package/src/table/row-context.tsx +61 -0
- package/src/table/row-disabled-context.tsx +7 -0
- package/src/table/row.tsx +98 -0
- package/src/table/table-context.tsx +127 -0
- package/src/table/table-empty.tsx +57 -0
- package/src/table/table.tsx +547 -0
- package/src/table/tree-icon.tsx +147 -0
- package/src/table/use-checkbox.ts +200 -0
- package/src/table/use-column-layout.ts +117 -0
- package/src/table/use-groups.ts +134 -0
- package/src/table/use-keyboard-navigation.ts +274 -0
- package/src/table/use-sort-functions.ts +90 -0
- package/src/tabs/index.ts +20 -0
- package/src/tabs/list.tsx +29 -0
- package/src/tabs/tab.tsx +141 -0
- package/src/tabs/tabs-context.tsx +38 -0
- package/src/tabs/tabs.tsx +27 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.tsx +88 -0
- package/src/toast/index.ts +19 -0
- package/src/toast/list.tsx +133 -0
- package/src/toast/provider.tsx +36 -0
- package/src/toggle/index.ts +17 -0
- package/src/toggle/indicator.tsx +27 -0
- package/src/toggle/item.tsx +54 -0
- package/src/toggle/toggle-context.tsx +68 -0
- package/src/toggle/toggle.tsx +124 -0
- package/src/toggle/use-indicator.tsx +69 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.tsx +122 -0
- package/theme.css +70 -0
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toolpath/ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Toolpath React and Tailwind UI primitives",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/toolpath/toolpath.git",
|
|
9
|
+
"directory": "packages/ui"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://developers.toolpath.com",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/toolpath/toolpath/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"registry": "https://registry.npmjs.org"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./theme.css": "./theme.css",
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"theme.css",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "^19.2.0",
|
|
40
|
+
"react-dom": "^19.2.0",
|
|
41
|
+
"tailwindcss": "^4.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@base-ui/react": "1.2.0",
|
|
45
|
+
"@phosphor-icons/react": "2.1.10",
|
|
46
|
+
"@table-library/react-table-library": "^4.1.15",
|
|
47
|
+
"clsx": "^2.1.1",
|
|
48
|
+
"lodash-es": "^4.18.1",
|
|
49
|
+
"react-resizable-panels": "4.9.0",
|
|
50
|
+
"tailwind-merge": "^3.4.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
54
|
+
"@testing-library/react": "^16.3.2",
|
|
55
|
+
"@types/node": "^24.10.1",
|
|
56
|
+
"@types/react": "^19.0.0",
|
|
57
|
+
"@types/react-dom": "^19.0.0",
|
|
58
|
+
"jsdom": "^30.0.1",
|
|
59
|
+
"tsup": "^8.5.1",
|
|
60
|
+
"typescript": "5.9.3",
|
|
61
|
+
"vitest": "^4.1.10"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsup src/index.ts --format esm --dts --clean --external react --external react-dom",
|
|
65
|
+
"check-types": "tsc --noEmit",
|
|
66
|
+
"test": "pnpm build && vitest run"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { capitalize } from 'lodash-es'
|
|
4
|
+
|
|
5
|
+
export type AttributionActionProps = HTMLAttributes<HTMLSpanElement> & {
|
|
6
|
+
children?: ReactNode
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Action = ({ className, children = 'created', ...props }: AttributionActionProps) => (
|
|
10
|
+
<span className={cn(className)} {...props}>
|
|
11
|
+
{typeof children === 'string' ? capitalize(children) : children}
|
|
12
|
+
</span>
|
|
13
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { HTMLAttributes, useMemo } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { Tooltip } from '../tooltip/tooltip'
|
|
4
|
+
import { formatFullDate, formatRelativeTime } from './format-time'
|
|
5
|
+
|
|
6
|
+
export type AttributionAtProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
|
|
7
|
+
date: string | Date
|
|
8
|
+
hideTime?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const At = ({ date, hideTime = false, className, ...props }: AttributionAtProps) => {
|
|
12
|
+
const parsed = useMemo(() => new Date(date), [date])
|
|
13
|
+
|
|
14
|
+
const formattedTime = useMemo(() => formatRelativeTime(parsed, hideTime), [parsed, hideTime])
|
|
15
|
+
|
|
16
|
+
const fullDate = useMemo(() => formatFullDate(parsed), [parsed])
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{' '}
|
|
21
|
+
<Tooltip tip={fullDate}>
|
|
22
|
+
<span className={cn('inline-block', className)} {...props}>
|
|
23
|
+
{formattedTime}
|
|
24
|
+
</span>
|
|
25
|
+
</Tooltip>
|
|
26
|
+
</>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React, { HTMLAttributes } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
|
|
4
|
+
export type AttributionProps = HTMLAttributes<HTMLDivElement>
|
|
5
|
+
|
|
6
|
+
export const Attribution = ({ className, children, ...props }: AttributionProps) => (
|
|
7
|
+
<div
|
|
8
|
+
className={cn(
|
|
9
|
+
'truncate break-words text-xs leading-4 font-semibold tracking-normal text-gray-300 dark:text-zinc-400 md:line-clamp-2 md:whitespace-normal',
|
|
10
|
+
className,
|
|
11
|
+
)}
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
|
|
4
|
+
export type AttributionByProps = HTMLAttributes<HTMLSpanElement> & {
|
|
5
|
+
children?: ReactNode
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const By = ({ className, children, ...props }: AttributionByProps) => {
|
|
9
|
+
if (children == null) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
{' by '}
|
|
16
|
+
<span className={cn(className)} {...props}>
|
|
17
|
+
{children}
|
|
18
|
+
</span>
|
|
19
|
+
</>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
2
|
+
|
|
3
|
+
const formatHour = (date: Date): string => {
|
|
4
|
+
const hours = date.getHours()
|
|
5
|
+
const minutes = date.getMinutes()
|
|
6
|
+
const ampm = hours >= 12 ? 'PM' : 'AM'
|
|
7
|
+
const h = hours % 12 || 12
|
|
8
|
+
const mm = minutes.toString().padStart(2, '0')
|
|
9
|
+
return `${h}:${mm} ${ampm}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const formatFullDate = (date: Date): string =>
|
|
13
|
+
`${MONTHS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()} at ${formatHour(date)}`
|
|
14
|
+
|
|
15
|
+
export const formatRelativeTime = (
|
|
16
|
+
date: Date,
|
|
17
|
+
hideTime: boolean,
|
|
18
|
+
now: Date = new Date(),
|
|
19
|
+
): string => {
|
|
20
|
+
const diffMs = now.getTime() - date.getTime()
|
|
21
|
+
const diffMinutes = Math.floor(diffMs / 60_000)
|
|
22
|
+
const diffHours = Math.floor(diffMs / 3_600_000)
|
|
23
|
+
const diffDays = Math.floor(diffMs / 86_400_000)
|
|
24
|
+
|
|
25
|
+
if (diffMinutes < 1) {
|
|
26
|
+
return 'Just now'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (diffHours < 1) {
|
|
30
|
+
return `${diffMinutes}m ago`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (diffDays < 1) {
|
|
34
|
+
return `${diffHours}h ago`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (diffDays < 2) {
|
|
38
|
+
return `Yesterday${!hideTime ? ` at ${formatHour(date)}` : ''}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isWithinYear = Math.floor(diffMs / (365.25 * 86_400_000)) < 1
|
|
42
|
+
|
|
43
|
+
if (isWithinYear) {
|
|
44
|
+
return `${MONTHS[date.getMonth()]} ${date.getDate()}${!hideTime ? ` at ${formatHour(date)}` : ''}`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return `${MONTHS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`
|
|
48
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactElement } from 'react'
|
|
2
|
+
import { Action, AttributionActionProps } from './action'
|
|
3
|
+
import { At, AttributionAtProps } from './at'
|
|
4
|
+
import { AttributionProps, Attribution as AttributionRoot } from './attribution'
|
|
5
|
+
import { AttributionByProps, By } from './by'
|
|
6
|
+
|
|
7
|
+
type AttributionCompoundType = {
|
|
8
|
+
(props: AttributionProps): ReactElement
|
|
9
|
+
Action: typeof Action
|
|
10
|
+
At: typeof At
|
|
11
|
+
By: typeof By
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Attribution: AttributionCompoundType = Object.assign(AttributionRoot, {
|
|
15
|
+
Action,
|
|
16
|
+
At,
|
|
17
|
+
By,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export type { AttributionProps, AttributionActionProps, AttributionAtProps, AttributionByProps }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
|
|
4
|
+
export type BadgeProps = {
|
|
5
|
+
children: ReactNode
|
|
6
|
+
size?: 'sm' | 'md' | 'lg'
|
|
7
|
+
variant?: 'default' | 'secondary' | 'success' | 'primary' | 'info' | 'danger'
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Badge = ({ children, size = 'md', variant = 'default', className }: BadgeProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
className={cn(
|
|
15
|
+
'inline-flex items-center justify-center whitespace-nowrap border font-semibold tracking-wide',
|
|
16
|
+
{
|
|
17
|
+
'h-3 px-1 text-3xs rounded-sm': size === 'sm',
|
|
18
|
+
'h-4 px-1.5 text-2xs rounded': size === 'md',
|
|
19
|
+
'h-6 px-2 text-xs rounded': size === 'lg',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
'border-gray bg-gray/25 text-gray dark:border-zinc-800 dark:bg-zinc-800/50 dark:text-zinc-100':
|
|
23
|
+
variant === 'default',
|
|
24
|
+
'border-gray-300 bg-gray-300/25 text-gray-400 dark:border-zinc-700 dark:bg-zinc-700/25 dark:text-zinc-200':
|
|
25
|
+
variant === 'secondary',
|
|
26
|
+
'border-success bg-success/25 text-success dark:text-success': variant === 'success',
|
|
27
|
+
'border-primary bg-primary/25 text-primary dark:text-primary': variant === 'primary',
|
|
28
|
+
'border-info bg-info/25 text-info dark:text-info': variant === 'info',
|
|
29
|
+
'border-danger bg-danger/25 text-danger dark:text-danger': variant === 'danger',
|
|
30
|
+
},
|
|
31
|
+
'data-[content]:leading-none',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<span data-content>{children}</span>
|
|
36
|
+
</span>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './badge'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { createContext, ReactNode, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
type Breakpoint = 'sm' | 'md' | 'lg' | 'xl'
|
|
4
|
+
|
|
5
|
+
export type BreadcrumbsContextValue = {
|
|
6
|
+
breakpoint: Breakpoint
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const BreadcrumbsContext = createContext<BreadcrumbsContextValue>({
|
|
10
|
+
breakpoint: 'md',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export const useBreadcrumbs = () => {
|
|
14
|
+
return useContext(BreadcrumbsContext)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type BreadcrumbsProviderProps = {
|
|
18
|
+
children: ReactNode
|
|
19
|
+
breakpoint?: Breakpoint
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const BreadcrumbsProvider = ({ children, breakpoint = 'md' }: BreadcrumbsProviderProps) => {
|
|
23
|
+
return (
|
|
24
|
+
<BreadcrumbsContext.Provider value={{ breakpoint }}>{children}</BreadcrumbsContext.Provider>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { BreadcrumbsProvider } from './breadcrumbs-context'
|
|
4
|
+
|
|
5
|
+
type Breakpoint = 'sm' | 'md' | 'lg' | 'xl'
|
|
6
|
+
|
|
7
|
+
export type BreadcrumbsProps = {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
breakpoint?: Breakpoint
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const BreadcrumbsRoot = ({ children, breakpoint = 'md', className }: BreadcrumbsProps) => (
|
|
14
|
+
<BreadcrumbsProvider breakpoint={breakpoint}>
|
|
15
|
+
<ul
|
|
16
|
+
className={cn(
|
|
17
|
+
'flex whitespace-nowrap text-sm font-medium text-gray-300 dark:text-zinc-500',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</ul>
|
|
23
|
+
</BreadcrumbsProvider>
|
|
24
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ReactElement } from 'react'
|
|
2
|
+
import { BreadcrumbsRoot, BreadcrumbsProps } from './breadcrumbs'
|
|
3
|
+
import { Item, BreadcrumbsItemProps } from './item'
|
|
4
|
+
|
|
5
|
+
type BreadcrumbsCompoundType = {
|
|
6
|
+
(props: BreadcrumbsProps): ReactElement
|
|
7
|
+
Item: typeof Item
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Breadcrumbs: BreadcrumbsCompoundType = Object.assign(BreadcrumbsRoot, { Item })
|
|
11
|
+
|
|
12
|
+
export type { BreadcrumbsProps, BreadcrumbsItemProps }
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { CaretRightIcon } from '@phosphor-icons/react'
|
|
4
|
+
import { Link } from '../link'
|
|
5
|
+
import { useBreadcrumbs } from './breadcrumbs-context'
|
|
6
|
+
|
|
7
|
+
export type BreadcrumbsItemProps = {
|
|
8
|
+
to: string
|
|
9
|
+
children: ReactNode
|
|
10
|
+
current?: boolean
|
|
11
|
+
previous?: boolean
|
|
12
|
+
className?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Item = ({
|
|
16
|
+
to,
|
|
17
|
+
children,
|
|
18
|
+
current = false,
|
|
19
|
+
previous = false,
|
|
20
|
+
className,
|
|
21
|
+
}: BreadcrumbsItemProps) => {
|
|
22
|
+
const { breakpoint } = useBreadcrumbs()
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<li
|
|
26
|
+
className={cn(
|
|
27
|
+
'hidden items-center',
|
|
28
|
+
{
|
|
29
|
+
'sm:flex': breakpoint === 'sm',
|
|
30
|
+
'md:flex': breakpoint === 'md',
|
|
31
|
+
'lg:flex': breakpoint === 'lg',
|
|
32
|
+
'xl:flex': breakpoint === 'xl',
|
|
33
|
+
flex: previous,
|
|
34
|
+
hidden: !previous,
|
|
35
|
+
},
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
{current ? (
|
|
40
|
+
<span data-content className="flex truncate pl-2 text-gray-300 dark:text-zinc-500">
|
|
41
|
+
{children}
|
|
42
|
+
</span>
|
|
43
|
+
) : (
|
|
44
|
+
<>
|
|
45
|
+
<Link
|
|
46
|
+
href={to}
|
|
47
|
+
data-content
|
|
48
|
+
className={cn(
|
|
49
|
+
'no-underline hover:no-underline',
|
|
50
|
+
'flex items-center rounded px-2 text-gray-300 dark:text-zinc-500',
|
|
51
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-info/75',
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<>
|
|
55
|
+
{previous && (
|
|
56
|
+
<CaretRightIcon
|
|
57
|
+
data-previous
|
|
58
|
+
className={cn(
|
|
59
|
+
'flex size-5 rotate-180 truncate text-gray-300 dark:text-zinc-500',
|
|
60
|
+
{
|
|
61
|
+
'sm:hidden': breakpoint === 'sm',
|
|
62
|
+
'md:hidden': breakpoint === 'md',
|
|
63
|
+
'lg:hidden': breakpoint === 'lg',
|
|
64
|
+
'xl:hidden': breakpoint === 'xl',
|
|
65
|
+
},
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
69
|
+
{children}
|
|
70
|
+
</>
|
|
71
|
+
</Link>
|
|
72
|
+
<CaretRightIcon
|
|
73
|
+
data-separator
|
|
74
|
+
className={cn('hidden size-5 text-gray-300 dark:text-zinc-500', {
|
|
75
|
+
'sm:flex': breakpoint === 'sm',
|
|
76
|
+
'md:flex': breakpoint === 'md',
|
|
77
|
+
'lg:flex': breakpoint === 'lg',
|
|
78
|
+
'xl:flex': breakpoint === 'xl',
|
|
79
|
+
})}
|
|
80
|
+
/>
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
</li>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Svg } from '../common'
|
|
3
|
+
import { cn } from '../helpers'
|
|
4
|
+
|
|
5
|
+
export type AnimatedEllipsisProps = {
|
|
6
|
+
className?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AnimatedEllipsis = ({ className }: AnimatedEllipsisProps) => (
|
|
10
|
+
<div className="mx-auto inline-block">
|
|
11
|
+
<div className="flex items-center">
|
|
12
|
+
<Svg
|
|
13
|
+
viewBox="0 0 24 24"
|
|
14
|
+
className={cn(
|
|
15
|
+
'opacity-85 mx-1 h-3 w-3',
|
|
16
|
+
className,
|
|
17
|
+
'animate-[pulse_1.5s_ease-in-out_infinite]',
|
|
18
|
+
)}
|
|
19
|
+
>
|
|
20
|
+
<circle cx="12" cy="12" r="12" />
|
|
21
|
+
</Svg>
|
|
22
|
+
<Svg
|
|
23
|
+
viewBox="0 0 24 24"
|
|
24
|
+
className={cn(
|
|
25
|
+
'opacity-85 mx-1 h-3 w-3',
|
|
26
|
+
className,
|
|
27
|
+
'animate-[pulse_1.5s_ease-in-out_0.5s_infinite]',
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
<circle cx="12" cy="12" r="12" />
|
|
31
|
+
</Svg>
|
|
32
|
+
<Svg
|
|
33
|
+
viewBox="0 0 24 24"
|
|
34
|
+
className={cn(
|
|
35
|
+
'opacity-85 mx-1 h-3 w-3',
|
|
36
|
+
className,
|
|
37
|
+
'animate-[pulse_1.5s_ease-in-out_1.5s_infinite]',
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
<circle cx="12" cy="12" r="12" />
|
|
41
|
+
</Svg>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import React, { Dispatch, HTMLAttributes, MouseEvent, ReactNode, Ref } from 'react'
|
|
2
|
+
import { cn } from '../helpers'
|
|
3
|
+
import { Link } from '../link'
|
|
4
|
+
import { AnimatedEllipsis } from './animated-ellipsis'
|
|
5
|
+
|
|
6
|
+
export type ButtonProps = HTMLAttributes<HTMLButtonElement | HTMLAnchorElement> & {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
variant?: 'default' | 'primary' | 'secondary' | 'info' | 'muted' | 'danger' | 'success'
|
|
9
|
+
size?: 'sm' | 'md' | 'lg'
|
|
10
|
+
shape?: 'default' | 'pill'
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
isLoading?: boolean
|
|
13
|
+
onClick?: Dispatch<MouseEvent>
|
|
14
|
+
type?: 'button' | 'submit' | 'reset'
|
|
15
|
+
className?: string
|
|
16
|
+
asLink?: boolean
|
|
17
|
+
href?: string
|
|
18
|
+
form?: string
|
|
19
|
+
target?: string
|
|
20
|
+
full?: boolean
|
|
21
|
+
ref?: Ref<HTMLButtonElement | HTMLAnchorElement>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Button = ({
|
|
25
|
+
variant = 'default',
|
|
26
|
+
size = 'md',
|
|
27
|
+
shape = 'default',
|
|
28
|
+
type = 'button',
|
|
29
|
+
className,
|
|
30
|
+
isLoading = false,
|
|
31
|
+
children,
|
|
32
|
+
onClick,
|
|
33
|
+
asLink = false,
|
|
34
|
+
href,
|
|
35
|
+
form,
|
|
36
|
+
full = false,
|
|
37
|
+
disabled,
|
|
38
|
+
ref,
|
|
39
|
+
...props
|
|
40
|
+
}: ButtonProps) => {
|
|
41
|
+
const isMuted = variant === 'muted' && !disabled
|
|
42
|
+
const isPrimary = variant === 'primary' && !disabled
|
|
43
|
+
const isSuccess = variant === 'success' && !disabled
|
|
44
|
+
const isSecondary = variant === 'secondary' && !disabled
|
|
45
|
+
const isInfo = variant === 'info' && !disabled
|
|
46
|
+
const isDanger = variant === 'danger' && !disabled
|
|
47
|
+
const isColoredVariant =
|
|
48
|
+
variant === 'primary' || variant === 'success' || variant === 'info' || variant === 'danger'
|
|
49
|
+
|
|
50
|
+
const isPill = shape === 'pill'
|
|
51
|
+
|
|
52
|
+
const buttonClasses = cn(
|
|
53
|
+
'flex flex-col items-center group rounded outline-none font-semibold',
|
|
54
|
+
'cursor-pointer select-none transition duration-200 ease-in-out',
|
|
55
|
+
'focus-visible:ring-2 focus-visible:ring-info/75',
|
|
56
|
+
'text-gray dark:text-zinc-50 focus-visible:bg-gray-100 dark:focus-visible:bg-zinc-800',
|
|
57
|
+
{
|
|
58
|
+
'text-white focus-visible:bg-success': isSuccess,
|
|
59
|
+
'text-white focus-visible:bg-primary': isPrimary,
|
|
60
|
+
'text-white focus-visible:bg-info': isInfo,
|
|
61
|
+
'focus-visible:bg-gray-50': isMuted,
|
|
62
|
+
'text-white focus-visible:bg-danger': isDanger,
|
|
63
|
+
'text-gray-400 dark:text-zinc-200 focus-visible:bg-white dark:focus-visible:bg-zinc-800 focus-visible:rounded-md':
|
|
64
|
+
isSecondary,
|
|
65
|
+
'text-gray-300 dark:text-zinc-400 cursor-not-allowed': disabled && !isColoredVariant,
|
|
66
|
+
'text-white/90 dark:text-zinc-100 cursor-not-allowed': disabled && isColoredVariant,
|
|
67
|
+
'text-gray-200 dark:text-zinc-500': variant === 'muted' && disabled,
|
|
68
|
+
'block text-center': asLink,
|
|
69
|
+
'rounded-lg': size === 'lg' && !isPill,
|
|
70
|
+
'rounded-full focus-visible:rounded-full': isPill,
|
|
71
|
+
'w-full': full,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const clickHandler = (event: React.MouseEvent): void => {
|
|
76
|
+
if (typeof onClick === 'function') {
|
|
77
|
+
if (!asLink) {
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onClick(event)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ContentWrapper = ({ children }: { children: ReactNode }) => (
|
|
86
|
+
<div
|
|
87
|
+
className={cn(
|
|
88
|
+
'whitespace-nowrap rounded px-3 py-1 font-body text-xs font-semibold tracking-normal',
|
|
89
|
+
'bg-gray-100 group-hover:bg-gray-200/75 dark:bg-zinc-800 dark:group-hover:bg-zinc-900',
|
|
90
|
+
'transition duration-200 ease-in-out',
|
|
91
|
+
{
|
|
92
|
+
'bg-primary group-hover:bg-primary-darken group-active:bg-primary-darken dark:bg-primary dark:group-hover:bg-primary-darken dark:group-active:bg-primary-darken':
|
|
93
|
+
isPrimary,
|
|
94
|
+
'bg-success group-hover:bg-success-darken group-active:bg-success-darken dark:bg-success dark:group-hover:bg-success-darken dark:group-active:bg-success-darken':
|
|
95
|
+
isSuccess,
|
|
96
|
+
'bg-info group-hover:bg-info group-active:bg-info dark:bg-info dark:group-hover:bg-info dark:group-active:bg-info':
|
|
97
|
+
isInfo,
|
|
98
|
+
'bg-gray-50 group-hover:bg-gray-100 group-active:bg-gray-100 dark:bg-zinc-800 dark:group-hover:bg-zinc-900 dark:group-active:bg-zinc-900':
|
|
99
|
+
isMuted,
|
|
100
|
+
'bg-danger group-hover:bg-danger-darken group-active:bg-danger-darken dark:bg-danger dark:group-hover:bg-danger-darken dark:group-active:bg-danger-darken':
|
|
101
|
+
isDanger,
|
|
102
|
+
'border border-gray-100 bg-white dark:bg-zinc-900 dark:border-zinc-800 group-hover:bg-gray-50 dark:group-hover:bg-zinc-800 group-active:bg-gray-50 dark:group-active:bg-zinc-800 dark:group-hover:border-zinc-800':
|
|
103
|
+
isSecondary,
|
|
104
|
+
'bg-primary/50 dark:bg-primary/45': disabled && variant === 'primary',
|
|
105
|
+
'bg-success/50 dark:bg-success/45': disabled && variant === 'success',
|
|
106
|
+
'bg-info/50 dark:bg-info/45': disabled && variant === 'info',
|
|
107
|
+
'bg-danger/50 dark:bg-danger/45': disabled && variant === 'danger',
|
|
108
|
+
'dark:bg-zinc-900 group-hover:bg-gray-100 dark:group-hover:bg-zinc-900 dark:group-active:bg-zinc-900':
|
|
109
|
+
disabled && !isColoredVariant,
|
|
110
|
+
'group-active:scale-95': !disabled,
|
|
111
|
+
'px-1.5 py-px text-2xs': size === 'sm',
|
|
112
|
+
'px-4 py-2 text-base': size === 'lg',
|
|
113
|
+
'rounded-lg': size === 'lg' && !isPill,
|
|
114
|
+
'rounded-full': isPill,
|
|
115
|
+
'px-8': isPill && size === 'lg',
|
|
116
|
+
'w-full': full,
|
|
117
|
+
},
|
|
118
|
+
className,
|
|
119
|
+
)}
|
|
120
|
+
>
|
|
121
|
+
{children}
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const content = !isLoading ? (
|
|
126
|
+
<ContentWrapper>{children}</ContentWrapper>
|
|
127
|
+
) : (
|
|
128
|
+
<ContentWrapper>
|
|
129
|
+
<AnimatedEllipsis
|
|
130
|
+
className={cn({
|
|
131
|
+
'h-2 w-1': size === 'sm',
|
|
132
|
+
'h-2 w-1.5': size === 'md',
|
|
133
|
+
'h-3 w-2': size === 'lg',
|
|
134
|
+
})}
|
|
135
|
+
/>
|
|
136
|
+
</ContentWrapper>
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if (asLink) {
|
|
140
|
+
return (
|
|
141
|
+
<Link
|
|
142
|
+
ref={ref as Ref<HTMLAnchorElement>}
|
|
143
|
+
href={disabled ? '#' : href}
|
|
144
|
+
aria-disabled={disabled}
|
|
145
|
+
onClick={clickHandler}
|
|
146
|
+
className={cn('text-inherit no-underline hover:no-underline', buttonClasses)}
|
|
147
|
+
{...props}
|
|
148
|
+
>
|
|
149
|
+
{content}
|
|
150
|
+
</Link>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<button
|
|
156
|
+
ref={ref as Ref<HTMLButtonElement>}
|
|
157
|
+
aria-label={isLoading ? 'Loading...' : ''}
|
|
158
|
+
type={type}
|
|
159
|
+
form={type === 'submit' ? form : undefined}
|
|
160
|
+
onClick={clickHandler}
|
|
161
|
+
className={buttonClasses}
|
|
162
|
+
{...props}
|
|
163
|
+
disabled={disabled}
|
|
164
|
+
>
|
|
165
|
+
{content}
|
|
166
|
+
</button>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './button'
|