@bronzelabs/oakma-ui 0.0.1

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 (121) hide show
  1. package/.prettierrc.cjs +25 -0
  2. package/.storybook/components/ActionButton.tsx +44 -0
  3. package/.storybook/components/DummyIcons.tsx +47 -0
  4. package/.storybook/components/index.ts +2 -0
  5. package/.storybook/docs/blocks/ImportStatement.tsx +52 -0
  6. package/.storybook/docs/blocks/index.ts +1 -0
  7. package/.storybook/docs/page.tsx +41 -0
  8. package/.storybook/main.ts +21 -0
  9. package/.storybook/postcss.config.cjs +8 -0
  10. package/.storybook/preview-body.html +20 -0
  11. package/.storybook/preview-head.html +6 -0
  12. package/.storybook/preview.tsx +30 -0
  13. package/.storybook/tailwind.css +6 -0
  14. package/.storybook/utils/index.ts +2 -0
  15. package/.storybook/utils/renderAsReact.tsx +30 -0
  16. package/.storybook/utils/renderDocsWithProps.tsx +22 -0
  17. package/@types/markdown.d.ts +4 -0
  18. package/README.md +3 -0
  19. package/eslint.config.js +91 -0
  20. package/package.json +63 -0
  21. package/postcss.config.cjs +8 -0
  22. package/scripts/release.sh +76 -0
  23. package/src/components/Button/Button.stories.tsx +314 -0
  24. package/src/components/Button/Button.tsx +132 -0
  25. package/src/components/Button/index.ts +2 -0
  26. package/src/components/Button/types.ts +19 -0
  27. package/src/components/Checkbox/Checkbox.stories.tsx +152 -0
  28. package/src/components/Checkbox/Checkbox.tsx +90 -0
  29. package/src/components/Checkbox/index.ts +2 -0
  30. package/src/components/Checkbox/types.ts +6 -0
  31. package/src/components/Chip/Chip.stories.tsx +146 -0
  32. package/src/components/Chip/Chip.tsx +59 -0
  33. package/src/components/Chip/index.ts +2 -0
  34. package/src/components/Chip/types.ts +6 -0
  35. package/src/components/Drawer/Drawer.docs.md +88 -0
  36. package/src/components/Drawer/Drawer.stories.tsx +239 -0
  37. package/src/components/Drawer/Drawer.tsx +194 -0
  38. package/src/components/Drawer/index.ts +3 -0
  39. package/src/components/Drawer/types.ts +3 -0
  40. package/src/components/Dropdown/AsyncDropdown.tsx +105 -0
  41. package/src/components/Dropdown/Dropdown.docs.md +33 -0
  42. package/src/components/Dropdown/Dropdown.stories.tsx +419 -0
  43. package/src/components/Dropdown/Dropdown.tsx +104 -0
  44. package/src/components/Dropdown/MultiValue.tsx +19 -0
  45. package/src/components/Dropdown/ValueContainer.tsx +114 -0
  46. package/src/components/Dropdown/index.ts +4 -0
  47. package/src/components/Dropdown/types.ts +29 -0
  48. package/src/components/Dropdown/useDropdown.tsx +257 -0
  49. package/src/components/Logo/Logo.stories.tsx +130 -0
  50. package/src/components/Logo/Logo.tsx +80 -0
  51. package/src/components/Logo/index.ts +2 -0
  52. package/src/components/Modal/Modal.docs.md +94 -0
  53. package/src/components/Modal/Modal.stories.tsx +318 -0
  54. package/src/components/Modal/Modal.tsx +217 -0
  55. package/src/components/Modal/index.ts +1 -0
  56. package/src/components/MultiSelect/AsyncMultiSelect.tsx +47 -0
  57. package/src/components/MultiSelect/MultiSelect.docs.md +37 -0
  58. package/src/components/MultiSelect/MultiSelect.stories.tsx +493 -0
  59. package/src/components/MultiSelect/MultiSelect.tsx +81 -0
  60. package/src/components/MultiSelect/index.ts +2 -0
  61. package/src/components/Notification/Notification.stories.tsx +158 -0
  62. package/src/components/Notification/Notification.tsx +110 -0
  63. package/src/components/Notification/index.ts +1 -0
  64. package/src/components/Notification/types.ts +11 -0
  65. package/src/components/Notifications/Notifications.docs.md +103 -0
  66. package/src/components/Notifications/Notifications.stories.tsx +159 -0
  67. package/src/components/Notifications/Notifications.tsx +90 -0
  68. package/src/components/Notifications/NotificationsContext.tsx +90 -0
  69. package/src/components/Notifications/index.ts +7 -0
  70. package/src/components/Select/Select.stories.tsx +234 -0
  71. package/src/components/Select/Select.tsx +129 -0
  72. package/src/components/Select/index.ts +2 -0
  73. package/src/components/Select/types.ts +1 -0
  74. package/src/components/Spinner/Spinner.stories.tsx +55 -0
  75. package/src/components/Spinner/Spinner.tsx +48 -0
  76. package/src/components/Spinner/index.ts +2 -0
  77. package/src/components/Spinner/types.ts +8 -0
  78. package/src/components/TextArea/TextArea.stories.tsx +243 -0
  79. package/src/components/TextArea/TextArea.tsx +133 -0
  80. package/src/components/TextArea/index.ts +2 -0
  81. package/src/components/TextArea/types.ts +4 -0
  82. package/src/components/TextField/Container.tsx +68 -0
  83. package/src/components/TextField/ErrorMessage.tsx +37 -0
  84. package/src/components/TextField/Icon.tsx +77 -0
  85. package/src/components/TextField/Label.tsx +56 -0
  86. package/src/components/TextField/NotchBorder.tsx +67 -0
  87. package/src/components/TextField/index.ts +14 -0
  88. package/src/components/TextField/types.ts +15 -0
  89. package/src/components/TextField/useInputKeyboardFocus.tsx +63 -0
  90. package/src/components/TextInput/TextInput.stories.tsx +384 -0
  91. package/src/components/TextInput/TextInput.tsx +255 -0
  92. package/src/components/TextInput/index.ts +2 -0
  93. package/src/components/TextInput/types.ts +4 -0
  94. package/src/components/Toggle/Toggle.stories.tsx +142 -0
  95. package/src/components/Toggle/Toggle.tsx +69 -0
  96. package/src/components/Toggle/index.ts +1 -0
  97. package/src/hooks/index.ts +6 -0
  98. package/src/hooks/useCombinedRefs.ts +37 -0
  99. package/src/hooks/useEventListener.ts +87 -0
  100. package/src/hooks/useFocusTrap/createAriaHider.ts +62 -0
  101. package/src/hooks/useFocusTrap/index.ts +1 -0
  102. package/src/hooks/useFocusTrap/scopeTab.ts +46 -0
  103. package/src/hooks/useFocusTrap/tabbable.ts +107 -0
  104. package/src/hooks/useFocusTrap/useFocusTrap.ts +97 -0
  105. package/src/hooks/useIsomorphicLayoutEffect.ts +14 -0
  106. package/src/hooks/useLockBodyScroll.ts +24 -0
  107. package/src/hooks/useOnClickOutside.ts +53 -0
  108. package/src/index.ts +22 -0
  109. package/src/tailwind.css +4 -0
  110. package/src/types/helpers.ts +11 -0
  111. package/src/types/polymorphic.ts +39 -0
  112. package/src/utils/animation/variants.ts +21 -0
  113. package/src/utils/array/index.ts +1 -0
  114. package/src/utils/array/uniqBy.ts +12 -0
  115. package/src/utils/common/index.ts +1 -0
  116. package/src/utils/common/isFunction.ts +17 -0
  117. package/src/utils/react/extractDisplayName.ts +15 -0
  118. package/src/utils/react/index.ts +1 -0
  119. package/tsconfig.json +16 -0
  120. package/tsconfig.production.json +19 -0
  121. package/tsup.config.ts +16 -0
