@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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/index.d.ts +1174 -0
  4. package/dist/index.js +5934 -0
  5. package/package.json +68 -0
  6. package/src/attribution/action.tsx +13 -0
  7. package/src/attribution/at.tsx +28 -0
  8. package/src/attribution/attribution.tsx +16 -0
  9. package/src/attribution/by.tsx +21 -0
  10. package/src/attribution/format-time.ts +48 -0
  11. package/src/attribution/index.ts +20 -0
  12. package/src/badge/badge.tsx +38 -0
  13. package/src/badge/index.ts +1 -0
  14. package/src/breadcrumbs/breadcrumbs-context.tsx +26 -0
  15. package/src/breadcrumbs/breadcrumbs.tsx +24 -0
  16. package/src/breadcrumbs/index.ts +12 -0
  17. package/src/breadcrumbs/item.tsx +85 -0
  18. package/src/button/animated-ellipsis.tsx +44 -0
  19. package/src/button/button.tsx +168 -0
  20. package/src/button/index.ts +1 -0
  21. package/src/callout/callout.tsx +202 -0
  22. package/src/callout/index.ts +2 -0
  23. package/src/callout/use-dismissed-callouts.ts +78 -0
  24. package/src/card/card.tsx +19 -0
  25. package/src/card/index.ts +2 -0
  26. package/src/checkbox/checkbox.tsx +76 -0
  27. package/src/checkbox/index.ts +1 -0
  28. package/src/combobox/button.tsx +99 -0
  29. package/src/combobox/combobox-context.tsx +42 -0
  30. package/src/combobox/combobox.tsx +60 -0
  31. package/src/combobox/empty.tsx +21 -0
  32. package/src/combobox/group-label.tsx +20 -0
  33. package/src/combobox/group.tsx +11 -0
  34. package/src/combobox/icon.tsx +11 -0
  35. package/src/combobox/index.tsx +64 -0
  36. package/src/combobox/input.tsx +77 -0
  37. package/src/combobox/item-indicator.tsx +16 -0
  38. package/src/combobox/item.tsx +58 -0
  39. package/src/combobox/list.tsx +19 -0
  40. package/src/combobox/popover.tsx +62 -0
  41. package/src/combobox/trigger.tsx +33 -0
  42. package/src/combobox/value.tsx +12 -0
  43. package/src/common/index.ts +1 -0
  44. package/src/common/svg.tsx +17 -0
  45. package/src/dialog/actions.tsx +17 -0
  46. package/src/dialog/confirm-button.tsx +25 -0
  47. package/src/dialog/dialog-context.tsx +21 -0
  48. package/src/dialog/dialog.tsx +37 -0
  49. package/src/dialog/dismiss-button.tsx +29 -0
  50. package/src/dialog/index.ts +37 -0
  51. package/src/dialog/message.tsx +20 -0
  52. package/src/dialog/provider.tsx +102 -0
  53. package/src/dialog/title.tsx +20 -0
  54. package/src/editable-cell/editable-cell.tsx +307 -0
  55. package/src/editable-cell/index.ts +7 -0
  56. package/src/editable-cell/use-editable-grid.tsx +296 -0
  57. package/src/field/field.tsx +22 -0
  58. package/src/field/index.ts +2 -0
  59. package/src/helpers/cn.ts +6 -0
  60. package/src/helpers/index.ts +1 -0
  61. package/src/icon-button/icon-button.tsx +126 -0
  62. package/src/icon-button/index.ts +2 -0
  63. package/src/index.ts +32 -0
  64. package/src/input/index.ts +1 -0
  65. package/src/input/input.tsx +276 -0
  66. package/src/link/index.ts +4 -0
  67. package/src/link/link-context.tsx +33 -0
  68. package/src/link/link.tsx +24 -0
  69. package/src/menu/divider.tsx +17 -0
  70. package/src/menu/index.ts +40 -0
  71. package/src/menu/item.tsx +149 -0
  72. package/src/menu/menu-context.tsx +24 -0
  73. package/src/menu/menu.tsx +14 -0
  74. package/src/menu/popover.tsx +69 -0
  75. package/src/menu/submenu-trigger.tsx +48 -0
  76. package/src/menu/submenu.tsx +20 -0
  77. package/src/menu/trigger.tsx +43 -0
  78. package/src/notification/index.ts +16 -0
  79. package/src/notification/list.tsx +123 -0
  80. package/src/notification/provider.tsx +75 -0
  81. package/src/page-header/index.ts +2 -0
  82. package/src/page-header/page-header.tsx +14 -0
  83. package/src/pagination/index.ts +1 -0
  84. package/src/pagination/pagination.tsx +136 -0
  85. package/src/panels/collapsed.tsx +32 -0
  86. package/src/panels/group.tsx +27 -0
  87. package/src/panels/index.ts +57 -0
  88. package/src/panels/layout-context.ts +5 -0
  89. package/src/panels/orientation-context.tsx +5 -0
  90. package/src/panels/panel.tsx +95 -0
  91. package/src/panels/separator.tsx +44 -0
  92. package/src/pin/index.ts +1 -0
  93. package/src/pin/pin.tsx +99 -0
  94. package/src/progress-bar/index.ts +1 -0
  95. package/src/progress-bar/progress-bar.tsx +48 -0
  96. package/src/quantity-input/index.ts +1 -0
  97. package/src/quantity-input/quantity-input.tsx +158 -0
  98. package/src/scroll-area/index.ts +1 -0
  99. package/src/scroll-area/scroll-area.tsx +82 -0
  100. package/src/slider/index.ts +1 -0
  101. package/src/slider/slider.tsx +75 -0
  102. package/src/star/index.ts +1 -0
  103. package/src/star/star.tsx +99 -0
  104. package/src/switch/index.ts +1 -0
  105. package/src/switch/switch.tsx +33 -0
  106. package/src/table/body.tsx +15 -0
  107. package/src/table/cell-context.tsx +48 -0
  108. package/src/table/cell.tsx +83 -0
  109. package/src/table/group-empty-state.tsx +28 -0
  110. package/src/table/group-header.tsx +62 -0
  111. package/src/table/header-cell.tsx +158 -0
  112. package/src/table/header-row.tsx +28 -0
  113. package/src/table/index.ts +56 -0
  114. package/src/table/is-group-empty-state.ts +17 -0
  115. package/src/table/is-group-header.ts +5 -0
  116. package/src/table/row-context.tsx +61 -0
  117. package/src/table/row-disabled-context.tsx +7 -0
  118. package/src/table/row.tsx +98 -0
  119. package/src/table/table-context.tsx +127 -0
  120. package/src/table/table-empty.tsx +57 -0
  121. package/src/table/table.tsx +547 -0
  122. package/src/table/tree-icon.tsx +147 -0
  123. package/src/table/use-checkbox.ts +200 -0
  124. package/src/table/use-column-layout.ts +117 -0
  125. package/src/table/use-groups.ts +134 -0
  126. package/src/table/use-keyboard-navigation.ts +274 -0
  127. package/src/table/use-sort-functions.ts +90 -0
  128. package/src/tabs/index.ts +20 -0
  129. package/src/tabs/list.tsx +29 -0
  130. package/src/tabs/tab.tsx +141 -0
  131. package/src/tabs/tabs-context.tsx +38 -0
  132. package/src/tabs/tabs.tsx +27 -0
  133. package/src/textarea/index.ts +1 -0
  134. package/src/textarea/textarea.tsx +88 -0
  135. package/src/toast/index.ts +19 -0
  136. package/src/toast/list.tsx +133 -0
  137. package/src/toast/provider.tsx +36 -0
  138. package/src/toggle/index.ts +17 -0
  139. package/src/toggle/indicator.tsx +27 -0
  140. package/src/toggle/item.tsx +54 -0
  141. package/src/toggle/toggle-context.tsx +68 -0
  142. package/src/toggle/toggle.tsx +124 -0
  143. package/src/toggle/use-indicator.tsx +69 -0
  144. package/src/tooltip/index.ts +1 -0
  145. package/src/tooltip/tooltip.tsx +122 -0
  146. package/theme.css +70 -0
