migrate to radix ui, make header draft
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import type { JSX, ReactNode } from "react"
|
||||
|
||||
import { Theme } from "@radix-ui/themes"
|
||||
import { Provider as ReduxProvider } from "react-redux"
|
||||
|
||||
import { store } from "@shared/store"
|
||||
|
||||
import { QueryClientProvider } from "./QueryClientProvider"
|
||||
|
||||
export const AppProviders = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<ReduxProvider store={store}>
|
||||
<QueryClientProvider>
|
||||
<Theme accentColor="violet" grayColor="slate" radius="medium">
|
||||
{children}
|
||||
</Theme>
|
||||
</QueryClientProvider>
|
||||
</ReduxProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import type { JSX, ReactNode } from "react"
|
||||
|
||||
import { QueryClientProvider as QueryClientProviderTanstack } from "@tanstack/react-query"
|
||||
import { JSX, Suspense } from "react"
|
||||
|
||||
import { queryClient } from "@shared/lib/query_client"
|
||||
|
||||
export const QueryClientProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
children: ReactNode
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<QueryClientProviderTanstack client={queryClient}>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { AppDispatch } from "@shared/store"
|
||||
|
||||
import { useDispatch } from "react-redux"
|
||||
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { RootState } from "@shared/store"
|
||||
|
||||
import { useSelector } from "react-redux"
|
||||
|
||||
export const useAppSelector = useSelector.withTypes<RootState>()
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { PayloadAction } from "@reduxjs/toolkit"
|
||||
|
||||
import { createSlice } from "@reduxjs/toolkit"
|
||||
|
||||
export interface AppState {
|
||||
currentScreenName: string
|
||||
}
|
||||
|
||||
const initialState: AppState = {
|
||||
currentScreenName: "",
|
||||
}
|
||||
|
||||
const appStateSlice = createSlice({
|
||||
name: "appState",
|
||||
initialState,
|
||||
reducers: {
|
||||
setCurrentScreenName(state, action: PayloadAction<string>) {
|
||||
state.currentScreenName = action.payload
|
||||
},
|
||||
resetAppState(state) {
|
||||
state.currentScreenName = initialState.currentScreenName
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { resetAppState, setCurrentScreenName } = appStateSlice.actions
|
||||
export const appStateReducer = appStateSlice.reducer
|
||||
@@ -0,0 +1,14 @@
|
||||
import { configureStore } from "@reduxjs/toolkit"
|
||||
|
||||
import { appStateReducer } from "./appStateStore"
|
||||
import { userReducer } from "./userStore"
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
appState: appStateReducer,
|
||||
user: userReducer,
|
||||
},
|
||||
})
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { PayloadAction } from "@reduxjs/toolkit"
|
||||
import type { components } from "@shared/api/__generated__/openapi.types"
|
||||
|
||||
import { createSlice } from "@reduxjs/toolkit"
|
||||
|
||||
export type UserEntity = components["schemas"]["UserRead"]
|
||||
|
||||
export interface UserState {
|
||||
user: UserEntity | null
|
||||
}
|
||||
|
||||
const initialState: UserState = {
|
||||
user: null,
|
||||
}
|
||||
|
||||
const userSlice = createSlice({
|
||||
name: "user",
|
||||
initialState,
|
||||
reducers: {
|
||||
setAuthData(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
user: UserEntity
|
||||
}>,
|
||||
) {
|
||||
state.user = action.payload.user
|
||||
},
|
||||
setUser(state, action: PayloadAction<UserEntity | null>) {
|
||||
state.user = action.payload
|
||||
},
|
||||
reset(state) {
|
||||
state.user = initialState.user
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { reset: resetUser, setAuthData, setUser } = userSlice.actions
|
||||
export const userReducer = userSlice.reducer
|
||||
@@ -1,37 +1,43 @@
|
||||
@mixin font-header-l {
|
||||
@mixin font-numeric {
|
||||
font-variant-numeric: lining-nums proportional-nums;
|
||||
}
|
||||
|
||||
@mixin font-body-16($weight) {
|
||||
font-weight: $weight;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-body-14($weight) {
|
||||
font-weight: $weight;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-display {
|
||||
@include font-numeric;
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
line-height: 42px;
|
||||
letter-spacing: -0.65px;
|
||||
}
|
||||
|
||||
@mixin font-header-l {
|
||||
@include font-numeric;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
letter-spacing: -0.41px;
|
||||
}
|
||||
|
||||
@mixin font-subheader-l {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-body-m {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-body-mm {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
@include font-body-16(500);
|
||||
}
|
||||
|
||||
@mixin font-body-mr {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
@include font-body-16(400);
|
||||
}
|
||||
|
||||
@mixin font-body-s {
|
||||
|
||||
@@ -30,4 +30,6 @@ $color-white: var(--color-white);
|
||||
$color-black: var(--color-black);
|
||||
|
||||
$header-height: var(--header-height);
|
||||
$text-primary: var(--text-primary);
|
||||
$text-secondary: var(--text-secondary);
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
@import "normalize.css";
|
||||
@import "@radix-ui/themes/styles.css";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-family: var(--font-roboto);
|
||||
font-family: var(--font-open-sans);
|
||||
font-variant-numeric: lining-nums proportional-nums;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -14,10 +15,10 @@
|
||||
|
||||
body {
|
||||
background-color: #f8f8f8;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: #121212;
|
||||
}
|
||||
color: var(--text-primary);
|
||||
// @media (prefers-color-scheme: dark) {
|
||||
// background-color: #121212;
|
||||
// }
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -54,10 +55,12 @@ body {
|
||||
--color-white: #ffffff;
|
||||
--color-black: #000000;
|
||||
|
||||
--text-primary: #111827;
|
||||
--text-secondary: #6b7280;
|
||||
|
||||
--header-height: 56px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.radix-themes {
|
||||
--default-font-family: var(--font-open-sans);
|
||||
}
|
||||
Vendored
+9
-2
@@ -1,3 +1,10 @@
|
||||
import type { AlertProps } from "react-bootstrap/Alert"
|
||||
import type { Callout } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface IAlertProps extends AlertProps {}
|
||||
export type AlertVariant = "info" | "success" | "warning" | "danger"
|
||||
|
||||
export interface IAlertProps
|
||||
extends Omit<ComponentProps<typeof Callout.Root>, "color"> {
|
||||
variant?: AlertVariant
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
.alert {
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: #e0f2fe;
|
||||
color: #0369a1;
|
||||
border: 1px solid #7dd3fc;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: #dcfce7;
|
||||
color: #166534;
|
||||
border: 1px solid #86efac;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: #fef9c3;
|
||||
color: #854d0e;
|
||||
border: 1px solid #fde047;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: #fee2e2;
|
||||
color: #991b1b;
|
||||
border: 1px solid #fca5a5;
|
||||
}
|
||||
@@ -3,11 +3,30 @@
|
||||
import type { IAlertProps } from "../model/Alert.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Callout } from "@radix-ui/themes"
|
||||
import { AlertCircle, CheckCircle, Info, TriangleAlert } from "lucide-react"
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapAlert from "react-bootstrap/Alert"
|
||||
|
||||
const variantMap = {
|
||||
info: { color: "blue", Icon: Info },
|
||||
success: { color: "green", Icon: CheckCircle },
|
||||
warning: { color: "yellow", Icon: TriangleAlert },
|
||||
danger: { color: "red", Icon: AlertCircle },
|
||||
} as const
|
||||
|
||||
export const Alert = forwardRef<HTMLDivElement, IAlertProps>(
|
||||
(props, ref): JSX.Element => <BootstrapAlert ref={ref} {...props} />,
|
||||
({ variant = "info", children, ...props }, ref): JSX.Element => {
|
||||
const { color, Icon } = variantMap[variant]
|
||||
|
||||
return (
|
||||
<Callout.Root ref={ref} color={color} role="alert" {...props}>
|
||||
<Callout.Icon>
|
||||
<Icon size={16} />
|
||||
</Callout.Icon>
|
||||
<Callout.Text>{children}</Callout.Text>
|
||||
</Callout.Root>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Alert.displayName = "Alert"
|
||||
|
||||
Vendored
+15
-2
@@ -1,3 +1,16 @@
|
||||
import type { BadgeProps } from "react-bootstrap/Badge"
|
||||
import type { Badge } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface IBadgeProps extends BadgeProps {}
|
||||
export type BadgeVariant =
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "success"
|
||||
| "danger"
|
||||
| "warning"
|
||||
| "info"
|
||||
|
||||
export interface IBadgeProps
|
||||
extends Omit<ComponentProps<typeof Badge>, "color"> {
|
||||
variant?: BadgeVariant
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: var(--purple-400);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background-color: var(--green-600);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--color-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--color-danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--color-warning);
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
@@ -3,11 +3,24 @@
|
||||
import type { IBadgeProps } from "../model/Badge.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Badge as RadixBadge } from "@radix-ui/themes"
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapBadge from "react-bootstrap/Badge"
|
||||
|
||||
const variantMap = {
|
||||
primary: "violet",
|
||||
secondary: "gray",
|
||||
success: "green",
|
||||
danger: "red",
|
||||
warning: "yellow",
|
||||
info: "blue",
|
||||
} as const
|
||||
|
||||
export const Badge = forwardRef<HTMLSpanElement, IBadgeProps>(
|
||||
(props, ref): JSX.Element => <BootstrapBadge ref={ref} {...props} />,
|
||||
({ variant = "primary", children, ...props }, ref): JSX.Element => (
|
||||
<RadixBadge ref={ref} color={variantMap[variant]} {...props}>
|
||||
{children}
|
||||
</RadixBadge>
|
||||
),
|
||||
)
|
||||
|
||||
Badge.displayName = "Badge"
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export * from "./model/Button.d"
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./ui/Button"
|
||||
+19
-2
@@ -1,3 +1,20 @@
|
||||
import type { ButtonProps } from "react-bootstrap/Button"
|
||||
import type { Button as RadixButton } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface IButtonProps extends ButtonProps {}
|
||||
export type ButtonVariant =
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "outline"
|
||||
| "ghost"
|
||||
| "danger"
|
||||
| "icon"
|
||||
export type ButtonSize = "sm" | "md" | "lg"
|
||||
|
||||
export interface IButtonProps extends Omit<
|
||||
ComponentProps<typeof RadixButton>,
|
||||
"size" | "variant"
|
||||
> {
|
||||
variant?: ButtonVariant
|
||||
size?: ButtonSize
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -4,10 +4,61 @@ import type { IButtonProps } from "../model/Button.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapButton from "react-bootstrap/Button"
|
||||
|
||||
import {
|
||||
Button as RadixButton,
|
||||
IconButton as RadixIconButton,
|
||||
} from "@radix-ui/themes"
|
||||
|
||||
const sizeMap = {
|
||||
sm: "1",
|
||||
md: "2",
|
||||
lg: "3",
|
||||
} as const
|
||||
|
||||
const variantMap = {
|
||||
primary: { variant: "solid", color: "indigo" },
|
||||
secondary: { variant: "soft", color: "grass" },
|
||||
outline: { variant: "outline", color: "indigo" },
|
||||
ghost: { variant: "ghost", color: "gray" },
|
||||
danger: { variant: "solid", color: "ruby" },
|
||||
icon: { variant: "ghost", color: "gray" },
|
||||
} as const
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, IButtonProps>(
|
||||
(props, ref): JSX.Element => <BootstrapButton ref={ref} {...props} />,
|
||||
(
|
||||
{ variant = "primary", size = "md", children, ...props },
|
||||
ref,
|
||||
): JSX.Element => {
|
||||
const visual = variantMap[variant]
|
||||
const radixSize = sizeMap[size]
|
||||
|
||||
if (variant === "icon") {
|
||||
return (
|
||||
<RadixIconButton
|
||||
ref={ref}
|
||||
size={radixSize}
|
||||
variant={visual.variant}
|
||||
color={visual.color}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</RadixIconButton>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixButton
|
||||
ref={ref}
|
||||
size={radixSize}
|
||||
variant={visual.variant}
|
||||
color={visual.color}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</RadixButton>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Button.displayName = "Button"
|
||||
|
||||
Vendored
+5
-2
@@ -1,3 +1,6 @@
|
||||
import type { CardProps } from "react-bootstrap/Card"
|
||||
import type { Card } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface ICardProps extends CardProps {}
|
||||
export interface ICardProps extends ComponentProps<typeof Card> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
padding: 1.5rem;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,15 @@
|
||||
import type { ICardProps } from "../model/Card.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Card as RadixCard } from "@radix-ui/themes"
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapCard from "react-bootstrap/Card"
|
||||
|
||||
export const Card = forwardRef<HTMLDivElement, ICardProps>(
|
||||
(props, ref): JSX.Element => <BootstrapCard ref={ref} {...props} />,
|
||||
({ children, ...props }, ref): JSX.Element => (
|
||||
<RadixCard ref={ref} {...props}>
|
||||
{children}
|
||||
</RadixCard>
|
||||
),
|
||||
)
|
||||
|
||||
Card.displayName = "Card"
|
||||
|
||||
+5
-2
@@ -1,3 +1,6 @@
|
||||
import type { FormCheckProps } from "react-bootstrap/FormCheck"
|
||||
import type { Checkbox } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface ICheckboxProps extends Omit<FormCheckProps, "type"> {}
|
||||
export interface ICheckboxProps extends ComponentProps<typeof Checkbox> {
|
||||
label?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
background-color: white;
|
||||
border: 2px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--purple-400);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--purple-400);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&[data-state="checked"] {
|
||||
background-color: var(--purple-400);
|
||||
border-color: var(--purple-400);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -3,13 +3,23 @@
|
||||
import type { ICheckboxProps } from "../model/Checkbox.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapFormCheck from "react-bootstrap/FormCheck"
|
||||
import { Checkbox as RadixCheckbox, Flex, Text } from "@radix-ui/themes"
|
||||
import { forwardRef, useId } from "react"
|
||||
|
||||
export const Checkbox = forwardRef<HTMLInputElement, ICheckboxProps>(
|
||||
(props, ref): JSX.Element => (
|
||||
<BootstrapFormCheck ref={ref} type="checkbox" {...props} />
|
||||
),
|
||||
export const Checkbox = forwardRef<HTMLButtonElement, ICheckboxProps>(
|
||||
({ label, id: propId, ...props }, ref): JSX.Element => {
|
||||
const generatedId = useId()
|
||||
const id = propId ?? generatedId
|
||||
|
||||
return (
|
||||
<Text as="label" size="2">
|
||||
<Flex gap="2" align="center">
|
||||
<RadixCheckbox ref={ref} id={id} {...props} />
|
||||
{label}
|
||||
</Flex>
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Checkbox.displayName = "Checkbox"
|
||||
|
||||
Vendored
+4
-2
@@ -1,3 +1,5 @@
|
||||
import type { FormProps } from "react-bootstrap/Form"
|
||||
import type { FormHTMLAttributes, ReactNode } from "react"
|
||||
|
||||
export interface IFormProps extends FormProps {}
|
||||
export interface IFormProps extends FormHTMLAttributes<HTMLFormElement> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
@@ -4,10 +4,17 @@ import type { IFormProps } from "../model/Form.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapForm from "react-bootstrap/Form"
|
||||
|
||||
import classNames from "classnames"
|
||||
|
||||
import styles from "./Form.module.scss"
|
||||
|
||||
export const Form = forwardRef<HTMLFormElement, IFormProps>(
|
||||
(props, ref): JSX.Element => <BootstrapForm ref={ref} {...props} />,
|
||||
({ className, children, ...props }, ref): JSX.Element => (
|
||||
<form ref={ref} className={classNames(styles.form, className)} {...props}>
|
||||
{children}
|
||||
</form>
|
||||
),
|
||||
)
|
||||
|
||||
Form.displayName = "Form"
|
||||
|
||||
Vendored
+7
-2
@@ -1,3 +1,8 @@
|
||||
import type { ModalProps } from "react-bootstrap/Modal"
|
||||
import type { Dialog } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface IModalProps extends ModalProps {}
|
||||
export interface IModalProps extends ComponentProps<typeof Dialog.Root> {
|
||||
title?: ReactNode
|
||||
description?: ReactNode
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 100;
|
||||
animation: overlayShow 0.15s ease;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 32rem;
|
||||
width: calc(100% - 2rem);
|
||||
max-height: 85vh;
|
||||
padding: 1.5rem;
|
||||
background-color: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
z-index: 101;
|
||||
animation: contentShow 0.15s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
background: transparent;
|
||||
border-radius: 0.25rem;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--purple-400);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes overlayShow {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes contentShow {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -48%) scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,26 @@
|
||||
import type { IModalProps } from "../model/Modal.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Dialog } from "@radix-ui/themes"
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapModal from "react-bootstrap/Modal"
|
||||
|
||||
export const Modal = forwardRef<HTMLDivElement, IModalProps>(
|
||||
(props, ref): JSX.Element => <BootstrapModal ref={ref} {...props} />,
|
||||
({ title, description, children, ...props }, ref): JSX.Element => (
|
||||
<Dialog.Root {...props}>
|
||||
<Dialog.Content ref={ref}>
|
||||
{title && <Dialog.Title>{title}</Dialog.Title>}
|
||||
{description && (
|
||||
<Dialog.Description size="2" mb="4">
|
||||
{description}
|
||||
</Dialog.Description>
|
||||
)}
|
||||
{children}
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
),
|
||||
)
|
||||
|
||||
Modal.displayName = "Modal"
|
||||
|
||||
export const ModalTrigger = Dialog.Trigger
|
||||
ModalTrigger.displayName = "ModalTrigger"
|
||||
|
||||
+8
-2
@@ -1,3 +1,9 @@
|
||||
import type { PaginationProps } from "react-bootstrap/Pagination"
|
||||
import type { Flex } from "@radix-ui/themes"
|
||||
import type { ComponentProps } from "react"
|
||||
|
||||
export interface IPaginationProps extends PaginationProps {}
|
||||
export interface IPaginationProps extends ComponentProps<typeof Flex> {
|
||||
currentPage: number
|
||||
totalPages: number
|
||||
onPageChange: (page: number) => void
|
||||
showFirstLast?: boolean
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.5rem;
|
||||
background: transparent;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--purple-400);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.page {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--purple-400);
|
||||
color: white;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--purple-500);
|
||||
}
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
@@ -3,11 +3,113 @@
|
||||
import type { IPaginationProps } from "../model/Pagination.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapPagination from "react-bootstrap/Pagination"
|
||||
import { Flex, IconButton, Text } from "@radix-ui/themes"
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
} from "lucide-react"
|
||||
import { forwardRef, useMemo } from "react"
|
||||
|
||||
export const Pagination = forwardRef<HTMLUListElement, IPaginationProps>(
|
||||
(props, ref): JSX.Element => <BootstrapPagination ref={ref} {...props} />,
|
||||
export const Pagination = forwardRef<HTMLDivElement, IPaginationProps>(
|
||||
(
|
||||
{ currentPage, totalPages, onPageChange, showFirstLast = true, ...props },
|
||||
ref,
|
||||
): JSX.Element => {
|
||||
const pages = useMemo(() => {
|
||||
const items: (number | "ellipsis")[] = []
|
||||
const maxVisible = 5
|
||||
|
||||
if (totalPages <= maxVisible) {
|
||||
for (let i = 1; i <= totalPages; i++) items.push(i)
|
||||
} else {
|
||||
items.push(1)
|
||||
if (currentPage > 3) items.push("ellipsis")
|
||||
|
||||
const start = Math.max(2, currentPage - 1)
|
||||
const end = Math.min(totalPages - 1, currentPage + 1)
|
||||
|
||||
for (let i = start; i <= end; i++) items.push(i)
|
||||
|
||||
if (currentPage < totalPages - 2) items.push("ellipsis")
|
||||
items.push(totalPages)
|
||||
}
|
||||
|
||||
return items
|
||||
}, [currentPage, totalPages])
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={ref}
|
||||
role="navigation"
|
||||
align="center"
|
||||
gap="1"
|
||||
aria-label="Pagination"
|
||||
{...props}
|
||||
>
|
||||
{showFirstLast && (
|
||||
<IconButton
|
||||
variant="soft"
|
||||
size="1"
|
||||
onClick={() => onPageChange(1)}
|
||||
disabled={currentPage === 1}
|
||||
aria-label="First page"
|
||||
>
|
||||
<ChevronsLeft size={16} />
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton
|
||||
variant="soft"
|
||||
size="1"
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
aria-label="Previous page"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</IconButton>
|
||||
|
||||
{pages.map((page, index) =>
|
||||
page === "ellipsis" ? (
|
||||
<Text key={`ellipsis-${index}`} size="2" color="gray" mx="1">
|
||||
...
|
||||
</Text>
|
||||
) : (
|
||||
<IconButton
|
||||
key={page}
|
||||
variant={page === currentPage ? "solid" : "soft"}
|
||||
size="1"
|
||||
onClick={() => onPageChange(page)}
|
||||
aria-current={page === currentPage ? "page" : undefined}
|
||||
>
|
||||
{page}
|
||||
</IconButton>
|
||||
),
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
variant="soft"
|
||||
size="1"
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
aria-label="Next page"
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
</IconButton>
|
||||
{showFirstLast && (
|
||||
<IconButton
|
||||
variant="soft"
|
||||
size="1"
|
||||
onClick={() => onPageChange(totalPages)}
|
||||
disabled={currentPage === totalPages}
|
||||
aria-label="Last page"
|
||||
>
|
||||
<ChevronsRight size={16} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Pagination.displayName = "Pagination"
|
||||
|
||||
Vendored
+8
-2
@@ -1,3 +1,9 @@
|
||||
import type { FormCheckProps } from "react-bootstrap/FormCheck"
|
||||
import type { RadioGroup } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface IRadioProps extends Omit<FormCheckProps, "type"> {}
|
||||
export interface IRadioGroupProps
|
||||
extends ComponentProps<typeof RadioGroup.Root> {}
|
||||
|
||||
export interface IRadioProps extends ComponentProps<typeof RadioGroup.Item> {
|
||||
label?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.radio {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
background-color: white;
|
||||
border: 2px solid #d1d5db;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--purple-400);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--purple-400);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&[data-state="checked"] {
|
||||
border-color: var(--purple-400);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 0.625rem;
|
||||
height: 0.625rem;
|
||||
background-color: var(--purple-400);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -1,15 +1,35 @@
|
||||
"use client"
|
||||
|
||||
import type { IRadioProps } from "../model/Radio.d"
|
||||
import type { IRadioGroupProps, IRadioProps } from "../model/Radio.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapFormCheck from "react-bootstrap/FormCheck"
|
||||
import { Flex, RadioGroup, Text } from "@radix-ui/themes"
|
||||
import { forwardRef, useId } from "react"
|
||||
|
||||
export const Radio = forwardRef<HTMLInputElement, IRadioProps>(
|
||||
(props, ref): JSX.Element => (
|
||||
<BootstrapFormCheck ref={ref} type="radio" {...props} />
|
||||
export const RadioGroupRoot = forwardRef<HTMLDivElement, IRadioGroupProps>(
|
||||
({ ...props }, ref): JSX.Element => (
|
||||
<RadioGroup.Root ref={ref} {...props} />
|
||||
),
|
||||
)
|
||||
|
||||
RadioGroupRoot.displayName = "RadioGroup"
|
||||
|
||||
export const Radio = forwardRef<HTMLButtonElement, IRadioProps>(
|
||||
({ label, id: propId, ...props }, ref): JSX.Element => {
|
||||
const generatedId = useId()
|
||||
const id = propId ?? generatedId
|
||||
|
||||
return (
|
||||
<Text as="label" size="2">
|
||||
<Flex gap="2" align="center">
|
||||
<RadioGroup.Item ref={ref} id={id} {...props} />
|
||||
{label}
|
||||
</Flex>
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Radio.displayName = "Radio"
|
||||
|
||||
export { RadioGroupRoot as RadioGroup }
|
||||
|
||||
+10
-2
@@ -1,3 +1,11 @@
|
||||
import type { FormSelectProps } from "react-bootstrap/FormSelect"
|
||||
import type { Select } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface ISelectProps extends FormSelectProps {}
|
||||
export interface ISelectProps extends ComponentProps<typeof Select.Root> {
|
||||
placeholder?: string
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export interface ISelectItemProps extends ComponentProps<typeof Select.Item> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
.trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
min-width: 10rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background-color: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--purple-400);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--purple-400);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&[data-placeholder] {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
padding-right: 2rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
|
||||
&:hover,
|
||||
&[data-highlighted] {
|
||||
background-color: var(--purple-50);
|
||||
color: var(--purple-600);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.itemIndicator {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--purple-400);
|
||||
}
|
||||
|
||||
.scrollButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
color: #6b7280;
|
||||
cursor: default;
|
||||
}
|
||||
@@ -1,13 +1,34 @@
|
||||
"use client"
|
||||
|
||||
import type { ISelectProps } from "../model/Select.d"
|
||||
import type { ISelectItemProps, ISelectProps } from "../model/Select.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Select as RadixSelect } from "@radix-ui/themes"
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapFormSelect from "react-bootstrap/FormSelect"
|
||||
|
||||
export const Select = forwardRef<HTMLSelectElement, ISelectProps>(
|
||||
(props, ref): JSX.Element => <BootstrapFormSelect ref={ref} {...props} />,
|
||||
export const Select = forwardRef<HTMLButtonElement, ISelectProps>(
|
||||
({ placeholder, children, ...props }, ref): JSX.Element => (
|
||||
<RadixSelect.Root {...props}>
|
||||
<RadixSelect.Trigger ref={ref} placeholder={placeholder} />
|
||||
<RadixSelect.Content position="popper">
|
||||
{children}
|
||||
</RadixSelect.Content>
|
||||
</RadixSelect.Root>
|
||||
),
|
||||
)
|
||||
|
||||
Select.displayName = "Select"
|
||||
|
||||
export const SelectItem = forwardRef<HTMLDivElement, ISelectItemProps>(
|
||||
({ children, ...props }, ref): JSX.Element => (
|
||||
<RadixSelect.Item ref={ref} {...props}>
|
||||
{children}
|
||||
</RadixSelect.Item>
|
||||
),
|
||||
)
|
||||
|
||||
SelectItem.displayName = "SelectItem"
|
||||
|
||||
export const SelectGroup = RadixSelect.Group
|
||||
export const SelectLabel = RadixSelect.Label
|
||||
export const SelectSeparator = RadixSelect.Separator
|
||||
|
||||
Vendored
+5
-2
@@ -1,3 +1,6 @@
|
||||
import type { TableProps } from "react-bootstrap/Table"
|
||||
import type { Table } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface ITableProps extends TableProps {}
|
||||
export interface ITableProps extends ComponentProps<typeof Table.Root> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875rem;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
thead th {
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
tbody td {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
.striped {
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
.hover {
|
||||
tbody tr:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,22 @@
|
||||
import type { ITableProps } from "../model/Table.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Table as RadixTable } from "@radix-ui/themes"
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapTable from "react-bootstrap/Table"
|
||||
|
||||
export const Table = forwardRef<HTMLTableElement, ITableProps>(
|
||||
(props, ref): JSX.Element => <BootstrapTable ref={ref} {...props} />,
|
||||
({ children, ...props }, ref): JSX.Element => (
|
||||
<RadixTable.Root ref={ref} {...props}>
|
||||
{children}
|
||||
</RadixTable.Root>
|
||||
),
|
||||
)
|
||||
|
||||
Table.displayName = "Table"
|
||||
|
||||
export const TableHeader = RadixTable.Header
|
||||
export const TableBody = RadixTable.Body
|
||||
export const TableRow = RadixTable.Row
|
||||
export const TableCell = RadixTable.Cell
|
||||
export const TableColumnHeaderCell = RadixTable.ColumnHeaderCell
|
||||
export const TableRowHeaderCell = RadixTable.RowHeaderCell
|
||||
|
||||
Vendored
+19
-2
@@ -1,3 +1,20 @@
|
||||
import type { TabsProps } from "react-bootstrap/Tabs"
|
||||
import type { Tabs } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface ITabsProps extends TabsProps {}
|
||||
export interface ITabsProps extends ComponentProps<typeof Tabs.Root> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export interface ITabsListProps extends ComponentProps<typeof Tabs.List> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export interface ITabsTriggerProps
|
||||
extends ComponentProps<typeof Tabs.Trigger> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export interface ITabsContentProps
|
||||
extends ComponentProps<typeof Tabs.Content> {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
padding: 0.75rem 1rem;
|
||||
background: transparent;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--purple-400);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--purple-400);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&[data-state="active"] {
|
||||
color: var(--purple-400);
|
||||
border-bottom-color: var(--purple-400);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 1rem 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,40 @@
|
||||
"use client"
|
||||
|
||||
import type { ITabsProps } from "../model/Tabs.d"
|
||||
import type { ForwardRefExoticComponent, JSX, RefAttributes } from "react"
|
||||
import type {
|
||||
ITabsContentProps,
|
||||
ITabsListProps,
|
||||
ITabsProps,
|
||||
ITabsTriggerProps,
|
||||
} from "../model/Tabs.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Tabs as RadixTabs } from "@radix-ui/themes"
|
||||
import { forwardRef } from "react"
|
||||
import BootstrapTabs from "react-bootstrap/Tabs"
|
||||
|
||||
const BootstrapTabsWithRef =
|
||||
BootstrapTabs as unknown as ForwardRefExoticComponent<
|
||||
ITabsProps & RefAttributes<HTMLDivElement>
|
||||
>
|
||||
|
||||
export const Tabs = forwardRef<HTMLDivElement, ITabsProps>(
|
||||
(props, ref): JSX.Element => <BootstrapTabsWithRef ref={ref} {...props} />,
|
||||
({ ...props }, ref): JSX.Element => <RadixTabs.Root ref={ref} {...props} />,
|
||||
)
|
||||
|
||||
Tabs.displayName = "Tabs"
|
||||
|
||||
export const TabsList = forwardRef<HTMLDivElement, ITabsListProps>(
|
||||
({ ...props }, ref): JSX.Element => <RadixTabs.List ref={ref} {...props} />,
|
||||
)
|
||||
|
||||
TabsList.displayName = "TabsList"
|
||||
|
||||
export const TabsTrigger = forwardRef<HTMLButtonElement, ITabsTriggerProps>(
|
||||
({ ...props }, ref): JSX.Element => (
|
||||
<RadixTabs.Trigger ref={ref} {...props} />
|
||||
),
|
||||
)
|
||||
|
||||
TabsTrigger.displayName = "TabsTrigger"
|
||||
|
||||
export const TabsContent = forwardRef<HTMLDivElement, ITabsContentProps>(
|
||||
({ ...props }, ref): JSX.Element => (
|
||||
<RadixTabs.Content ref={ref} {...props} />
|
||||
),
|
||||
)
|
||||
|
||||
TabsContent.displayName = "TabsContent"
|
||||
|
||||
+7
-4
@@ -1,7 +1,10 @@
|
||||
import type { FormControlProps } from "react-bootstrap/FormControl"
|
||||
import type { TextField } from "@radix-ui/themes"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
export interface ITextFieldProps extends FormControlProps {
|
||||
export interface ITextFieldProps
|
||||
extends Omit<ComponentProps<typeof TextField.Root>, "color"> {
|
||||
id: string
|
||||
label?: string
|
||||
undertitle?: string
|
||||
label?: ReactNode
|
||||
undertitle?: ReactNode
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background-color: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--purple-400);
|
||||
box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.1);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #4b5563;
|
||||
color: #f3f4f6;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: #6b7280;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
border-color: var(--color-danger);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--color-danger);
|
||||
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.undertitle {
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
@@ -3,25 +3,32 @@
|
||||
import type { ITextFieldProps } from "../model/TextField.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import React, { forwardRef } from "react"
|
||||
import BootstrapForm from "react-bootstrap/Form"
|
||||
import { Text, TextField as RadixTextField } from "@radix-ui/themes"
|
||||
import { forwardRef } from "react"
|
||||
|
||||
export const TextField = forwardRef<HTMLInputElement, ITextFieldProps>(
|
||||
({ id, label, undertitle, ...props }, ref): JSX.Element => (
|
||||
<React.Fragment>
|
||||
{label && <BootstrapForm.Label htmlFor={id}>{label}</BootstrapForm.Label>}
|
||||
<BootstrapForm.Control
|
||||
({ id, label, undertitle, error, size = "2", ...props }, ref): JSX.Element => (
|
||||
<label htmlFor={id}>
|
||||
{label && (
|
||||
<Text as="div" size="2" mb="1" weight="medium">
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
<RadixTextField.Root
|
||||
id={id}
|
||||
ref={ref}
|
||||
size={size}
|
||||
color={error ? "red" : undefined}
|
||||
aria-describedby={undertitle ? `${id}-undertitle` : undefined}
|
||||
aria-invalid={error}
|
||||
{...props}
|
||||
aria-describedby={`${id}-undertitle`}
|
||||
/>
|
||||
{undertitle && (
|
||||
<BootstrapForm.Text id={`${id}-undertitle`} muted>
|
||||
<Text as="p" size="1" color="gray" mt="1" id={`${id}-undertitle`}>
|
||||
{undertitle}
|
||||
</BootstrapForm.Text>
|
||||
</Text>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</label>
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user