diff --git a/.gitignore b/.gitignore index a547bf3..1461e83 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +package-lock.json # Editor directories and files .vscode/* @@ -22,3 +23,6 @@ dist-ssr *.njsproj *.sln *.sw? + +.env +/src/pages/test.jsx diff --git a/README.md b/README.md index 86f5adf..5234659 100644 --- a/README.md +++ b/README.md @@ -1,13 +1 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. -# Frontend +# ArchAIve Frontend diff --git a/package-lock.json b/package-lock.json index 76258e4..ad74479 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2683,11 +2683,10 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" diff --git a/package.json b/package.json index 4056e2e..67d7a79 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "redux": "^5.0.1" }, "devDependencies": { + "@chakra-ui/cli": "^3.21.1", "@eslint/js": "^9.25.0", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", diff --git a/src/Layout.jsx b/src/Layout.jsx index 6385412..fdb152a 100644 --- a/src/Layout.jsx +++ b/src/Layout.jsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react' import { useDispatch } from 'react-redux' import { Outlet, useNavigate } from 'react-router'; -import "./core.css"; +import Navbar from './components/Navbar'; function Layout() { const dispatch = useDispatch(); @@ -13,6 +13,7 @@ function Layout() { return (
+
) diff --git a/src/assets/hp1.png b/src/assets/hp1.png new file mode 100644 index 0000000..0180bce Binary files /dev/null and b/src/assets/hp1.png differ diff --git a/src/assets/hp2.png b/src/assets/hp2.png new file mode 100644 index 0000000..1d80252 Binary files /dev/null and b/src/assets/hp2.png differ diff --git a/src/assets/hp3.png b/src/assets/hp3.png new file mode 100644 index 0000000..629e057 Binary files /dev/null and b/src/assets/hp3.png differ diff --git a/src/assets/hp4.png b/src/assets/hp4.png new file mode 100644 index 0000000..12df40b Binary files /dev/null and b/src/assets/hp4.png differ diff --git a/src/assets/hp5.png b/src/assets/hp5.png new file mode 100644 index 0000000..4f54d4c Binary files /dev/null and b/src/assets/hp5.png differ diff --git a/src/assets/hp6.png b/src/assets/hp6.png new file mode 100644 index 0000000..a05695a Binary files /dev/null and b/src/assets/hp6.png differ diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx new file mode 100644 index 0000000..fa5b3dd --- /dev/null +++ b/src/components/Navbar.jsx @@ -0,0 +1,28 @@ +import React, { useState } from 'react' +import Sidebar from './Sidebar' +import { Avatar, Box, Button, Flex, HStack, Icon, Spacer, Text } from '@chakra-ui/react' +import { FaHamburger } from 'react-icons/fa' +import { FaBarsStaggered } from 'react-icons/fa6' + +function Navbar() { + const [isOpen, setIsOpen] = useState(false); + + const toggleSidebar = () => { + setIsOpen(!isOpen); + } + + return <> + + + + ArchAIve + + + + + + setIsOpen(e.open)} /> + +} + +export default Navbar \ No newline at end of file diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx new file mode 100644 index 0000000..f464d4b --- /dev/null +++ b/src/components/Sidebar.jsx @@ -0,0 +1,34 @@ +import { Button, CloseButton, Drawer, Portal, Text } from '@chakra-ui/react' +import React, { useState } from 'react' + +function Sidebar({ isOpen, onOpenChange }) { + return ( + + + + + + + Drawer Title + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. +

+
+ + + + + + + +
+
+
+
+ ) +} + +export default Sidebar \ No newline at end of file diff --git a/src/components/ui/accordion.jsx b/src/components/ui/accordion.jsx new file mode 100644 index 0000000..a0e86f8 --- /dev/null +++ b/src/components/ui/accordion.jsx @@ -0,0 +1,39 @@ +import { Accordion, HStack } from '@chakra-ui/react' +import * as React from 'react' +import { LuChevronDown } from 'react-icons/lu' + +export const AccordionItemTrigger = React.forwardRef( + function AccordionItemTrigger(props, ref) { + const { children, indicatorPlacement = 'end', ...rest } = props + return ( + + {indicatorPlacement === 'start' && ( + + + + )} + + {children} + + {indicatorPlacement === 'end' && ( + + + + )} + + ) + }, +) + +export const AccordionItemContent = React.forwardRef( + function AccordionItemContent(props, ref) { + return ( + + + + ) + }, +) + +export const AccordionRoot = Accordion.Root +export const AccordionItem = Accordion.Item diff --git a/src/components/ui/action-bar.jsx b/src/components/ui/action-bar.jsx new file mode 100644 index 0000000..450f11b --- /dev/null +++ b/src/components/ui/action-bar.jsx @@ -0,0 +1,33 @@ +import { ActionBar, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +export const ActionBarContent = React.forwardRef( + function ActionBarContent(props, ref) { + const { children, portalled = true, portalRef, ...rest } = props + + return ( + + + + {children} + + + + ) + }, +) + +export const ActionBarCloseTrigger = React.forwardRef( + function ActionBarCloseTrigger(props, ref) { + return ( + + + + ) + }, +) + +export const ActionBarRoot = ActionBar.Root +export const ActionBarSelectionTrigger = ActionBar.SelectionTrigger +export const ActionBarSeparator = ActionBar.Separator diff --git a/src/components/ui/alert.jsx b/src/components/ui/alert.jsx new file mode 100644 index 0000000..ba74303 --- /dev/null +++ b/src/components/ui/alert.jsx @@ -0,0 +1,20 @@ +import { Alert as ChakraAlert } from '@chakra-ui/react' +import * as React from 'react' + +export const Alert = React.forwardRef(function Alert(props, ref) { + const { title, children, icon, startElement, endElement, ...rest } = props + return ( + + {startElement || {icon}} + {children ? ( + + {title} + {children} + + ) : ( + {title} + )} + {endElement} + + ) +}) diff --git a/src/components/ui/avatar.jsx b/src/components/ui/avatar.jsx new file mode 100644 index 0000000..9321b39 --- /dev/null +++ b/src/components/ui/avatar.jsx @@ -0,0 +1,21 @@ +import { + Avatar as ChakraAvatar, + AvatarGroup as ChakraAvatarGroup, +} from '@chakra-ui/react' +import * as React from 'react' + +export const Avatar = React.forwardRef(function Avatar(props, ref) { + const { name, src, srcSet, loading, icon, fallback, children, ...rest } = + props + return ( + + + {icon || fallback} + + + {children} + + ) +}) + +export const AvatarGroup = ChakraAvatarGroup diff --git a/src/components/ui/blockquote.jsx b/src/components/ui/blockquote.jsx new file mode 100644 index 0000000..31bbf46 --- /dev/null +++ b/src/components/ui/blockquote.jsx @@ -0,0 +1,22 @@ +import { Blockquote as ChakraBlockquote } from '@chakra-ui/react' +import * as React from 'react' + +export const Blockquote = React.forwardRef(function Blockquote(props, ref) { + const { children, cite, citeUrl, showDash, icon, ...rest } = props + + return ( + + {icon} + + {children} + + {cite && ( + + {showDash ? <>— : null} {cite} + + )} + + ) +}) + +export const BlockquoteIcon = ChakraBlockquote.Icon diff --git a/src/components/ui/breadcrumb.jsx b/src/components/ui/breadcrumb.jsx new file mode 100644 index 0000000..af9c4d0 --- /dev/null +++ b/src/components/ui/breadcrumb.jsx @@ -0,0 +1,34 @@ +import { Breadcrumb } from '@chakra-ui/react' +import * as React from 'react' + +export const BreadcrumbRoot = React.forwardRef( + function BreadcrumbRoot(props, ref) { + const { separator, separatorGap, children, ...rest } = props + + const validChildren = React.Children.toArray(children).filter( + React.isValidElement, + ) + + return ( + + + {validChildren.map((child, index) => { + const last = index === validChildren.length - 1 + return ( + + {child} + {!last && ( + {separator} + )} + + ) + })} + + + ) + }, +) + +export const BreadcrumbLink = Breadcrumb.Link +export const BreadcrumbCurrentLink = Breadcrumb.CurrentLink +export const BreadcrumbEllipsis = Breadcrumb.Ellipsis diff --git a/src/components/ui/checkbox-card.jsx b/src/components/ui/checkbox-card.jsx new file mode 100644 index 0000000..e00ed62 --- /dev/null +++ b/src/components/ui/checkbox-card.jsx @@ -0,0 +1,45 @@ +import { CheckboxCard as ChakraCheckboxCard } from '@chakra-ui/react' +import * as React from 'react' + +export const CheckboxCard = React.forwardRef(function CheckboxCard(props, ref) { + const { + inputProps, + label, + description, + icon, + addon, + indicator = , + indicatorPlacement = 'end', + ...rest + } = props + + const hasContent = label || description || icon + const ContentWrapper = indicator ? ChakraCheckboxCard.Content : React.Fragment + + return ( + + + + {indicatorPlacement === 'start' && indicator} + {hasContent && ( + + {icon} + {label && ( + {label} + )} + {description && ( + + {description} + + )} + {indicatorPlacement === 'inside' && indicator} + + )} + {indicatorPlacement === 'end' && indicator} + + {addon && {addon}} + + ) +}) + +export const CheckboxCardIndicator = ChakraCheckboxCard.Indicator diff --git a/src/components/ui/checkbox.jsx b/src/components/ui/checkbox.jsx new file mode 100644 index 0000000..78ff43a --- /dev/null +++ b/src/components/ui/checkbox.jsx @@ -0,0 +1,17 @@ +import { Checkbox as ChakraCheckbox } from '@chakra-ui/react' +import * as React from 'react' + +export const Checkbox = React.forwardRef(function Checkbox(props, ref) { + const { icon, children, inputProps, rootRef, ...rest } = props + return ( + + + + {icon || } + + {children != null && ( + {children} + )} + + ) +}) diff --git a/src/components/ui/clipboard.jsx b/src/components/ui/clipboard.jsx new file mode 100644 index 0000000..2b98420 --- /dev/null +++ b/src/components/ui/clipboard.jsx @@ -0,0 +1,101 @@ +import { + Button, + Clipboard as ChakraClipboard, + IconButton, + Input, +} from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck, LuClipboard, LuLink } from 'react-icons/lu' + +const ClipboardIcon = React.forwardRef(function ClipboardIcon(props, ref) { + return ( + } {...props} ref={ref}> + + + ) +}) + +const ClipboardCopyText = React.forwardRef( + function ClipboardCopyText(props, ref) { + return ( + + Copy + + ) + }, +) + +export const ClipboardLabel = React.forwardRef( + function ClipboardLabel(props, ref) { + return ( + + ) + }, +) + +export const ClipboardButton = React.forwardRef( + function ClipboardButton(props, ref) { + return ( + + + + ) + }, +) + +export const ClipboardLink = React.forwardRef( + function ClipboardLink(props, ref) { + return ( + + + + ) + }, +) + +export const ClipboardIconButton = React.forwardRef( + function ClipboardIconButton(props, ref) { + return ( + + + + + + + ) + }, +) + +export const ClipboardInput = React.forwardRef( + function ClipboardInputElement(props, ref) { + return ( + + + + ) + }, +) + +export const ClipboardRoot = ChakraClipboard.Root diff --git a/src/components/ui/close-button.jsx b/src/components/ui/close-button.jsx new file mode 100644 index 0000000..07d3631 --- /dev/null +++ b/src/components/ui/close-button.jsx @@ -0,0 +1,20 @@ +function _nullishCoalesce(lhs, rhsFn) { + if (lhs != null) { + return lhs + } else { + return rhsFn() + } +} +import { IconButton as ChakraIconButton } from '@chakra-ui/react' +import * as React from 'react' +import { LuX } from 'react-icons/lu' + +export const CloseButton = React.forwardRef(function CloseButton(props, ref) { + return ( + + {_nullishCoalesce(props.children, () => ( + + ))} + + ) +}) diff --git a/src/components/ui/color-picker.jsx b/src/components/ui/color-picker.jsx new file mode 100644 index 0000000..c997bb5 --- /dev/null +++ b/src/components/ui/color-picker.jsx @@ -0,0 +1,201 @@ +import { + ColorPicker as ChakraColorPicker, + For, + IconButton, + Portal, + Span, + Stack, + Text, + VStack, +} from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck, LuPipette } from 'react-icons/lu' + +export const ColorPickerTrigger = React.forwardRef( + function ColorPickerTrigger(props, ref) { + const { fitContent, ...rest } = props + return ( + + {props.children || } + + ) + }, +) + +export const ColorPickerInput = React.forwardRef( + function ColorHexInput(props, ref) { + return + }, +) + +export const ColorPickerContent = React.forwardRef( + function ColorPickerContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + }, +) + +export const ColorPickerInlineContent = React.forwardRef( + function ColorPickerInlineContent(props, ref) { + return ( + + ) + }, +) + +export const ColorPickerSliders = React.forwardRef( + function ColorPickerSliders(props, ref) { + return ( + + + + + ) + }, +) + +export const ColorPickerArea = React.forwardRef( + function ColorPickerArea(props, ref) { + return ( + + + + + ) + }, +) + +export const ColorPickerEyeDropper = React.forwardRef( + function ColorPickerEyeDropper(props, ref) { + return ( + + + + + + ) + }, +) + +export const ColorPickerChannelSlider = React.forwardRef( + function ColorPickerSlider(props, ref) { + return ( + + + + + + ) + }, +) + +export const ColorPickerSwatchTrigger = React.forwardRef( + function ColorPickerSwatchTrigger(props, ref) { + const { swatchSize, children, ...rest } = props + return ( + + {children || ( + + + + + + )} + + ) + }, +) + +export const ColorPickerRoot = React.forwardRef( + function ColorPickerRoot(props, ref) { + return ( + + {props.children} + + + ) + }, +) + +const formatMap = { + rgba: ['red', 'green', 'blue', 'alpha'], + hsla: ['hue', 'saturation', 'lightness', 'alpha'], + hsba: ['hue', 'saturation', 'brightness', 'alpha'], + hexa: ['hex', 'alpha'], +} + +export const ColorPickerChannelInputs = React.forwardRef( + function ColorPickerChannelInputs(props, ref) { + const channels = formatMap[props.format] + return ( + + {channels.map((channel) => ( + + + + {channel.charAt(0).toUpperCase()} + + + ))} + + ) + }, +) + +export const ColorPickerChannelSliders = React.forwardRef( + function ColorPickerChannelSliders(props, ref) { + const channels = formatMap[props.format] + return ( + + + {(channel) => ( + + + {channel} + + + + )} + + + ) + }, +) + +export const ColorPickerLabel = ChakraColorPicker.Label +export const ColorPickerControl = ChakraColorPicker.Control +export const ColorPickerValueText = ChakraColorPicker.ValueText +export const ColorPickerValueSwatch = ChakraColorPicker.ValueSwatch +export const ColorPickerChannelInput = ChakraColorPicker.ChannelInput +export const ColorPickerSwatchGroup = ChakraColorPicker.SwatchGroup diff --git a/src/components/ui/data-list.jsx b/src/components/ui/data-list.jsx new file mode 100644 index 0000000..8ad0000 --- /dev/null +++ b/src/components/ui/data-list.jsx @@ -0,0 +1,21 @@ +import { DataList as ChakraDataList } from '@chakra-ui/react' +import { InfoTip } from './toggle-tip' +import * as React from 'react' + +export const DataListRoot = ChakraDataList.Root + +export const DataListItem = React.forwardRef(function DataListItem(props, ref) { + const { label, info, value, children, grow, ...rest } = props + return ( + + + {label} + {info && {info}} + + + {value} + + {children} + + ) +}) diff --git a/src/components/ui/dialog.jsx b/src/components/ui/dialog.jsx new file mode 100644 index 0000000..4558ec1 --- /dev/null +++ b/src/components/ui/dialog.jsx @@ -0,0 +1,54 @@ +import { Dialog as ChakraDialog, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +export const DialogContent = React.forwardRef( + function DialogContent(props, ref) { + const { + children, + portalled = true, + portalRef, + backdrop = true, + ...rest + } = props + + return ( + + {backdrop && } + + + {children} + + + + ) + }, +) + +export const DialogCloseTrigger = React.forwardRef( + function DialogCloseTrigger(props, ref) { + return ( + + + {props.children} + + + ) + }, +) + +export const DialogRoot = ChakraDialog.Root +export const DialogFooter = ChakraDialog.Footer +export const DialogHeader = ChakraDialog.Header +export const DialogBody = ChakraDialog.Body +export const DialogBackdrop = ChakraDialog.Backdrop +export const DialogTitle = ChakraDialog.Title +export const DialogDescription = ChakraDialog.Description +export const DialogTrigger = ChakraDialog.Trigger +export const DialogActionTrigger = ChakraDialog.ActionTrigger diff --git a/src/components/ui/drawer.jsx b/src/components/ui/drawer.jsx new file mode 100644 index 0000000..6c51d29 --- /dev/null +++ b/src/components/ui/drawer.jsx @@ -0,0 +1,44 @@ +import { Drawer as ChakraDrawer, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +export const DrawerContent = React.forwardRef( + function DrawerContent(props, ref) { + const { children, portalled = true, portalRef, offset, ...rest } = props + return ( + + + + {children} + + + + ) + }, +) + +export const DrawerCloseTrigger = React.forwardRef( + function DrawerCloseTrigger(props, ref) { + return ( + + + + ) + }, +) + +export const DrawerTrigger = ChakraDrawer.Trigger +export const DrawerRoot = ChakraDrawer.Root +export const DrawerFooter = ChakraDrawer.Footer +export const DrawerHeader = ChakraDrawer.Header +export const DrawerBody = ChakraDrawer.Body +export const DrawerBackdrop = ChakraDrawer.Backdrop +export const DrawerDescription = ChakraDrawer.Description +export const DrawerTitle = ChakraDrawer.Title +export const DrawerActionTrigger = ChakraDrawer.ActionTrigger diff --git a/src/components/ui/empty-state.jsx b/src/components/ui/empty-state.jsx new file mode 100644 index 0000000..8e87fac --- /dev/null +++ b/src/components/ui/empty-state.jsx @@ -0,0 +1,26 @@ +import { EmptyState as ChakraEmptyState, VStack } from '@chakra-ui/react' +import * as React from 'react' + +export const EmptyState = React.forwardRef(function EmptyState(props, ref) { + const { title, description, icon, children, ...rest } = props + return ( + + + {icon && ( + {icon} + )} + {description ? ( + + {title} + + {description} + + + ) : ( + {title} + )} + {children} + + + ) +}) diff --git a/src/components/ui/field.jsx b/src/components/ui/field.jsx new file mode 100644 index 0000000..d890c88 --- /dev/null +++ b/src/components/ui/field.jsx @@ -0,0 +1,22 @@ +import { Field as ChakraField } from '@chakra-ui/react' +import * as React from 'react' + +export const Field = React.forwardRef(function Field(props, ref) { + const { label, children, helperText, errorText, optionalText, ...rest } = + props + return ( + + {label && ( + + {label} + + + )} + {children} + {helperText && ( + {helperText} + )} + {errorText && {errorText}} + + ) +}) diff --git a/src/components/ui/file-upload.jsx b/src/components/ui/file-upload.jsx new file mode 100644 index 0000000..6b8c8da --- /dev/null +++ b/src/components/ui/file-upload.jsx @@ -0,0 +1,141 @@ +function _nullishCoalesce(lhs, rhsFn) { + if (lhs != null) { + return lhs + } else { + return rhsFn() + } +} +;('use client') + +import { + Button, + FileUpload as ChakraFileUpload, + Icon, + IconButton, + Span, + Text, + useFileUploadContext, + useRecipe, +} from '@chakra-ui/react' +import * as React from 'react' +import { LuFile, LuUpload, LuX } from 'react-icons/lu' + +export const FileUploadRoot = React.forwardRef( + function FileUploadRoot(props, ref) { + const { children, inputProps, ...rest } = props + return ( + + + {children} + + ) + }, +) + +export const FileUploadDropzone = React.forwardRef( + function FileUploadDropzone(props, ref) { + const { children, label, description, ...rest } = props + return ( + + + + + +
{label}
+ {description && {description}} +
+ {children} +
+ ) + }, +) + +const FileUploadItem = React.forwardRef(function FileUploadItem(props, ref) { + const { file, showSize, clearable } = props + return ( + + + + + + + + {showSize ? ( + + + + + ) : ( + + )} + + {clearable && ( + + + + + + )} + + ) +}) + +export const FileUploadList = React.forwardRef( + function FileUploadList(props, ref) { + const { showSize, clearable, files, ...rest } = props + + const fileUpload = useFileUploadContext() + const acceptedFiles = _nullishCoalesce( + files, + () => fileUpload.acceptedFiles, + ) + + if (acceptedFiles.length === 0) return null + + return ( + + {acceptedFiles.map((file) => ( + + ))} + + ) + }, +) + +export const FileInput = React.forwardRef(function FileInput(props, ref) { + const inputRecipe = useRecipe({ key: 'input' }) + const [recipeProps, restProps] = inputRecipe.splitVariantProps(props) + const { placeholder = 'Select file(s)', ...rest } = restProps + return ( + + + + ) +}) + +export const FileUploadLabel = ChakraFileUpload.Label +export const FileUploadClearTrigger = ChakraFileUpload.ClearTrigger +export const FileUploadTrigger = ChakraFileUpload.Trigger diff --git a/src/components/ui/hover-card.jsx b/src/components/ui/hover-card.jsx new file mode 100644 index 0000000..1dd4b18 --- /dev/null +++ b/src/components/ui/hover-card.jsx @@ -0,0 +1,29 @@ +import { HoverCard, Portal } from '@chakra-ui/react' +import * as React from 'react' + +export const HoverCardContent = React.forwardRef( + function HoverCardContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + + return ( + + + + + + ) + }, +) + +export const HoverCardArrow = React.forwardRef( + function HoverCardArrow(props, ref) { + return ( + + + + ) + }, +) + +export const HoverCardRoot = HoverCard.Root +export const HoverCardTrigger = HoverCard.Trigger diff --git a/src/components/ui/input-group.jsx b/src/components/ui/input-group.jsx new file mode 100644 index 0000000..46d83b2 --- /dev/null +++ b/src/components/ui/input-group.jsx @@ -0,0 +1,39 @@ +import { Group, InputElement } from '@chakra-ui/react' +import * as React from 'react' + +export const InputGroup = React.forwardRef(function InputGroup(props, ref) { + const { + startElement, + startElementProps, + endElement, + endElementProps, + children, + startOffset = '6px', + endOffset = '6px', + ...rest + } = props + + const child = React.Children.only(children) + + return ( + + {startElement && ( + + {startElement} + + )} + {React.cloneElement(child, { + ...(startElement && { + ps: `calc(var(--input-height) - ${startOffset})`, + }), + ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }), + ...children.props, + })} + {endElement && ( + + {endElement} + + )} + + ) +}) diff --git a/src/components/ui/link-button.jsx b/src/components/ui/link-button.jsx new file mode 100644 index 0000000..23ca046 --- /dev/null +++ b/src/components/ui/link-button.jsx @@ -0,0 +1,8 @@ +'use client' + +import { createRecipeContext } from '@chakra-ui/react' + +const { withContext } = createRecipeContext({ key: 'button' }) + +// Replace "a" with your framework's link component +export const LinkButton = withContext('a') diff --git a/src/components/ui/menu.jsx b/src/components/ui/menu.jsx new file mode 100644 index 0000000..68c1a8b --- /dev/null +++ b/src/components/ui/menu.jsx @@ -0,0 +1,94 @@ +'use client' + +import { AbsoluteCenter, Menu as ChakraMenu, Portal } from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck, LuChevronRight } from 'react-icons/lu' + +export const MenuContent = React.forwardRef(function MenuContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) +}) + +export const MenuArrow = React.forwardRef(function MenuArrow(props, ref) { + return ( + + + + ) +}) + +export const MenuCheckboxItem = React.forwardRef( + function MenuCheckboxItem(props, ref) { + return ( + + + + + + + {props.children} + + ) + }, +) + +export const MenuRadioItem = React.forwardRef( + function MenuRadioItem(props, ref) { + const { children, ...rest } = props + return ( + + + + + + + {children} + + ) + }, +) + +export const MenuItemGroup = React.forwardRef( + function MenuItemGroup(props, ref) { + const { title, children, ...rest } = props + return ( + + {title && ( + + {title} + + )} + {children} + + ) + }, +) + +export const MenuTriggerItem = React.forwardRef( + function MenuTriggerItem(props, ref) { + const { startIcon, children, ...rest } = props + return ( + + {startIcon} + {children} + + + ) + }, +) + +export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup +export const MenuContextTrigger = ChakraMenu.ContextTrigger +export const MenuRoot = ChakraMenu.Root +export const MenuSeparator = ChakraMenu.Separator + +export const MenuItem = ChakraMenu.Item +export const MenuItemText = ChakraMenu.ItemText +export const MenuItemCommand = ChakraMenu.ItemCommand +export const MenuTrigger = ChakraMenu.Trigger diff --git a/src/components/ui/native-select.jsx b/src/components/ui/native-select.jsx new file mode 100644 index 0000000..40e099e --- /dev/null +++ b/src/components/ui/native-select.jsx @@ -0,0 +1,80 @@ +function _optionalChain(ops) { + let lastAccessLHS = undefined + let value = ops[0] + let i = 1 + while (i < ops.length) { + const op = ops[i] + const fn = ops[i + 1] + i += 2 + if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { + return undefined + } + if (op === 'access' || op === 'optionalAccess') { + lastAccessLHS = value + value = fn(value) + } else if (op === 'call' || op === 'optionalCall') { + value = fn((...args) => value.call(lastAccessLHS, ...args)) + lastAccessLHS = undefined + } + } + return value +} +;('use client') + +import { NativeSelect as Select } from '@chakra-ui/react' +import * as React from 'react' + +export const NativeSelectRoot = React.forwardRef( + function NativeSelect(props, ref) { + const { icon, children, ...rest } = props + return ( + + {children} + {icon} + + ) + }, +) + +export const NativeSelectField = React.forwardRef( + function NativeSelectField(props, ref) { + const { items: itemsProp, children, ...rest } = props + + const items = React.useMemo( + () => + _optionalChain([ + itemsProp, + 'optionalAccess', + (_) => _.map, + 'call', + (_2) => + _2((item) => + typeof item === 'string' ? { label: item, value: item } : item, + ), + ]), + [itemsProp], + ) + + return ( + + {children} + {_optionalChain([ + items, + 'optionalAccess', + (_3) => _3.map, + 'call', + (_4) => + _4((item) => ( + + )), + ])} + + ) + }, +) diff --git a/src/components/ui/number-input.jsx b/src/components/ui/number-input.jsx new file mode 100644 index 0000000..e35fe51 --- /dev/null +++ b/src/components/ui/number-input.jsx @@ -0,0 +1,21 @@ +import { NumberInput as ChakraNumberInput } from '@chakra-ui/react' +import * as React from 'react' + +export const NumberInputRoot = React.forwardRef( + function NumberInput(props, ref) { + const { children, ...rest } = props + return ( + + {children} + + + + + + ) + }, +) + +export const NumberInputField = ChakraNumberInput.Input +export const NumberInputScrubber = ChakraNumberInput.Scrubber +export const NumberInputLabel = ChakraNumberInput.Label diff --git a/src/components/ui/pagination.jsx b/src/components/ui/pagination.jsx new file mode 100644 index 0000000..e84b1f2 --- /dev/null +++ b/src/components/ui/pagination.jsx @@ -0,0 +1,176 @@ +'use client' + +import { + Button, + Pagination as ChakraPagination, + IconButton, + Text, + createContext, + usePaginationContext, +} from '@chakra-ui/react' +import * as React from 'react' +import { + HiChevronLeft, + HiChevronRight, + HiMiniEllipsisHorizontal, +} from 'react-icons/hi2' +import { LinkButton } from './link-button' + +const [RootPropsProvider, useRootProps] = createContext({ + name: 'RootPropsProvider', +}) + +const variantMap = { + outline: { default: 'ghost', ellipsis: 'plain', current: 'outline' }, + solid: { default: 'outline', ellipsis: 'outline', current: 'solid' }, + subtle: { default: 'ghost', ellipsis: 'plain', current: 'subtle' }, +} + +export const PaginationRoot = React.forwardRef( + function PaginationRoot(props, ref) { + const { size = 'sm', variant = 'outline', getHref, ...rest } = props + return ( + + + + ) + }, +) + +export const PaginationEllipsis = React.forwardRef( + function PaginationEllipsis(props, ref) { + const { size, variantMap } = useRootProps() + return ( + + + + ) + }, +) + +export const PaginationItem = React.forwardRef( + function PaginationItem(props, ref) { + const { page } = usePaginationContext() + const { size, variantMap, getHref } = useRootProps() + + const current = page === props.value + const variant = current ? variantMap.current : variantMap.default + + if (getHref) { + return ( + + {props.value} + + ) + } + + return ( + + + + ) + }, +) + +export const PaginationPrevTrigger = React.forwardRef( + function PaginationPrevTrigger(props, ref) { + const { size, variantMap, getHref } = useRootProps() + const { previousPage } = usePaginationContext() + + if (getHref) { + return ( + + + + ) + } + + return ( + + + + + + ) + }, +) + +export const PaginationNextTrigger = React.forwardRef( + function PaginationNextTrigger(props, ref) { + const { size, variantMap, getHref } = useRootProps() + const { nextPage } = usePaginationContext() + + if (getHref) { + return ( + + + + ) + } + + return ( + + + + + + ) + }, +) + +export const PaginationItems = (props) => { + return ( + + {({ pages }) => + pages.map((page, index) => { + return page.type === 'ellipsis' ? ( + + ) : ( + + ) + }) + } + + ) +} + +export const PaginationPageText = React.forwardRef( + function PaginationPageText(props, ref) { + const { format = 'compact', ...rest } = props + const { page, totalPages, pageRange, count } = usePaginationContext() + const content = React.useMemo(() => { + if (format === 'short') return `${page} / ${totalPages}` + if (format === 'compact') return `${page} of ${totalPages}` + return `${pageRange.start + 1} - ${Math.min(pageRange.end, count)} of ${count}` + }, [format, page, totalPages, pageRange, count]) + + return ( + + {content} + + ) + }, +) diff --git a/src/components/ui/password-input.jsx b/src/components/ui/password-input.jsx new file mode 100644 index 0000000..95c082b --- /dev/null +++ b/src/components/ui/password-input.jsx @@ -0,0 +1,121 @@ +'use client' + +import { + Box, + HStack, + IconButton, + Input, + InputGroup, + Stack, + mergeRefs, + useControllableState, +} from '@chakra-ui/react' +import * as React from 'react' +import { LuEye, LuEyeOff } from 'react-icons/lu' + +export const PasswordInput = React.forwardRef( + function PasswordInput(props, ref) { + const { + rootProps, + defaultVisible, + visible: visibleProp, + onVisibleChange, + visibilityIcon = { on: , off: }, + ...rest + } = props + + const [visible, setVisible] = useControllableState({ + value: visibleProp, + defaultValue: defaultVisible || false, + onChange: onVisibleChange, + }) + + const inputRef = React.useRef(null) + + return ( + { + if (rest.disabled) return + if (e.button !== 0) return + e.preventDefault() + setVisible(!visible) + }} + > + {visible ? visibilityIcon.off : visibilityIcon.on} + + } + {...rootProps} + > + + + ) + }, +) + +const VisibilityTrigger = React.forwardRef( + function VisibilityTrigger(props, ref) { + return ( + + ) + }, +) + +export const PasswordStrengthMeter = React.forwardRef( + function PasswordStrengthMeter(props, ref) { + const { max = 4, value, ...rest } = props + + const percent = (value / max) * 100 + const { label, colorPalette } = getColorPalette(percent) + + return ( + + + {Array.from({ length: max }).map((_, index) => ( + + ))} + + {label && {label}} + + ) + }, +) + +function getColorPalette(percent) { + switch (true) { + case percent < 33: + return { label: 'Low', colorPalette: 'red' } + case percent < 66: + return { label: 'Medium', colorPalette: 'orange' } + default: + return { label: 'High', colorPalette: 'green' } + } +} diff --git a/src/components/ui/pin-input.jsx b/src/components/ui/pin-input.jsx new file mode 100644 index 0000000..7f2afcd --- /dev/null +++ b/src/components/ui/pin-input.jsx @@ -0,0 +1,18 @@ +import { PinInput as ChakraPinInput, Group } from '@chakra-ui/react' +import * as React from 'react' + +export const PinInput = React.forwardRef(function PinInput(props, ref) { + const { count = 4, inputProps, rootRef, attached, ...rest } = props + return ( + + + + + {Array.from({ length: count }).map((_, index) => ( + + ))} + + + + ) +}) diff --git a/src/components/ui/popover.jsx b/src/components/ui/popover.jsx new file mode 100644 index 0000000..6449eef --- /dev/null +++ b/src/components/ui/popover.jsx @@ -0,0 +1,49 @@ +import { Popover as ChakraPopover, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +export const PopoverContent = React.forwardRef( + function PopoverContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + }, +) + +export const PopoverArrow = React.forwardRef(function PopoverArrow(props, ref) { + return ( + + + + ) +}) + +export const PopoverCloseTrigger = React.forwardRef( + function PopoverCloseTrigger(props, ref) { + return ( + + + + ) + }, +) + +export const PopoverTitle = ChakraPopover.Title +export const PopoverDescription = ChakraPopover.Description +export const PopoverFooter = ChakraPopover.Footer +export const PopoverHeader = ChakraPopover.Header +export const PopoverRoot = ChakraPopover.Root +export const PopoverBody = ChakraPopover.Body +export const PopoverTrigger = ChakraPopover.Trigger diff --git a/src/components/ui/progress-circle.jsx b/src/components/ui/progress-circle.jsx new file mode 100644 index 0000000..d709606 --- /dev/null +++ b/src/components/ui/progress-circle.jsx @@ -0,0 +1,29 @@ +import { + AbsoluteCenter, + ProgressCircle as ChakraProgressCircle, +} from '@chakra-ui/react' +import * as React from 'react' + +export const ProgressCircleRing = React.forwardRef( + function ProgressCircleRing(props, ref) { + const { trackColor, cap, color, ...rest } = props + return ( + + + + + ) + }, +) + +export const ProgressCircleValueText = React.forwardRef( + function ProgressCircleValueText(props, ref) { + return ( + + + + ) + }, +) + +export const ProgressCircleRoot = ChakraProgressCircle.Root diff --git a/src/components/ui/progress.jsx b/src/components/ui/progress.jsx new file mode 100644 index 0000000..300c0fb --- /dev/null +++ b/src/components/ui/progress.jsx @@ -0,0 +1,26 @@ +import { Progress as ChakraProgress } from '@chakra-ui/react' +import { InfoTip } from './toggle-tip' +import * as React from 'react' + +export const ProgressBar = React.forwardRef(function ProgressBar(props, ref) { + return ( + + + + ) +}) + +export const ProgressLabel = React.forwardRef( + function ProgressLabel(props, ref) { + const { children, info, ...rest } = props + return ( + + {children} + {info && {info}} + + ) + }, +) + +export const ProgressRoot = ChakraProgress.Root +export const ProgressValueText = ChakraProgress.ValueText diff --git a/src/components/ui/prose.jsx b/src/components/ui/prose.jsx new file mode 100644 index 0000000..c927a3f --- /dev/null +++ b/src/components/ui/prose.jsx @@ -0,0 +1,264 @@ +'use client' + +import { chakra } from '@chakra-ui/react' + +export const Prose = chakra('div', { + base: { + color: 'fg.muted', + maxWidth: '65ch', + fontSize: 'sm', + lineHeight: '1.7em', + '& p': { + marginTop: '1em', + marginBottom: '1em', + }, + '& blockquote': { + marginTop: '1.285em', + marginBottom: '1.285em', + paddingInline: '1.285em', + borderInlineStartWidth: '0.25em', + }, + '& a': { + color: 'fg', + textDecoration: 'underline', + textUnderlineOffset: '3px', + textDecorationThickness: '2px', + textDecorationColor: 'border.muted', + fontWeight: '500', + }, + '& strong': { + fontWeight: '600', + }, + '& a strong': { + color: 'inherit', + }, + '& h1': { + fontSize: '2.15em', + letterSpacing: '-0.02em', + marginTop: '0', + marginBottom: '0.8em', + lineHeight: '1.2em', + }, + '& h2': { + fontSize: '1.4em', + letterSpacing: '-0.02em', + marginTop: '1.6em', + marginBottom: '0.8em', + lineHeight: '1.4em', + }, + '& h3': { + fontSize: '1.285em', + letterSpacing: '-0.01em', + marginTop: '1.5em', + marginBottom: '0.4em', + lineHeight: '1.5em', + }, + '& h4': { + marginTop: '1.4em', + marginBottom: '0.5em', + letterSpacing: '-0.01em', + lineHeight: '1.5em', + }, + '& img': { + marginTop: '1.7em', + marginBottom: '1.7em', + borderRadius: 'lg', + boxShadow: 'inset', + }, + '& picture': { + marginTop: '1.7em', + marginBottom: '1.7em', + }, + '& picture > img': { + marginTop: '0', + marginBottom: '0', + }, + '& video': { + marginTop: '1.7em', + marginBottom: '1.7em', + }, + '& kbd': { + fontSize: '0.85em', + borderRadius: 'xs', + paddingTop: '0.15em', + paddingBottom: '0.15em', + paddingInlineEnd: '0.35em', + paddingInlineStart: '0.35em', + fontFamily: 'inherit', + color: 'fg.muted', + '--shadow': 'colors.border', + boxShadow: '0 0 0 1px var(--shadow),0 1px 0 1px var(--shadow)', + }, + '& code': { + fontSize: '0.925em', + letterSpacing: '-0.01em', + borderRadius: 'md', + borderWidth: '1px', + padding: '0.25em', + }, + '& pre code': { + fontSize: 'inherit', + letterSpacing: 'inherit', + borderWidth: 'inherit', + padding: '0', + }, + '& h2 code': { + fontSize: '0.9em', + }, + '& h3 code': { + fontSize: '0.8em', + }, + '& pre': { + backgroundColor: 'bg.subtle', + marginTop: '1.6em', + marginBottom: '1.6em', + borderRadius: 'md', + fontSize: '0.9em', + paddingTop: '0.65em', + paddingBottom: '0.65em', + paddingInlineEnd: '1em', + paddingInlineStart: '1em', + overflowX: 'auto', + fontWeight: '400', + }, + '& ol': { + marginTop: '1em', + marginBottom: '1em', + paddingInlineStart: '1.5em', + }, + '& ul': { + marginTop: '1em', + marginBottom: '1em', + paddingInlineStart: '1.5em', + }, + '& li': { + marginTop: '0.285em', + marginBottom: '0.285em', + }, + '& ol > li': { + paddingInlineStart: '0.4em', + listStyleType: 'decimal', + '&::marker': { + color: 'fg.muted', + }, + }, + '& ul > li': { + paddingInlineStart: '0.4em', + listStyleType: 'disc', + '&::marker': { + color: 'fg.muted', + }, + }, + '& > ul > li p': { + marginTop: '0.5em', + marginBottom: '0.5em', + }, + '& > ul > li > p:first-of-type': { + marginTop: '1em', + }, + '& > ul > li > p:last-of-type': { + marginBottom: '1em', + }, + '& > ol > li > p:first-of-type': { + marginTop: '1em', + }, + '& > ol > li > p:last-of-type': { + marginBottom: '1em', + }, + '& ul ul, ul ol, ol ul, ol ol': { + marginTop: '0.5em', + marginBottom: '0.5em', + }, + '& dl': { + marginTop: '1em', + marginBottom: '1em', + }, + '& dt': { + fontWeight: '600', + marginTop: '1em', + }, + '& dd': { + marginTop: '0.285em', + paddingInlineStart: '1.5em', + }, + '& hr': { + marginTop: '2.25em', + marginBottom: '2.25em', + }, + '& :is(h1,h2,h3,h4,h5,hr) + *': { + marginTop: '0', + }, + '& table': { + width: '100%', + tableLayout: 'auto', + textAlign: 'start', + lineHeight: '1.5em', + marginTop: '2em', + marginBottom: '2em', + }, + '& thead': { + borderBottomWidth: '1px', + color: 'fg', + }, + '& tbody tr': { + borderBottomWidth: '1px', + borderBottomColor: 'border', + }, + '& thead th': { + paddingInlineEnd: '1em', + paddingBottom: '0.65em', + paddingInlineStart: '1em', + fontWeight: 'medium', + textAlign: 'start', + }, + '& thead th:first-of-type': { + paddingInlineStart: '0', + }, + '& thead th:last-of-type': { + paddingInlineEnd: '0', + }, + '& tbody td, tfoot td': { + paddingTop: '0.65em', + paddingInlineEnd: '1em', + paddingBottom: '0.65em', + paddingInlineStart: '1em', + }, + '& tbody td:first-of-type, tfoot td:first-of-type': { + paddingInlineStart: '0', + }, + '& tbody td:last-of-type, tfoot td:last-of-type': { + paddingInlineEnd: '0', + }, + '& figure': { + marginTop: '1.625em', + marginBottom: '1.625em', + }, + '& figure > *': { + marginTop: '0', + marginBottom: '0', + }, + '& figcaption': { + fontSize: '0.85em', + lineHeight: '1.25em', + marginTop: '0.85em', + color: 'fg.muted', + }, + '& h1, h2, h3, h4': { + color: 'fg', + fontWeight: '600', + }, + }, + variants: { + size: { + md: { + fontSize: 'sm', + }, + lg: { + fontSize: 'md', + }, + }, + }, + defaultVariants: { + size: 'md', + }, +}) diff --git a/src/components/ui/qr-code.jsx b/src/components/ui/qr-code.jsx new file mode 100644 index 0000000..91a20b1 --- /dev/null +++ b/src/components/ui/qr-code.jsx @@ -0,0 +1,15 @@ +import { QrCode as ChakraQrCode } from '@chakra-ui/react' +import * as React from 'react' + +export const QrCode = React.forwardRef(function QrCode(props, ref) { + const { children, fill, overlay, ...rest } = props + return ( + + + + + {overlay} + {children && {children}} + + ) +}) diff --git a/src/components/ui/radio-card.jsx b/src/components/ui/radio-card.jsx new file mode 100644 index 0000000..a0786b3 --- /dev/null +++ b/src/components/ui/radio-card.jsx @@ -0,0 +1,47 @@ +import { RadioCard } from '@chakra-ui/react' +import * as React from 'react' + +export const RadioCardItem = React.forwardRef( + function RadioCardItem(props, ref) { + const { + inputProps, + label, + description, + addon, + icon, + indicator = , + indicatorPlacement = 'end', + ...rest + } = props + + const hasContent = label || description || icon + const ContentWrapper = indicator ? RadioCard.ItemContent : React.Fragment + + return ( + + + + {indicatorPlacement === 'start' && indicator} + {hasContent && ( + + {icon} + {label && {label}} + {description && ( + + {description} + + )} + {indicatorPlacement === 'inside' && indicator} + + )} + {indicatorPlacement === 'end' && indicator} + + {addon && {addon}} + + ) + }, +) + +export const RadioCardRoot = RadioCard.Root +export const RadioCardLabel = RadioCard.Label +export const RadioCardItemIndicator = RadioCard.ItemIndicator diff --git a/src/components/ui/radio.jsx b/src/components/ui/radio.jsx new file mode 100644 index 0000000..8058902 --- /dev/null +++ b/src/components/ui/radio.jsx @@ -0,0 +1,17 @@ +import { RadioGroup as ChakraRadioGroup } from '@chakra-ui/react' +import * as React from 'react' + +export const Radio = React.forwardRef(function Radio(props, ref) { + const { children, inputProps, rootRef, ...rest } = props + return ( + + + + {children && ( + {children} + )} + + ) +}) + +export const RadioGroup = ChakraRadioGroup.Root diff --git a/src/components/ui/rating.jsx b/src/components/ui/rating.jsx new file mode 100644 index 0000000..399c7d0 --- /dev/null +++ b/src/components/ui/rating.jsx @@ -0,0 +1,19 @@ +import { RatingGroup } from '@chakra-ui/react' +import * as React from 'react' + +export const Rating = React.forwardRef(function Rating(props, ref) { + const { icon, count = 5, label, ...rest } = props + return ( + + {label && {label}} + + + {Array.from({ length: count }).map((_, index) => ( + + + + ))} + + + ) +}) diff --git a/src/components/ui/segmented-control.jsx b/src/components/ui/segmented-control.jsx new file mode 100644 index 0000000..00ce604 --- /dev/null +++ b/src/components/ui/segmented-control.jsx @@ -0,0 +1,36 @@ +'use client' + +import { For, SegmentGroup } from '@chakra-ui/react' +import * as React from 'react' + +function normalize(items) { + return items.map((item) => { + if (typeof item === 'string') return { value: item, label: item } + return item + }) +} + +export const SegmentedControl = React.forwardRef( + function SegmentedControl(props, ref) { + const { items, ...rest } = props + const data = React.useMemo(() => normalize(items), [items]) + + return ( + + + + {(item) => ( + + {item.label} + + + )} + + + ) + }, +) diff --git a/src/components/ui/select.jsx b/src/components/ui/select.jsx new file mode 100644 index 0000000..ed02fe2 --- /dev/null +++ b/src/components/ui/select.jsx @@ -0,0 +1,113 @@ +'use client' + +import { Select as ChakraSelect, Portal } from '@chakra-ui/react' +import { CloseButton } from './close-button' +import * as React from 'react' + +export const SelectTrigger = React.forwardRef( + function SelectTrigger(props, ref) { + const { children, clearable, ...rest } = props + return ( + + {children} + + {clearable && } + + + + ) + }, +) + +const SelectClearTrigger = React.forwardRef( + function SelectClearTrigger(props, ref) { + return ( + + + + ) + }, +) + +export const SelectContent = React.forwardRef( + function SelectContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + }, +) + +export const SelectItem = React.forwardRef(function SelectItem(props, ref) { + const { item, children, ...rest } = props + return ( + + {children} + + + ) +}) + +export const SelectValueText = React.forwardRef( + function SelectValueText(props, ref) { + const { children, ...rest } = props + return ( + + + {(select) => { + const items = select.selectedItems + if (items.length === 0) return props.placeholder + if (children) return children(items) + if (items.length === 1) + return select.collection.stringifyItem(items[0]) + return `${items.length} selected` + }} + + + ) + }, +) + +export const SelectRoot = React.forwardRef(function SelectRoot(props, ref) { + return ( + + {props.asChild ? ( + props.children + ) : ( + <> + + {props.children} + + )} + + ) +}) + +export const SelectItemGroup = React.forwardRef( + function SelectItemGroup(props, ref) { + const { children, label, ...rest } = props + return ( + + {label} + {children} + + ) + }, +) + +export const SelectLabel = ChakraSelect.Label +export const SelectItemText = ChakraSelect.ItemText diff --git a/src/components/ui/skeleton.jsx b/src/components/ui/skeleton.jsx new file mode 100644 index 0000000..8195fa1 --- /dev/null +++ b/src/components/ui/skeleton.jsx @@ -0,0 +1,32 @@ +import { Skeleton as ChakraSkeleton, Circle, Stack } from '@chakra-ui/react' +import * as React from 'react' + +export const SkeletonCircle = React.forwardRef( + function SkeletonCircle(props, ref) { + const { size, ...rest } = props + return ( + + + + ) + }, +) + +export const SkeletonText = React.forwardRef(function SkeletonText(props, ref) { + const { noOfLines = 3, gap, ...rest } = props + return ( + + {Array.from({ length: noOfLines }).map((_, index) => ( + + ))} + + ) +}) + +export const Skeleton = ChakraSkeleton diff --git a/src/components/ui/slider.jsx b/src/components/ui/slider.jsx new file mode 100644 index 0000000..35e3659 --- /dev/null +++ b/src/components/ui/slider.jsx @@ -0,0 +1,107 @@ +function _nullishCoalesce(lhs, rhsFn) { + if (lhs != null) { + return lhs + } else { + return rhsFn() + } +} +function _optionalChain(ops) { + let lastAccessLHS = undefined + let value = ops[0] + let i = 1 + while (i < ops.length) { + const op = ops[i] + const fn = ops[i + 1] + i += 2 + if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { + return undefined + } + if (op === 'access' || op === 'optionalAccess') { + lastAccessLHS = value + value = fn(value) + } else if (op === 'call' || op === 'optionalCall') { + value = fn((...args) => value.call(lastAccessLHS, ...args)) + lastAccessLHS = undefined + } + } + return value +} +import { Slider as ChakraSlider, For, HStack } from '@chakra-ui/react' +import * as React from 'react' + +export const Slider = React.forwardRef(function Slider(props, ref) { + const { marks: marksProp, label, showValue, ...rest } = props + const value = _nullishCoalesce(props.defaultValue, () => props.value) + + const marks = _optionalChain([ + marksProp, + 'optionalAccess', + (_2) => _2.map, + 'call', + (_3) => + _3((mark) => { + if (typeof mark === 'number') return { value: mark, label: undefined } + return mark + }), + ]) + + const hasMarkLabel = !!_optionalChain([ + marks, + 'optionalAccess', + (_4) => _4.some, + 'call', + (_5) => _5((mark) => mark.label), + ]) + + return ( + + {label && !showValue && {label}} + {label && showValue && ( + + {label} + + + )} + + + + + + + + + ) +}) + +function SliderThumbs(props) { + const { value } = props + return ( + + {(_, index) => ( + + + + )} + + ) +} + +const SliderMarks = React.forwardRef(function SliderMarks(props, ref) { + const { marks } = props + if (!_optionalChain([marks, 'optionalAccess', (_6) => _6.length])) return null + + return ( + + {marks.map((mark, index) => { + const value = typeof mark === 'number' ? mark : mark.value + const label = typeof mark === 'number' ? undefined : mark.label + return ( + + + {label} + + ) + })} + + ) +}) diff --git a/src/components/ui/stat.jsx b/src/components/ui/stat.jsx new file mode 100644 index 0000000..076a4f5 --- /dev/null +++ b/src/components/ui/stat.jsx @@ -0,0 +1,49 @@ +import { Badge, Stat as ChakraStat, FormatNumber } from '@chakra-ui/react' +import { InfoTip } from './toggle-tip' +import * as React from 'react' + +export const StatLabel = React.forwardRef(function StatLabel(props, ref) { + const { info, children, ...rest } = props + return ( + + {children} + {info && {info}} + + ) +}) + +export const StatValueText = React.forwardRef( + function StatValueText(props, ref) { + const { value, formatOptions, children, ...rest } = props + return ( + + {children || + (value != null && )} + + ) + }, +) + +export const StatUpTrend = React.forwardRef(function StatUpTrend(props, ref) { + return ( + + + {props.children} + + ) +}) + +export const StatDownTrend = React.forwardRef( + function StatDownTrend(props, ref) { + return ( + + + {props.children} + + ) + }, +) + +export const StatRoot = ChakraStat.Root +export const StatHelpText = ChakraStat.HelpText +export const StatValueUnit = ChakraStat.ValueUnit diff --git a/src/components/ui/status.jsx b/src/components/ui/status.jsx new file mode 100644 index 0000000..32e2c5e --- /dev/null +++ b/src/components/ui/status.jsx @@ -0,0 +1,30 @@ +function _nullishCoalesce(lhs, rhsFn) { + if (lhs != null) { + return lhs + } else { + return rhsFn() + } +} +import { Status as ChakraStatus } from '@chakra-ui/react' +import * as React from 'react' + +const statusMap = { + success: 'green', + error: 'red', + warning: 'orange', + info: 'blue', +} + +export const Status = React.forwardRef(function Status(props, ref) { + const { children, value = 'info', ...rest } = props + const colorPalette = _nullishCoalesce( + rest.colorPalette, + () => statusMap[value], + ) + return ( + + + {children} + + ) +}) diff --git a/src/components/ui/stepper-input.jsx b/src/components/ui/stepper-input.jsx new file mode 100644 index 0000000..2f9fb8a --- /dev/null +++ b/src/components/ui/stepper-input.jsx @@ -0,0 +1,41 @@ +import { HStack, IconButton, NumberInput } from '@chakra-ui/react' +import * as React from 'react' +import { LuMinus, LuPlus } from 'react-icons/lu' + +export const StepperInput = React.forwardRef(function StepperInput(props, ref) { + const { label, ...rest } = props + return ( + + {label && {label}} + + + + + + + ) +}) + +const DecrementTrigger = React.forwardRef( + function DecrementTrigger(props, ref) { + return ( + + + + + + ) + }, +) + +const IncrementTrigger = React.forwardRef( + function IncrementTrigger(props, ref) { + return ( + + + + + + ) + }, +) diff --git a/src/components/ui/steps.jsx b/src/components/ui/steps.jsx new file mode 100644 index 0000000..5394f1f --- /dev/null +++ b/src/components/ui/steps.jsx @@ -0,0 +1,62 @@ +import { Box, Steps as ChakraSteps } from '@chakra-ui/react' +import * as React from 'react' +import { LuCheck } from 'react-icons/lu' + +export const StepsItem = React.forwardRef(function StepsItem(props, ref) { + const { title, description, completedIcon, icon, ...rest } = props + return ( + + + + } + incomplete={icon || } + /> + + + + + + ) +}) + +const StepInfo = (props) => { + const { title, description } = props + + if (title && description) { + return ( + + {title} + {description} + + ) + } + + return ( + <> + {title && {title}} + {description && ( + {description} + )} + + ) +} + +export const StepsIndicator = React.forwardRef( + function StepsIndicator(props, ref) { + const { icon = , completedIcon } = props + return ( + + + + ) + }, +) + +export const StepsList = ChakraSteps.List +export const StepsRoot = ChakraSteps.Root +export const StepsContent = ChakraSteps.Content +export const StepsCompletedContent = ChakraSteps.CompletedContent + +export const StepsNextTrigger = ChakraSteps.NextTrigger +export const StepsPrevTrigger = ChakraSteps.PrevTrigger diff --git a/src/components/ui/switch.jsx b/src/components/ui/switch.jsx new file mode 100644 index 0000000..252b141 --- /dev/null +++ b/src/components/ui/switch.jsx @@ -0,0 +1,55 @@ +function _optionalChain(ops) { + let lastAccessLHS = undefined + let value = ops[0] + let i = 1 + while (i < ops.length) { + const op = ops[i] + const fn = ops[i + 1] + i += 2 + if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { + return undefined + } + if (op === 'access' || op === 'optionalAccess') { + lastAccessLHS = value + value = fn(value) + } else if (op === 'call' || op === 'optionalCall') { + value = fn((...args) => value.call(lastAccessLHS, ...args)) + lastAccessLHS = undefined + } + } + return value +} +import { Switch as ChakraSwitch } from '@chakra-ui/react' +import * as React from 'react' + +export const Switch = React.forwardRef(function Switch(props, ref) { + const { inputProps, children, rootRef, trackLabel, thumbLabel, ...rest } = + props + + return ( + + + + + {thumbLabel && ( + _.off, + ])} + > + {_optionalChain([thumbLabel, 'optionalAccess', (_2) => _2.on])} + + )} + + {trackLabel && ( + + {trackLabel.on} + + )} + + {children != null && {children}} + + ) +}) diff --git a/src/components/ui/tag.jsx b/src/components/ui/tag.jsx new file mode 100644 index 0000000..95de898 --- /dev/null +++ b/src/components/ui/tag.jsx @@ -0,0 +1,28 @@ +import { Tag as ChakraTag } from '@chakra-ui/react' +import * as React from 'react' + +export const Tag = React.forwardRef(function Tag(props, ref) { + const { + startElement, + endElement, + onClose, + closable = !!onClose, + children, + ...rest + } = props + + return ( + + {startElement && ( + {startElement} + )} + {children} + {endElement && {endElement}} + {closable && ( + + + + )} + + ) +}) diff --git a/src/components/ui/timeline.jsx b/src/components/ui/timeline.jsx new file mode 100644 index 0000000..33e57eb --- /dev/null +++ b/src/components/ui/timeline.jsx @@ -0,0 +1,20 @@ +import { Timeline as ChakraTimeline } from '@chakra-ui/react' +import * as React from 'react' + +export const TimelineConnector = React.forwardRef( + function TimelineConnector(props, ref) { + return ( + + + + + ) + }, +) + +export const TimelineRoot = ChakraTimeline.Root +export const TimelineContent = ChakraTimeline.Content +export const TimelineItem = ChakraTimeline.Item +export const TimelineIndicator = ChakraTimeline.Indicator +export const TimelineTitle = ChakraTimeline.Title +export const TimelineDescription = ChakraTimeline.Description diff --git a/src/components/ui/toggle-tip.jsx b/src/components/ui/toggle-tip.jsx new file mode 100644 index 0000000..b3ca45a --- /dev/null +++ b/src/components/ui/toggle-tip.jsx @@ -0,0 +1,58 @@ +import { Popover as ChakraPopover, IconButton, Portal } from '@chakra-ui/react' +import * as React from 'react' +import { HiOutlineInformationCircle } from 'react-icons/hi' + +export const ToggleTip = React.forwardRef(function ToggleTip(props, ref) { + const { + showArrow, + children, + portalled = true, + content, + portalRef, + ...rest + } = props + + return ( + + {children} + + + + {showArrow && ( + + + + )} + {content} + + + + + ) +}) + +export const InfoTip = React.forwardRef(function InfoTip(props, ref) { + const { children, ...rest } = props + return ( + + + + + + ) +}) diff --git a/src/components/ui/toggle.jsx b/src/components/ui/toggle.jsx new file mode 100644 index 0000000..d3926ac --- /dev/null +++ b/src/components/ui/toggle.jsx @@ -0,0 +1,44 @@ +'use client' + +import { + Button, + Toggle as ChakraToggle, + useToggleContext, +} from '@chakra-ui/react' +import * as React from 'react' + +const variantMap = { + solid: { on: 'solid', off: 'outline' }, + surface: { on: 'surface', off: 'outline' }, + subtle: { on: 'subtle', off: 'ghost' }, + ghost: { on: 'subtle', off: 'ghost' }, +} + +export const Toggle = React.forwardRef(function Toggle(props, ref) { + const { variant = 'subtle', size, children, ...rest } = props + const variantConfig = variantMap[variant] + + return ( + + + {children} + + + ) +}) + +const ToggleBaseButton = React.forwardRef( + function ToggleBaseButton(props, ref) { + const toggle = useToggleContext() + const { variant, ...rest } = props + return ( + + + + + + + {/* Second images column */} + + + + + + + + + + + +} + +export default Homepage \ No newline at end of file diff --git a/src/themes/MainTheme.js b/src/themes/MainTheme.js index 60e33f1..a912f61 100644 --- a/src/themes/MainTheme.js +++ b/src/themes/MainTheme.js @@ -17,7 +17,7 @@ const buttonRecipe = defineRecipe({ } } }) - + const textRecipe = defineRecipe({ base: { fontFamily: 'Lexend',