@@ -0,0 +1,136 @@
1
+ import React, { FC } from 'react'
2
+ import { cn } from '../helpers'
3
+ import { CaretRightIcon } from '@phosphor-icons/react'
4
+
5
+ export type PaginationMetadata = {
6
+ totalPages: number
7
+ perPage: number
8
+ }
9
+
10
+ export type PaginationProps = {
11
+ pagination: PaginationMetadata
12
+ page: number
13
+ onClick: (page: number) => void
14
+ className?: string
15
+ }
16
+
17
+ export const Pagination: FC<PaginationProps> = ({ pagination, page, onClick, className }) => {
18
+ const isDisabled = pagination.totalPages <= 1
19
+
20
+ const paginationLink = (linkedPage: number, ellipses: boolean = false) => (
21
+ <li
22
+ key={ellipses ? `ellipsis-${linkedPage}` : linkedPage}
23
+ className="inline-block text-xs tracking-tight text-gray-300 dark:text-zinc-400"
24
+ >
25
+ <button
26
+ disabled={page === linkedPage || isDisabled}
27
+ className={cn(
28
+ 'select-none rounded-sm',
29
+ 'hover:bg-gray-50 hover:text-gray dark:hover:bg-zinc-800 dark:hover:text-zinc-100',
30
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-info/75',
31
+ 'font-bold transition-colors duration-200',
32
+ {
33
+ 'bg-gray-300 text-white hover:bg-gray-300 hover:text-white dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800 dark:hover:text-zinc-50':
34
+ page === linkedPage && !isDisabled,
35
+ 'hidden sm:block': page !== linkedPage,
36
+ 'bg-gray-100 text-white hover:bg-gray-100 hover:text-white dark:bg-zinc-900 dark:text-zinc-500 dark:hover:bg-zinc-900 dark:hover:text-zinc-500':
37
+ isDisabled,
38
+ },
39
+ )}
40
+ onClick={() => onClick(linkedPage)}
41
+ >
42
+ <div className="min-w-4 px-1 py-px">{ellipses ? '…' : linkedPage}</div>
43
+ </button>
44
+ </li>
45
+ )
46
+
47
+ return (
48
+ <div className={cn('flex whitespace-nowrap', className)}>
49
+ <button
50
+ disabled={page === 1}
51
+ className={cn(
52
+ 'mt-px flex items-center rounded-sm font-semibold',
53
+ 'text-gray-300 hover:bg-gray-50',
54
+ 'dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100',
55
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-info/75',
56
+ {
57
+ 'cursor-not-allowed text-gray-100 hover:bg-transparent dark:text-zinc-600 dark:hover:bg-transparent':
58
+ page === 1,
59
+ },
60
+ )}
61
+ onClick={() => onClick(page - 1)}
62
+ >
63
+ <CaretRightIcon className="size-5 rotate-180" />
64
+ <span className="sr-only">Previous</span>
65
+ </button>
66
+ <nav className="text-sm text-gray-400 dark:text-zinc-300">
67
+ <ul>
68
+ {paginationLink(1)}
69
+ {pagination.totalPages >= 2 &&
70
+ (pagination.totalPages <= 5 || page < 4) &&
71
+ paginationLink(2)}
72
+ {pagination.totalPages >= 3 &&
73
+ (pagination.totalPages <= 5 || page < 4) &&
74
+ paginationLink(3)}
75
+ {pagination.totalPages === 5 && paginationLink(4)}
76
+ {page > 3 &&
77
+ pagination.totalPages > 5 &&
78
+ paginationLink(
79
+ page >= pagination.totalPages - 2 ? pagination.totalPages - 3 : page - 2,
80
+ true,
81
+ )}
82
+ {pagination.totalPages > 6 &&
83
+ page >= 4 &&
84
+ page < pagination.totalPages - 2 &&
85
+ paginationLink(page - 1)}
86
+ {pagination.totalPages > 6 &&
87
+ page >= 4 &&
88
+ page < pagination.totalPages - 2 &&
89
+ paginationLink(page)}
90
+ {pagination.totalPages > 6 &&
91
+ page >= 4 &&
92
+ page < pagination.totalPages - 2 &&
93
+ paginationLink(page + 1)}
94
+
95
+ {pagination.totalPages > 5 &&
96
+ page < pagination.totalPages - 2 &&
97
+ paginationLink(
98
+ page < 3
99
+ ? 4
100
+ : page === 3
101
+ ? page + 1
102
+ : page <= pagination.totalPages - 3
103
+ ? page + 2
104
+ : pagination.totalPages - 4,
105
+ true,
106
+ )}
107
+
108
+ {pagination.totalPages > 5 &&
109
+ page >= pagination.totalPages - 2 &&
110
+ paginationLink(pagination.totalPages - 2)}
111
+ {pagination.totalPages > 5 &&
112
+ page >= pagination.totalPages - 2 &&
113
+ paginationLink(pagination.totalPages - 1)}
114
+ {pagination.totalPages > 3 && paginationLink(pagination.totalPages)}
115
+ </ul>
116
+ </nav>
117
+ <button
118
+ disabled={page >= pagination.totalPages}
119
+ className={cn(
120
+ 'mt-px flex items-center rounded-sm font-semibold',
121
+ 'text-gray-300 hover:bg-gray-50',
122
+ 'dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100',
123
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-info/75',
124
+ {
125
+ 'cursor-not-allowed text-gray-100 hover:bg-transparent dark:text-zinc-600 dark:hover:bg-transparent':
126
+ page >= pagination.totalPages,
127
+ },
128
+ )}
129
+ onClick={() => onClick(page + 1)}
130
+ >
131
+ <span className="sr-only">Next</span>
132
+ <CaretRightIcon className="size-5" />
133
+ </button>
134
+ </div>
135
+ )
136
+ }
@@ -0,0 +1,32 @@
1
+ import React, { FC, KeyboardEvent } from 'react'
2
+ import { DotsSixVerticalIcon } from '@phosphor-icons/react'
3
+ import { useOrientation } from './orientation-context'
4
+
5
+ export interface CollapsedProps {
6
+ onExpand: () => void
7
+ }
8
+
9
+ export const Collapsed: FC<CollapsedProps> = ({ onExpand }) => {
10
+ const orientation = useOrientation()
11
+ const isVertical = orientation === 'vertical'
12
+
13
+ return (
14
+ <button
15
+ type="button"
16
+ data-handle
17
+ onMouseDown={onExpand}
18
+ onKeyDown={(e: KeyboardEvent) => {
19
+ if (e.key === ' ') {
20
+ e.preventDefault()
21
+ onExpand()
22
+ }
23
+ }}
24
+ className={`flex h-full relative w-full items-center justify-center group outline-none focus-visible:ring-2 ring-info/75 ring-inset ${isVertical ? 'cursor-row-resize' : 'cursor-col-resize'}`}
25
+ >
26
+ <DotsSixVerticalIcon
27
+ data-handle-icon
28
+ className={`size-4 text-gray-200 dark:text-zinc-500 group-hover:text-gray-300 dark:group-hover:text-zinc-400 ${isVertical ? 'rotate-90' : ''}`}
29
+ />
30
+ </button>
31
+ )
32
+ }
@@ -0,0 +1,27 @@
1
+ import React, { useCallback } from 'react'
2
+ import { Group as BaseGroup, type GroupProps, useGroupRef } from 'react-resizable-panels'
3
+ import { LayoutContext } from './layout-context'
4
+ import { OrientationContext } from './orientation-context'
5
+
6
+ export const Group = ({ orientation = 'horizontal', onLayoutChanged, ...props }: GroupProps) => {
7
+ const groupRef = useGroupRef()
8
+
9
+ const notifyLayoutChanged = useCallback(() => {
10
+ if (onLayoutChanged && groupRef.current) {
11
+ onLayoutChanged(groupRef.current.getLayout())
12
+ }
13
+ }, [onLayoutChanged, groupRef])
14
+
15
+ return (
16
+ <OrientationContext.Provider value={orientation}>
17
+ <LayoutContext.Provider value={notifyLayoutChanged}>
18
+ <BaseGroup
19
+ orientation={orientation}
20
+ groupRef={groupRef}
21
+ onLayoutChanged={onLayoutChanged}
22
+ {...props}
23
+ />
24
+ </LayoutContext.Provider>
25
+ </OrientationContext.Provider>
26
+ )
27
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ isCoarsePointer as _isCoarsePointer,
3
+ useDefaultLayout as _useDefaultLayout,
4
+ useGroupCallbackRef as _useGroupCallbackRef,
5
+ useGroupRef as _useGroupRef,
6
+ usePanelCallbackRef as _usePanelCallbackRef,
7
+ usePanelRef as _usePanelRef,
8
+ } from 'react-resizable-panels'
9
+ import type {
10
+ GroupImperativeHandle as _GroupImperativeHandle,
11
+ GroupProps as _GroupProps,
12
+ Layout as _Layout,
13
+ LayoutStorage as _LayoutStorage,
14
+ OnGroupLayoutChange as _OnGroupLayoutChange,
15
+ OnPanelResize as _OnPanelResize,
16
+ Orientation as _Orientation,
17
+ PanelImperativeHandle as _PanelImperativeHandle,
18
+ PanelProps as _PanelProps,
19
+ PanelSize as _PanelSize,
20
+ SeparatorProps as _SeparatorProps,
21
+ SizeUnit as _SizeUnit,
22
+ } from 'react-resizable-panels'
23
+ import { Collapsed as _Collapsed } from './collapsed'
24
+ import type { CollapsedProps as _CollapsedProps } from './collapsed'
25
+ import { useOrientation as _useOrientation } from './orientation-context'
26
+ import { Group as _Group } from './group'
27
+ import { Panel as _Panel } from './panel'
28
+ import { Separator as _Separator } from './separator'
29
+
30
+ // eslint-disable-next-line @typescript-eslint/no-namespace
31
+ export namespace Panels {
32
+ export const Group = _Group
33
+ export const Panel = _Panel
34
+ export const Separator = _Separator
35
+ export const Collapsed = _Collapsed
36
+ export const isCoarsePointer = _isCoarsePointer
37
+ export const useDefaultLayout = _useDefaultLayout
38
+ export const useGroupCallbackRef = _useGroupCallbackRef
39
+ export const useGroupRef = _useGroupRef
40
+ export const usePanelCallbackRef = _usePanelCallbackRef
41
+ export const useOrientation = _useOrientation
42
+ export const usePanelRef = _usePanelRef
43
+
44
+ export type GroupImperativeHandle = _GroupImperativeHandle
45
+ export type GroupProps = _GroupProps
46
+ export type Layout = _Layout
47
+ export type LayoutStorage = _LayoutStorage
48
+ export type OnGroupLayoutChange = _OnGroupLayoutChange
49
+ export type OnPanelResize = _OnPanelResize
50
+ export type Orientation = _Orientation
51
+ export type PanelImperativeHandle = _PanelImperativeHandle
52
+ export type PanelProps = _PanelProps
53
+ export type PanelSize = _PanelSize
54
+ export type SeparatorProps = _SeparatorProps
55
+ export type SizeUnit = _SizeUnit
56
+ export type CollapsedProps = _CollapsedProps
57
+ }
@@ -0,0 +1,5 @@
1
+ import { createContext, useContext } from 'react'
2
+
3
+ export const LayoutContext = createContext<(() => void) | null>(null)
4
+
5
+ export const useLayoutChanged = () => useContext(LayoutContext)
@@ -0,0 +1,5 @@
1
+ import { createContext, useContext } from 'react'
2
+
3
+ export const OrientationContext = createContext<'horizontal' | 'vertical'>('horizontal')
4
+
5
+ export const useOrientation = () => useContext(OrientationContext)
@@ -0,0 +1,95 @@
1
+ import React, { ReactNode, useCallback, useRef, useState } from 'react'
2
+ import { flushSync } from 'react-dom'
3
+ import {
4
+ Panel as BasePanel,
5
+ type PanelProps as BasePanelProps,
6
+ type PanelSize,
7
+ usePanelRef,
8
+ } from 'react-resizable-panels'
9
+ import { Collapsed } from './collapsed'
10
+ import { useLayoutChanged } from './layout-context'
11
+
12
+ type PanelProps = BasePanelProps & {
13
+ children?: ReactNode
14
+ onExpand?: () => void
15
+ onCollapse?: () => void
16
+ }
17
+
18
+ const DEFAULT_COLLAPSED_SIZE = 20
19
+
20
+ export const Panel = ({
21
+ collapsible,
22
+ collapsedSize = collapsible ? DEFAULT_COLLAPSED_SIZE : undefined,
23
+ panelRef: panelRefProp,
24
+ onResize: onResizeProp,
25
+ onExpand: onExpandProp,
26
+ onCollapse: onCollapseProp,
27
+ defaultSize,
28
+ children,
29
+ ...props
30
+ }: PanelProps) => {
31
+ const internalRef = usePanelRef()
32
+ const panelRef = panelRefProp ?? (collapsible ? internalRef : undefined)
33
+
34
+ const collapsedPx = typeof collapsedSize === 'number' ? collapsedSize : 0
35
+ const [width, setWidth] = useState(
36
+ typeof defaultSize === 'number' ? defaultSize : collapsedPx + 1,
37
+ )
38
+ const wasCollapsedRef = useRef(false)
39
+
40
+ const notifyLayoutChanged = useLayoutChanged()
41
+
42
+ const handleResize = useCallback(
43
+ (size: PanelSize, id: string | number | undefined, prevSize: PanelSize | undefined) => {
44
+ const isCollapsed = Boolean(collapsible && size.inPixels <= collapsedPx)
45
+ const wasCollapsed = wasCollapsedRef.current
46
+ if (isCollapsed !== wasCollapsed) {
47
+ wasCollapsedRef.current = isCollapsed
48
+ flushSync(() => setWidth(size.inPixels))
49
+ if (isCollapsed) {
50
+ onCollapseProp?.()
51
+ } else {
52
+ onExpandProp?.()
53
+ }
54
+ if (props.id) {
55
+ notifyLayoutChanged?.()
56
+ }
57
+ } else {
58
+ setWidth(size.inPixels)
59
+ }
60
+ onResizeProp?.(size, id, prevSize)
61
+ },
62
+ [
63
+ collapsible,
64
+ collapsedPx,
65
+ onResizeProp,
66
+ onExpandProp,
67
+ onCollapseProp,
68
+ props.id,
69
+ notifyLayoutChanged,
70
+ ],
71
+ )
72
+
73
+ const handleExpand = useCallback(() => {
74
+ if (panelRef && 'current' in panelRef) {
75
+ panelRef.current?.expand()
76
+ }
77
+ onExpandProp?.()
78
+ if (props.id) {
79
+ notifyLayoutChanged?.()
80
+ }
81
+ }, [panelRef, onExpandProp, props.id, notifyLayoutChanged])
82
+
83
+ return (
84
+ <BasePanel
85
+ collapsible={collapsible}
86
+ collapsedSize={collapsedSize}
87
+ panelRef={panelRef}
88
+ onResize={collapsible ? handleResize : onResizeProp}
89
+ defaultSize={defaultSize}
90
+ {...props}
91
+ >
92
+ {collapsible && width <= collapsedPx ? <Collapsed onExpand={handleExpand} /> : children}
93
+ </BasePanel>
94
+ )
95
+ }
@@ -0,0 +1,44 @@
1
+ import React, { useCallback } from 'react'
2
+ import {
3
+ Separator as BaseSeparator,
4
+ type SeparatorProps as BaseSeparatorProps,
5
+ } from 'react-resizable-panels'
6
+ import { cn } from '../helpers'
7
+ import { useOrientation } from './orientation-context'
8
+
9
+ type SeparatorProps = Omit<BaseSeparatorProps, 'elementRef'> & {
10
+ elementRef?: BaseSeparatorProps['elementRef']
11
+ }
12
+
13
+ export const Separator = ({ elementRef, className, ...props }: SeparatorProps) => {
14
+ const orientation = useOrientation()
15
+
16
+ const ref = useCallback(
17
+ (el: HTMLDivElement | null) => {
18
+ if (el) {
19
+ el.tabIndex = -1
20
+ }
21
+ if (typeof elementRef === 'function') {
22
+ elementRef(el)
23
+ } else if (elementRef) {
24
+ elementRef.current = el
25
+ }
26
+ },
27
+ [elementRef],
28
+ )
29
+
30
+ return (
31
+ <BaseSeparator
32
+ elementRef={ref}
33
+ className={cn(
34
+ 'border-gray-100 dark:border-zinc-800 outline-none',
35
+ {
36
+ 'border-r': orientation === 'horizontal',
37
+ 'border-t': orientation === 'vertical',
38
+ },
39
+ className,
40
+ )}
41
+ {...props}
42
+ />
43
+ )
44
+ }
@@ -0,0 +1 @@
1
+ export * from './pin'
@@ -0,0 +1,99 @@
1
+ import React, { useRef } from 'react'
2
+ import { cn } from '../helpers'
3
+ import { PushPinIcon } from '@phosphor-icons/react'
4
+
5
+ export interface PinChangeEvent {
6
+ shiftKey: boolean
7
+ }
8
+
9
+ export type PinProps = {
10
+ checked: boolean
11
+ onChange: (checked: boolean, event: PinChangeEvent) => void
12
+ name: string
13
+ className?: string
14
+ disabled?: boolean
15
+ size?: 'sm' | 'md' | 'lg'
16
+ tabIndex?: number
17
+ }
18
+
19
+ export const Pin = ({
20
+ checked,
21
+ onChange,
22
+ name,
23
+ className,
24
+ disabled = false,
25
+ size = 'md',
26
+ tabIndex = 0,
27
+ }: PinProps) => {
28
+ const shiftKeyRef = useRef(false)
29
+
30
+ const handleClick = (e: React.MouseEvent) => {
31
+ e.stopPropagation()
32
+ if (disabled) {
33
+ return
34
+ }
35
+ shiftKeyRef.current = e.shiftKey
36
+ onChange(!checked, { shiftKey: shiftKeyRef.current })
37
+ }
38
+
39
+ const handleKeyDown = (e: React.KeyboardEvent) => {
40
+ if (disabled) {
41
+ return
42
+ }
43
+ if (e.key === ' ' || e.key === 'Enter') {
44
+ e.preventDefault()
45
+ shiftKeyRef.current = e.shiftKey
46
+ onChange(!checked, { shiftKey: shiftKeyRef.current })
47
+ }
48
+ }
49
+
50
+ const sizeClasses = {
51
+ sm: 'size-3',
52
+ md: 'size-4',
53
+ lg: 'size-5',
54
+ }
55
+
56
+ return (
57
+ <button
58
+ type="button"
59
+ role="checkbox"
60
+ aria-checked={checked}
61
+ name={name}
62
+ disabled={disabled}
63
+ tabIndex={tabIndex}
64
+ onClick={handleClick}
65
+ onKeyDown={handleKeyDown}
66
+ className={cn(
67
+ 'group grid cursor-pointer outline-none',
68
+ 'focus-visible:ring-2 ring-info/75 ring-offset-1 rounded-sm',
69
+ 'transition-colors duration-150',
70
+ {
71
+ 'cursor-not-allowed opacity-50': disabled,
72
+ },
73
+ className,
74
+ )}
75
+ >
76
+ <PushPinIcon
77
+ weight="fill"
78
+ className={cn(
79
+ sizeClasses[size],
80
+ 'col-start-1 row-start-1 transition-opacity duration-150 pointer-event-none',
81
+ {
82
+ 'text-gray-200 dark:text-zinc-100': checked,
83
+ 'text-transparent': !checked,
84
+ 'group-hover:text-gray-200 dark:group-hover:text-zinc-400': !disabled && !checked,
85
+ },
86
+ )}
87
+ />
88
+ <PushPinIcon
89
+ weight="regular"
90
+ className={cn(sizeClasses[size], 'col-start-1 row-start-1 pointer-event-none', {
91
+ 'text-gray-200 dark:text-zinc-100': checked,
92
+ 'text-gray-200 group-hover:text-gray-200 dark:text-zinc-400 group-hover:dark:text-zinc-400':
93
+ !checked && !disabled,
94
+ 'text-gray-200 dark:text-zinc-400': !checked && disabled,
95
+ })}
96
+ />
97
+ </button>
98
+ )
99
+ }
@@ -0,0 +1 @@
1
+ export * from './progress-bar'
@@ -0,0 +1,48 @@
1
+ import React from 'react'
2
+ import { cn } from '../helpers'
3
+
4
+ export type ProgressBarSize = 'xs' | 'sm' | 'md' | 'lg'
5
+
6
+ export type ProgressBarProps = {
7
+ progress: number
8
+ size?: ProgressBarSize
9
+ className?: string
10
+ }
11
+
12
+ export const ProgressBar = ({ progress, size = 'lg', className }: ProgressBarProps) => {
13
+ return (
14
+ <div
15
+ aria-valuemax={100}
16
+ aria-valuemin={0}
17
+ aria-valuenow={Math.round(progress)}
18
+ aria-valuetext={`${Math.round(progress)}%`}
19
+ role="progressbar"
20
+ className={cn('relative', className)}
21
+ >
22
+ <div
23
+ data-track
24
+ data-complete={progress >= 100}
25
+ className={cn(
26
+ 'flex overflow-hidden rounded-full',
27
+ 'border border-gray-200 dark:border-zinc-800 bg-gray-100 dark:bg-zinc-900',
28
+ {
29
+ 'h-1': size === 'xs',
30
+ 'h-1.5': size === 'sm',
31
+ 'h-2': size === 'md',
32
+ 'h-4': size === 'lg',
33
+ },
34
+ )}
35
+ >
36
+ <div
37
+ data-indicator
38
+ data-complete={progress >= 100}
39
+ style={{ width: `${Math.min(Math.max(progress, 0), 100)}%` }}
40
+ className={cn(
41
+ 'rounded-r-full transition-[width] duration-300 ease-out',
42
+ 'bg-white dark:bg-zinc-600',
43
+ )}
44
+ />
45
+ </div>
46
+ </div>
47
+ )
48
+ }
@@ -0,0 +1 @@
1
+ export * from './quantity-input'