@cxxi-lab/hub-ui 0.0.0-stage → 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/fonts/MerriweatherSans/MerriweatherSans-Bold.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Bold.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-BoldItalic.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-BoldItalic.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-ExtraBold.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-ExtraBold.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-ExtraBoldItalic.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-ExtraBoldItalic.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Italic.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Italic.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Light.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Light.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-LightItalic.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-LightItalic.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Medium.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Medium.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-MediumItalic.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-MediumItalic.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Regular.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-Regular.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-SemiBold.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-SemiBold.woff2 +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-SemiBoldItalic.woff +0 -0
- package/fonts/MerriweatherSans/MerriweatherSans-SemiBoldItalic.woff2 +0 -0
- package/fonts/Sofia/Sofia-Regular.woff +0 -0
- package/fonts/Sofia/Sofia-Regular.woff2 +0 -0
- package/package.json +44 -5
- package/src/Brand/Brand.tsx +23 -0
- package/src/Brand/Brand.types.ts +6 -0
- package/src/Brand/Brand.variants.ts +9 -0
- package/src/Brand/HubLogo.tsx +22 -0
- package/src/Brand/index.ts +2 -0
- package/src/back.ts +52 -0
- package/src/buttons/Button/Button.tsx +38 -0
- package/src/buttons/Button/Button.types.ts +18 -0
- package/src/buttons/Button/Button.variants.ts +36 -0
- package/src/buttons/Button/index.ts +2 -0
- package/src/buttons/FavButton/FavButton.tsx +29 -0
- package/src/buttons/FavButton/FavButton.types.ts +10 -0
- package/src/buttons/FavButton/FavButton.variants.ts +12 -0
- package/src/buttons/FavButton/index.ts +2 -0
- package/src/buttons/IconButton/IconButton.tsx +40 -0
- package/src/buttons/IconButton/IconButton.types.ts +23 -0
- package/src/buttons/IconButton/IconButton.variants.ts +56 -0
- package/src/buttons/IconButton/index.ts +2 -0
- package/src/buttons/index.ts +3 -0
- package/src/contexts/FormContext.tsx +57 -0
- package/src/forms/CheckboxField/Checkbox.tsx +17 -0
- package/src/forms/CheckboxField/CheckboxField.tsx +39 -0
- package/src/forms/CheckboxField/CheckboxField.types.ts +5 -0
- package/src/forms/CheckboxField/CheckboxField.variants.ts +36 -0
- package/src/forms/CheckboxField/index.ts +3 -0
- package/src/forms/DropdownField/DropdownField.tsx +72 -0
- package/src/forms/DropdownField/DropdownField.types.ts +11 -0
- package/src/forms/DropdownField/DropdownField.variants.ts +37 -0
- package/src/forms/DropdownField/index.ts +2 -0
- package/src/forms/Form/Form.tsx +41 -0
- package/src/forms/Form/Form.types.ts +11 -0
- package/src/forms/Form/Form.variants.ts +8 -0
- package/src/forms/Form/index.ts +2 -0
- package/src/forms/FormField/FormField.tsx +26 -0
- package/src/forms/FormField/FormField.types.ts +10 -0
- package/src/forms/FormField/FormField.variants.ts +44 -0
- package/src/forms/FormField/index.ts +3 -0
- package/src/forms/InputField/InputField.tsx +33 -0
- package/src/forms/InputField/InputField.types.ts +14 -0
- package/src/forms/InputField/InputField.variants.ts +14 -0
- package/src/forms/InputField/index.ts +2 -0
- package/src/forms/SpoilerField/SpoilerField.tsx +48 -0
- package/src/forms/SpoilerField/SpoilerField.types.ts +8 -0
- package/src/forms/SpoilerField/SpoilerField.variants.ts +31 -0
- package/src/forms/SpoilerField/index.ts +2 -0
- package/src/forms/SwitchField/ChoiceSwitch.tsx +43 -0
- package/src/forms/SwitchField/Switch.tsx +18 -0
- package/src/forms/SwitchField/SwitchField.tsx +43 -0
- package/src/forms/SwitchField/SwitchField.types.ts +5 -0
- package/src/forms/SwitchField/SwitchField.variants.ts +62 -0
- package/src/forms/SwitchField/index.ts +4 -0
- package/src/forms/TagsField/TagsField.tsx +109 -0
- package/src/forms/TagsField/TagsField.types.ts +7 -0
- package/src/forms/TagsField/TagsField.variants.ts +51 -0
- package/src/forms/TagsField/index.ts +2 -0
- package/src/forms/TextareaField/TextareaField.tsx +33 -0
- package/src/forms/TextareaField/TextareaField.types.ts +9 -0
- package/src/forms/TextareaField/TextareaField.variants.ts +14 -0
- package/src/forms/TextareaField/index.ts +2 -0
- package/src/forms/UploadField/UploadField.tsx +70 -0
- package/src/forms/UploadField/UploadField.types.ts +7 -0
- package/src/forms/UploadField/UploadField.variants.ts +23 -0
- package/src/forms/UploadField/index.ts +2 -0
- package/src/forms/index.ts +10 -0
- package/src/index.ts +15 -0
- package/src/menus/Dropdown/Dropdown.tsx +39 -0
- package/src/menus/Dropdown/Dropdown.types.ts +17 -0
- package/src/menus/Dropdown/Dropdown.variants.ts +44 -0
- package/src/menus/Dropdown/index.ts +2 -0
- package/src/menus/index.ts +1 -0
- package/src/modals/ConfirmationModal/ConfirmationModal.tsx +44 -0
- package/src/modals/ConfirmationModal/ConfirmationModal.types.ts +14 -0
- package/src/modals/ConfirmationModal/ConfirmationModal.variants.ts +6 -0
- package/src/modals/ConfirmationModal/index.ts +2 -0
- package/src/modals/FormModal/FormModal.tsx +46 -0
- package/src/modals/FormModal/FormModal.types.ts +14 -0
- package/src/modals/FormModal/FormModal.variants.ts +5 -0
- package/src/modals/FormModal/index.ts +2 -0
- package/src/modals/InfoModal/InfoModal.tsx +21 -0
- package/src/modals/InfoModal/InfoModal.types.ts +10 -0
- package/src/modals/InfoModal/InfoModal.variants.ts +6 -0
- package/src/modals/InfoModal/index.ts +2 -0
- package/src/modals/Modal/Modal.tsx +34 -0
- package/src/modals/Modal/Modal.types.ts +10 -0
- package/src/modals/Modal/Modal.variants.ts +48 -0
- package/src/modals/Modal/index.ts +2 -0
- package/src/modals/index.ts +4 -0
- package/src/navs/BreadCrumb/BreadCrumb.tsx +19 -0
- package/src/navs/BreadCrumb/BreadCrumb.types.ts +11 -0
- package/src/navs/BreadCrumb/BreadCrumb.variants.ts +7 -0
- package/src/navs/BreadCrumb/index.ts +2 -0
- package/src/navs/index.ts +1 -0
- package/src/tables/Table/Table.tsx +6 -0
- package/src/tables/Table/Table.types.ts +6 -0
- package/src/tables/Table/Table.variants.ts +5 -0
- package/src/tables/Table/index.ts +2 -0
- package/src/tables/TableCell/TableCell.tsx +27 -0
- package/src/tables/TableCell/TableCell.types.ts +9 -0
- package/src/tables/TableCell/TableCell.variants.ts +26 -0
- package/src/tables/TableCell/index.ts +2 -0
- package/src/tables/TableFootGroup/TableFootGroup.tsx +10 -0
- package/src/tables/TableFootGroup/TableFootGroup.variants.ts +6 -0
- package/src/tables/TableFootGroup/index.ts +1 -0
- package/src/tables/TableHead/TableHead.tsx +22 -0
- package/src/tables/TableHead/TableHead.types.ts +9 -0
- package/src/tables/TableHead/TableHead.variants.ts +16 -0
- package/src/tables/TableHead/index.ts +2 -0
- package/src/tables/TableHeadGroup/TableHeadGroup.tsx +10 -0
- package/src/tables/TableHeadGroup/TableHeadGroup.variants.ts +6 -0
- package/src/tables/TableHeadGroup/index.ts +1 -0
- package/src/tables/TableRow/TableRow.tsx +7 -0
- package/src/tables/TableRow/TableRow.types.ts +7 -0
- package/src/tables/TableRow/TableRow.variants.ts +12 -0
- package/src/tables/TableRow/index.ts +2 -0
- package/src/tables/TableRowGroup/TableRowGroup.tsx +6 -0
- package/src/tables/TableRowGroup/TableRowGroup.variants.ts +5 -0
- package/src/tables/TableRowGroup/index.ts +1 -0
- package/src/tables/index.ts +7 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/snakeCase.ts +6 -0
- package/src/utils/useClickOutside.ts +16 -0
- package/src/utils/useMediaQuery.ts +11 -0
- package/src/utils/withLoading.ts +13 -0
- package/styles/fonts.css +116 -0
- package/styles/theme.css +78 -0
- package/README.md +0 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { type FormData } from '../../contexts/FormContext'
|
|
3
|
+
import { type ModalSize } from '../Modal/Modal.types'
|
|
4
|
+
|
|
5
|
+
export interface FormModalProps {
|
|
6
|
+
title: ReactNode
|
|
7
|
+
size?: ModalSize
|
|
8
|
+
initialData?: FormData
|
|
9
|
+
submitLabel?: string
|
|
10
|
+
cancelLabel?: string
|
|
11
|
+
onSubmit: (data: FormData) => void | Promise<void>
|
|
12
|
+
onClose: () => void
|
|
13
|
+
children: ReactNode
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Button } from '../../buttons'
|
|
2
|
+
import { Modal } from '../Modal'
|
|
3
|
+
import { type InfoModalProps } from './InfoModal.types'
|
|
4
|
+
import { styles } from './InfoModal.variants'
|
|
5
|
+
|
|
6
|
+
export function InfoModal({
|
|
7
|
+
title,
|
|
8
|
+
size,
|
|
9
|
+
dismissLabel = 'OK',
|
|
10
|
+
onClose,
|
|
11
|
+
children
|
|
12
|
+
}: InfoModalProps) {
|
|
13
|
+
return (
|
|
14
|
+
<Modal title={title} size={size} onClose={onClose}>
|
|
15
|
+
<div className={styles.content()}>{children}</div>
|
|
16
|
+
<footer className={styles.footer()}>
|
|
17
|
+
<Button label={dismissLabel} onClick={onClose} />
|
|
18
|
+
</footer>
|
|
19
|
+
</Modal>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
3
|
+
import { faXmark } from '@fortawesome/free-solid-svg-icons'
|
|
4
|
+
import { IconButton } from '../../buttons'
|
|
5
|
+
import { type ModalProps } from './Modal.types'
|
|
6
|
+
import { styles } from './Modal.variants'
|
|
7
|
+
import { useBackHandler } from '../../back'
|
|
8
|
+
|
|
9
|
+
export function Modal({ title, size, onClose, children }: ModalProps) {
|
|
10
|
+
// the phone's back button closes it
|
|
11
|
+
useBackHandler(true, onClose)
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const onKey = (e: KeyboardEvent) => {
|
|
14
|
+
if (e.key === 'Escape') onClose()
|
|
15
|
+
}
|
|
16
|
+
window.addEventListener('keydown', onKey)
|
|
17
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
18
|
+
}, [onClose])
|
|
19
|
+
|
|
20
|
+
return createPortal(
|
|
21
|
+
<div className={styles.backdrop()} onMouseDown={e => e.target === e.currentTarget && onClose()}>
|
|
22
|
+
<div className={styles.modal({ size })} role="dialog" aria-modal="true">
|
|
23
|
+
{title !== undefined && (
|
|
24
|
+
<header className={styles.header()}>
|
|
25
|
+
<h2 className={styles.title()}>{title}</h2>
|
|
26
|
+
<IconButton color="secondary" icon={faXmark} onClick={onClose} aria-label="Close" />
|
|
27
|
+
</header>
|
|
28
|
+
)}
|
|
29
|
+
<div className={styles.content()}>{children}</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>,
|
|
32
|
+
document.body
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { tv } from 'tailwind-variants'
|
|
2
|
+
|
|
3
|
+
export const styles = {
|
|
4
|
+
backdrop: tv({
|
|
5
|
+
base: [
|
|
6
|
+
'fixed inset-0',
|
|
7
|
+
'flex',
|
|
8
|
+
'items-start',
|
|
9
|
+
'justify-center',
|
|
10
|
+
// phone: the modal is the whole screen (no margin below sm)
|
|
11
|
+
'sm:p-4',
|
|
12
|
+
'bg-black-overlay',
|
|
13
|
+
'backdrop-blur-xs',
|
|
14
|
+
'pretty-scrollbar',
|
|
15
|
+
'z-999'
|
|
16
|
+
]
|
|
17
|
+
}),
|
|
18
|
+
|
|
19
|
+
modal: tv({
|
|
20
|
+
base: [
|
|
21
|
+
'bg-steel-800',
|
|
22
|
+
'text-steel-100',
|
|
23
|
+
'sm:rounded-sm',
|
|
24
|
+
'shadow',
|
|
25
|
+
'sm:my-4',
|
|
26
|
+
'min-h-full sm:min-h-0',
|
|
27
|
+
'h-auto',
|
|
28
|
+
'w-full',
|
|
29
|
+
'relative'
|
|
30
|
+
],
|
|
31
|
+
variants: {
|
|
32
|
+
size: {
|
|
33
|
+
sm: 'sm:max-w-lg',
|
|
34
|
+
md: 'sm:max-w-2xl',
|
|
35
|
+
lg: 'sm:max-w-4xl',
|
|
36
|
+
xl: 'sm:max-w-6xl',
|
|
37
|
+
full: 'max-w-none w-screen h-screen my-0'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: {
|
|
41
|
+
size: 'sm'
|
|
42
|
+
}
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
header: tv({ base: 'flex justify-between items-center gap-4 p-6 pb-4' }),
|
|
46
|
+
title: tv({ base: 'text-xl font-semibold' }),
|
|
47
|
+
content: tv({ base: 'px-6 pb-6' })
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Fragment } from 'react'
|
|
2
|
+
import { Link } from 'react-router'
|
|
3
|
+
import { type BreadCrumbProps } from './BreadCrumb.types'
|
|
4
|
+
import { styles } from './BreadCrumb.variants'
|
|
5
|
+
|
|
6
|
+
export function BreadCrumb({ crumbs, separator = '/' }: BreadCrumbProps) {
|
|
7
|
+
return (
|
|
8
|
+
<ul className={styles.container()}>
|
|
9
|
+
{crumbs.map((crumb, i) => (
|
|
10
|
+
<Fragment key={`crumb-${i}`}>
|
|
11
|
+
{i > 0 && <li className={styles.separator()}>{separator}</li>}
|
|
12
|
+
<li className={styles.crumb()}>
|
|
13
|
+
<Link to={crumb.path}>{crumb.label}</Link>
|
|
14
|
+
</li>
|
|
15
|
+
</Fragment>
|
|
16
|
+
))}
|
|
17
|
+
</ul>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './BreadCrumb'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Link } from 'react-router'
|
|
2
|
+
import { type TableCellProps } from './TableCell.types'
|
|
3
|
+
import { styles } from './TableCell.variants'
|
|
4
|
+
|
|
5
|
+
export function TableCell({
|
|
6
|
+
to,
|
|
7
|
+
textAlign = 'left',
|
|
8
|
+
mobile = true,
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
...rest
|
|
12
|
+
}: TableCellProps) {
|
|
13
|
+
const computed = styles.td({
|
|
14
|
+
textAlign,
|
|
15
|
+
hiddenOnMobile: !mobile,
|
|
16
|
+
isLink: Boolean(to)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<td className={`${computed} ${className ?? ''}`.trim()} {...rest}>
|
|
21
|
+
{to
|
|
22
|
+
? <Link to={to} className={styles.link()}>{children}</Link>
|
|
23
|
+
: <span>{children}</span>
|
|
24
|
+
}
|
|
25
|
+
</td>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode, type TdHTMLAttributes } from 'react'
|
|
2
|
+
import { type TextAlign } from '../TableHead/TableHead.types'
|
|
3
|
+
|
|
4
|
+
export interface TableCellProps extends Omit<TdHTMLAttributes<HTMLTableCellElement>, 'children'> {
|
|
5
|
+
to?: string
|
|
6
|
+
textAlign?: TextAlign
|
|
7
|
+
mobile?: boolean
|
|
8
|
+
children?: ReactNode
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { tv } from 'tailwind-variants'
|
|
2
|
+
|
|
3
|
+
export const styles = {
|
|
4
|
+
td: tv({
|
|
5
|
+
base: 'px-4 leading-none',
|
|
6
|
+
variants: {
|
|
7
|
+
isLink: { false: 'py-2' },
|
|
8
|
+
textAlign: {
|
|
9
|
+
left: 'text-left',
|
|
10
|
+
center: 'text-center',
|
|
11
|
+
right: 'text-right'
|
|
12
|
+
},
|
|
13
|
+
hiddenOnMobile: { true: 'hidden md:table-cell' }
|
|
14
|
+
}
|
|
15
|
+
}),
|
|
16
|
+
link: tv({
|
|
17
|
+
base: [
|
|
18
|
+
'inline-block',
|
|
19
|
+
'w-full',
|
|
20
|
+
'py-2',
|
|
21
|
+
'hover:text-primary-500',
|
|
22
|
+
'hover:underline',
|
|
23
|
+
'underline-offset-3'
|
|
24
|
+
]
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { styles } from './TableFootGroup.variants'
|
|
3
|
+
|
|
4
|
+
export function TableFootGroup({ children }: { children: ReactNode }) {
|
|
5
|
+
return (
|
|
6
|
+
<tfoot className={styles.tfoot()}>
|
|
7
|
+
<tr className={styles.rowFoot()}>{children}</tr>
|
|
8
|
+
</tfoot>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TableFootGroup } from './TableFootGroup'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Children } from 'react'
|
|
2
|
+
import { type TableHeadProps } from './TableHead.types'
|
|
3
|
+
import { styles } from './TableHead.variants'
|
|
4
|
+
|
|
5
|
+
export function TableHead({
|
|
6
|
+
textAlign = 'left',
|
|
7
|
+
mobile = true,
|
|
8
|
+
className,
|
|
9
|
+
children,
|
|
10
|
+
...rest
|
|
11
|
+
}: TableHeadProps) {
|
|
12
|
+
const computed = styles.th({
|
|
13
|
+
textAlign,
|
|
14
|
+
empty: Children.count(children) === 0,
|
|
15
|
+
hiddenOnMobile: !mobile
|
|
16
|
+
})
|
|
17
|
+
return (
|
|
18
|
+
<th className={`${computed} ${className ?? ''}`.trim()} {...rest}>
|
|
19
|
+
{children}
|
|
20
|
+
</th>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode, type ThHTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
export type TextAlign = 'left' | 'center' | 'right'
|
|
4
|
+
|
|
5
|
+
export interface TableHeadProps extends Omit<ThHTMLAttributes<HTMLTableCellElement>, 'children'> {
|
|
6
|
+
textAlign?: TextAlign
|
|
7
|
+
mobile?: boolean
|
|
8
|
+
children?: ReactNode
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { tv } from 'tailwind-variants'
|
|
2
|
+
|
|
3
|
+
export const styles = {
|
|
4
|
+
th: tv({
|
|
5
|
+
base: 'py-2',
|
|
6
|
+
variants: {
|
|
7
|
+
textAlign: {
|
|
8
|
+
left: 'text-left',
|
|
9
|
+
center: 'text-center',
|
|
10
|
+
right: 'text-right'
|
|
11
|
+
},
|
|
12
|
+
empty: { false: 'px-4' },
|
|
13
|
+
hiddenOnMobile: { true: 'hidden md:table-cell' }
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { styles } from './TableHeadGroup.variants'
|
|
3
|
+
|
|
4
|
+
export function TableHeadGroup({ children }: { children: ReactNode }) {
|
|
5
|
+
return (
|
|
6
|
+
<thead className={styles.thead()}>
|
|
7
|
+
<tr className={styles.rowHead()}>{children}</tr>
|
|
8
|
+
</thead>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TableHeadGroup } from './TableHeadGroup'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type TableRowProps } from './TableRow.types'
|
|
2
|
+
import { styles } from './TableRow.variants'
|
|
3
|
+
|
|
4
|
+
export function TableRow({ children, highlighted = false, className }: TableRowProps) {
|
|
5
|
+
const computed = styles.tr({ highlighted })
|
|
6
|
+
return <tr className={`${computed} ${className ?? ''}`.trim()}>{children}</tr>
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TableRowGroup } from './TableRowGroup'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, type RefObject } from 'react'
|
|
2
|
+
|
|
3
|
+
type Refs = ReadonlyArray<RefObject<HTMLElement | null>>
|
|
4
|
+
|
|
5
|
+
export function useClickOutside(refs: Refs, callback: () => void): void {
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const handler = (e: MouseEvent) => {
|
|
8
|
+
const target = e.target as Node
|
|
9
|
+
if (refs.every(ref => ref.current && !ref.current.contains(target))) {
|
|
10
|
+
callback()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
document.addEventListener('mousedown', handler)
|
|
14
|
+
return () => document.removeEventListener('mousedown', handler)
|
|
15
|
+
}, [refs, callback])
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from 'react'
|
|
2
|
+
|
|
3
|
+
/** Whether `query` (a CSS media query) currently matches; follows its changes. */
|
|
4
|
+
export function useMediaQuery(query: string): boolean {
|
|
5
|
+
const subscribe = useCallback((onChange: () => void) => {
|
|
6
|
+
const list = window.matchMedia(query)
|
|
7
|
+
list.addEventListener('change', onChange)
|
|
8
|
+
return () => list.removeEventListener('change', onChange)
|
|
9
|
+
}, [query])
|
|
10
|
+
return useSyncExternalStore(subscribe, () => window.matchMedia(query).matches)
|
|
11
|
+
}
|