@@ -0,0 +1,194 @@
1
+ "use client"
2
+
3
+ import React, { useEffect, useState } from "react"
4
+ import { createPortal } from "react-dom"
5
+
6
+ // Components
7
+ import { XMarkIcon } from "@heroicons/react/20/solid"
8
+
9
+ // Types
10
+ import type { DrawerPosition } from "./types"
11
+
12
+ // Utils
13
+ import { motion, AnimatePresence, Variants, HTMLMotionProps } from "motion/react"
14
+ import clsx from "clsx"
15
+ import {
16
+ useCombinedRefs,
17
+ useEventListener,
18
+ useLockBodyScroll,
19
+ useOnClickOutside,
20
+ useFocusTrap,
21
+ } from "../../hooks"
22
+ import { backdropVariants } from "../../utils/animation/variants"
23
+
24
+ // Variants
25
+ const initialCoords = {
26
+ top: { x: "0%", y: "calc(-100% - 1.5rem)" },
27
+ right: { x: "calc(100% + 1.5rem)", y: "0%" },
28
+ bottom: { x: "0%", y: "calc(100% + 1.5rem)" },
29
+ left: { x: "calc(-100% - 1.5rem)", y: "0%" },
30
+ }
31
+
32
+ const drawerVariants: Variants = {
33
+ initial: (position: DrawerPosition) => initialCoords[position],
34
+ animate: {
35
+ x: "0%",
36
+ y: "0%",
37
+ transition: {
38
+ duration: 0.3,
39
+ ease: [0.4, 0, 0.2, 1],
40
+ },
41
+ },
42
+ exit: (position: DrawerPosition) => ({
43
+ ...initialCoords[position],
44
+ transition: {
45
+ duration: 0.3,
46
+ ease: [0.4, 0, 0.2, 1],
47
+ },
48
+ }),
49
+ }
50
+
51
+ // Params
52
+ interface DrawerProps extends HTMLMotionProps<"div"> {
53
+ /** Side of the screen on which the Drawer will be opened. */
54
+ position?: DrawerPosition
55
+ /** Whether the Drawer is open. */
56
+ isOpen?: boolean
57
+ /** Function called when the Drawer is closed. */
58
+ onClose: () => void
59
+ /** Whether the backdrop should be rendered. */
60
+ withBackdrop?: boolean
61
+ /** Whether the close button should be rendered. */
62
+ withCloseButton?: boolean
63
+ /** Whether the Drawer should be closed when user clicks on the overlay. */
64
+ closeOnClickOutside?: boolean
65
+ /** Whether `onClose` should be called when user presses the escape key. */
66
+ closeOnEscape?: boolean
67
+ /** Whether scroll should be locked when `isOpen={true}`. */
68
+ lockScroll?: boolean
69
+ /** Whether focus should be trapped within the drawer when `isOpen={true}`. */
70
+ trapFocus?: boolean
71
+ /** The portal the Drawer should be rendered inside.*/
72
+ portal?: Element | DocumentFragment
73
+ /** */
74
+ children?: React.ReactNode
75
+ }
76
+
77
+ const MODAL_EVENT = "tribe-modal-state"
78
+
79
+ /*
80
+
81
+
82
+
83
+
84
+
85
+ */
86
+
87
+ const Drawer: React.FC<DrawerProps> = ({
88
+ position = "left",
89
+ isOpen = false,
90
+ withBackdrop = true,
91
+ withCloseButton = true,
92
+ closeOnClickOutside = true,
93
+ closeOnEscape = true,
94
+ lockScroll = true,
95
+ trapFocus = true,
96
+ portal,
97
+ onClose,
98
+ className,
99
+ children,
100
+ ref,
101
+ ...rest
102
+ }) => {
103
+ // Refs
104
+ const focusRef = useFocusTrap(trapFocus && isOpen)
105
+ const combinedRefs = useCombinedRefs<HTMLDivElement | null>(ref, focusRef)
106
+
107
+ // State
108
+ const [modalOpen, setModalOpen] = useState(false)
109
+
110
+ // Effects
111
+ useEffect(() => {
112
+ function handleModalEvent(e: Event): void {
113
+ const custom = e as CustomEvent
114
+ setModalOpen(!!custom.detail?.open)
115
+ }
116
+
117
+ window.addEventListener(MODAL_EVENT, handleModalEvent)
118
+
119
+ return () => {
120
+ window.removeEventListener(MODAL_EVENT, handleModalEvent)
121
+ }
122
+ }, [])
123
+
124
+ useOnClickOutside<HTMLDivElement | null>(combinedRefs, () => {
125
+ if (closeOnClickOutside && !modalOpen) onClose()
126
+ })
127
+
128
+ useEventListener("keydown", e => {
129
+ if (closeOnEscape && e.key === "Escape" && !modalOpen) onClose()
130
+ })
131
+
132
+ useLockBodyScroll(lockScroll && isOpen)
133
+
134
+ if (typeof window === "undefined") {
135
+ return null
136
+ }
137
+
138
+ return createPortal(
139
+ <AnimatePresence>
140
+ {isOpen && (
141
+ <div className="fixed inset-0 z-9998">
142
+ {withBackdrop && (
143
+ <motion.div
144
+ initial="hidden"
145
+ animate="visible"
146
+ exit="hidden"
147
+ variants={backdropVariants}
148
+ role="presentation"
149
+ className="fixed inset-0 opacity-30 backdrop-blur-xs"
150
+ style={{ backgroundColor: "rgba(15 41 41 / 0.3)" }}
151
+ />
152
+ )}
153
+
154
+ <motion.div
155
+ ref={combinedRefs}
156
+ className={clsx(
157
+ "fixed flex flex-col overflow-hidden rounded-5xl bg-white",
158
+ position === "top" && "inset-x-3 top-3 h-[min(14rem,calc(100dvh-1.5rem))]",
159
+ position === "right" && "inset-y-3 right-3 w-[min(30rem,calc(100vw-1.5rem))]",
160
+ position === "bottom" && "inset-x-3 bottom-3 h-[min(14rem,calc(100dvh-1.5rem))]",
161
+ position === "left" && "inset-y-3 left-3 w-[min(30rem,calc(100vw-1.5rem))]",
162
+ isOpen && "shadow-md",
163
+ className,
164
+ )}
165
+ initial="initial"
166
+ animate="animate"
167
+ exit="exit"
168
+ variants={drawerVariants}
169
+ custom={position}
170
+ {...rest}
171
+ >
172
+ {withCloseButton && (
173
+ <div className="sticky top-0 z-40 flex justify-end gap-6 bg-white px-4 pt-4.5 pb-2">
174
+ <button
175
+ className="flex size-6 cursor-pointer items-center justify-center justify-self-end rounded-full text-neutral-50 transition-colors hover:bg-neutral-50/10 hover:text-neutral-100 focus-visible:bg-neutral-50/10 focus-visible:text-neutral-100 active:bg-neutral-50/20"
176
+ onClick={onClose}
177
+ title="Close drawer"
178
+ >
179
+ <XMarkIcon className="size-5" />
180
+ </button>
181
+ </div>
182
+ )}
183
+ <>{children}</>
184
+ </motion.div>
185
+ </div>
186
+ )}
187
+ </AnimatePresence>,
188
+ portal || document?.body,
189
+ )
190
+ }
191
+
192
+ Drawer.displayName = "Drawer"
193
+ export default Drawer
194
+ export type { DrawerProps }
@@ -0,0 +1,3 @@
1
+ export { default, type DrawerProps } from "./Drawer"
2
+
3
+ export { DRAWER_POSITIONS, type DrawerPosition } from "./types"
@@ -0,0 +1,3 @@
1
+ export const DRAWER_POSITIONS = ["top", "right", "bottom", "left"] as const
2
+
3
+ export type DrawerPosition = (typeof DRAWER_POSITIONS)[number]
@@ -0,0 +1,105 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+
5
+ // Components
6
+ import { Container, ErrorMessage, Label } from "../TextField"
7
+ import AsyncReactSelect, { type AsyncProps } from "react-select/async"
8
+
9
+ // Types
10
+ import type { DropdownSharedProps } from "./types"
11
+ import type { GroupBase } from "react-select"
12
+
13
+ // Utils
14
+ import { useDropdown } from "./useDropdown"
15
+
16
+ // Params
17
+ interface AsyncDropdownProps
18
+ extends Omit<AsyncProps<unknown, boolean, GroupBase<unknown>>, "isMulti">, DropdownSharedProps {
19
+ ref?: React.Ref<React.ComponentRef<typeof AsyncReactSelect>>
20
+ /**
21
+ * The `isMulti` prop is not supported in the `AsyncDropdown` component. Please use the `MultiSelect` component for multi-select functionality.
22
+ */
23
+ isMulti?: never
24
+ }
25
+
26
+ /*
27
+
28
+
29
+
30
+
31
+
32
+ */
33
+
34
+ const AsyncDropdown: React.FC<AsyncDropdownProps> = ({
35
+ id,
36
+ label,
37
+ leadingIcon,
38
+ showOptional = true,
39
+ className,
40
+ required,
41
+ error,
42
+ size = "md",
43
+ // variant = "dark",
44
+ onMenuOpen,
45
+ inputId: _inputId,
46
+ instanceId: _instanceId,
47
+ components: _components,
48
+ classNames,
49
+ openMenuOnFocus = true,
50
+ showSelectedCount = true,
51
+ ...rest
52
+ }) => {
53
+ const { fieldId, instanceId, inputId, handleMenuOpen, stableComponents, selectClassNames } =
54
+ useDropdown({
55
+ id,
56
+ leadingIcon,
57
+ onMenuOpen,
58
+ required,
59
+ classNames,
60
+ size,
61
+ showSelectedCount,
62
+ components: _components,
63
+ inputId: _inputId,
64
+ instanceId: _instanceId,
65
+ })
66
+
67
+ return (
68
+ <div className={className} id={fieldId}>
69
+ <Container
70
+ size={size}
71
+ hasButton={false}
72
+ label={label}
73
+ showOptional={showOptional}
74
+ required={required}
75
+ variant={"dark"}
76
+ >
77
+ {label && (
78
+ <Label
79
+ inputId={inputId}
80
+ label={label}
81
+ showOptional={showOptional}
82
+ required={required}
83
+ variant={"dark"}
84
+ />
85
+ )}
86
+
87
+ <AsyncReactSelect
88
+ unstyled
89
+ inputId={inputId}
90
+ required={required}
91
+ onMenuOpen={handleMenuOpen}
92
+ openMenuOnFocus={openMenuOnFocus}
93
+ components={stableComponents}
94
+ classNames={selectClassNames}
95
+ instanceId={instanceId}
96
+ {...rest}
97
+ />
98
+ </Container>
99
+ {error && <ErrorMessage id={inputId} error={error} />}
100
+ </div>
101
+ )
102
+ }
103
+
104
+ export default AsyncDropdown
105
+ export type { AsyncDropdownProps }
@@ -0,0 +1,33 @@
1
+ ## Usage with `react-hook-form`
2
+
3
+ Due to the nature of the component should, you want to use the Dropdown within a form managed by `react-hook-form`, you'll need to use their `Controller` component:
4
+
5
+ ```tsx
6
+ "use client"
7
+
8
+ import React from "react"
9
+ import { useForm, Controller } from "react-hook-form"
10
+
11
+ export default function MyForm() {
12
+ const { control, handleSubmit } = useForm()
13
+
14
+ return (
15
+ <form onSubmit={handleSubmit(data => console.log(data))}>
16
+ <Controller
17
+ name="controlled-dropdown"
18
+ control={control}
19
+ render={({ field }) => {
20
+ return <Dropdown {...dropdownProps} {...field} />
21
+ }}
22
+ />
23
+ </form>
24
+ )
25
+ }
26
+ ```
27
+
28
+ Doing this will mean that the result from the component will be correctly passed to `rhf`'s submit handler.
29
+
30
+ ```json
31
+ // example result from single item select
32
+ [{ "label": "Item 1", "value": "item-1" }]
33
+ ```