add front-urban
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { forwardRef } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import MuiAvatar from '@mui/material/Avatar'
|
||||
import { lighten, styled } from '@mui/material/styles'
|
||||
import type { AvatarProps } from '@mui/material/Avatar'
|
||||
|
||||
// Type Imports
|
||||
import type { ThemeColor } from '@core/types'
|
||||
|
||||
export type CustomAvatarProps = AvatarProps & {
|
||||
color?: ThemeColor
|
||||
skin?: 'filled' | 'light' | 'light-static'
|
||||
size?: number
|
||||
}
|
||||
|
||||
const Avatar = styled(MuiAvatar)<CustomAvatarProps>(({ skin, color, size, theme }) => {
|
||||
return {
|
||||
...(color &&
|
||||
skin === 'light' && {
|
||||
backgroundColor: `var(--mui-palette-${color}-lightOpacity)`,
|
||||
color: `var(--mui-palette-${color}-main)`
|
||||
}),
|
||||
...(color &&
|
||||
skin === 'light-static' && {
|
||||
backgroundColor: lighten(theme.palette[color as ThemeColor].main, 0.84),
|
||||
color: `var(--mui-palette-${color}-main)`
|
||||
}),
|
||||
...(color &&
|
||||
skin === 'filled' && {
|
||||
backgroundColor: `var(--mui-palette-${color}-main)`,
|
||||
color: `var(--mui-palette-${color}-contrastText)`
|
||||
}),
|
||||
...(size && {
|
||||
height: size,
|
||||
width: size
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const CustomAvatar = forwardRef<HTMLDivElement, CustomAvatarProps>((props: CustomAvatarProps, ref) => {
|
||||
// Props
|
||||
const { color, skin = 'filled', ...rest } = props
|
||||
|
||||
return <Avatar color={color} skin={skin} ref={ref} {...rest} />
|
||||
})
|
||||
|
||||
export default CustomAvatar
|
||||
@@ -0,0 +1,75 @@
|
||||
'use client'
|
||||
|
||||
import MuiButton from '@mui/material/Button'
|
||||
import { styled } from '@mui/material/styles'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
const CustomIconButton = styled(MuiButton)(({ color, size, theme, variant }) => {
|
||||
return {
|
||||
minInlineSize: 0,
|
||||
...(size === 'small'
|
||||
? {
|
||||
fontSize: '20px',
|
||||
padding: theme.spacing(variant === 'outlined' ? 1 : 1.25),
|
||||
'& i, & svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
}
|
||||
: {
|
||||
...(size === 'large'
|
||||
? {
|
||||
fontSize: '24px',
|
||||
padding: theme.spacing(variant === 'outlined' ? 2 : 2.25),
|
||||
'& i, & svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
}
|
||||
: {
|
||||
fontSize: '22px',
|
||||
padding: theme.spacing(variant === 'outlined' ? 1.5 : 1.75),
|
||||
'& i, & svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
})
|
||||
}),
|
||||
...(!color && {
|
||||
color: 'var(--mui-palette-action-active)',
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'rgb(var(--mui-palette-text-primaryChannel) / 0.08)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': {
|
||||
backgroundColor: 'rgb(var(--mui-palette-text-primaryChannel) / 0.08)'
|
||||
}
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-action-active)'
|
||||
},
|
||||
...(variant === 'outlined' && {
|
||||
border: 'none !important',
|
||||
...(size === 'small'
|
||||
? {
|
||||
padding: theme.spacing(1.5)
|
||||
}
|
||||
: {
|
||||
...(size === 'large'
|
||||
? {
|
||||
padding: theme.spacing(2.25)
|
||||
}
|
||||
: {
|
||||
padding: theme.spacing(1.75)
|
||||
})
|
||||
})
|
||||
}),
|
||||
...(variant === 'contained' && {
|
||||
boxShadow: 'none !important',
|
||||
backgroundColor: 'transparent'
|
||||
})
|
||||
})
|
||||
}
|
||||
}) as typeof MuiButton
|
||||
|
||||
export default CustomIconButton
|
||||
@@ -0,0 +1,135 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useRef, useState } from 'react'
|
||||
import type { ReactElement, ReactNode, SyntheticEvent } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
|
||||
// MUI Imports
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import Box from '@mui/material/Box'
|
||||
import Popper from '@mui/material/Popper'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import MenuList from '@mui/material/MenuList'
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener'
|
||||
import Fade from '@mui/material/Fade'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Divider from '@mui/material/Divider'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Type Imports
|
||||
import type { OptionsMenuType, OptionType, OptionMenuItemType } from './types'
|
||||
|
||||
const IconButtonWrapper = (props: Pick<OptionsMenuType, 'tooltipProps'> & { children: ReactElement }) => {
|
||||
// Props
|
||||
const { tooltipProps, children } = props
|
||||
|
||||
return tooltipProps?.title ? <Tooltip {...tooltipProps}>{children}</Tooltip> : children
|
||||
}
|
||||
|
||||
const MenuItemWrapper = ({ children, option }: { children: ReactNode; option: OptionMenuItemType }) => {
|
||||
if (option.href) {
|
||||
return (
|
||||
<Box component={Link} href={option.href} {...option.linkProps}>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
} else {
|
||||
return <>{children}</>
|
||||
}
|
||||
}
|
||||
|
||||
const OptionMenu = (props: OptionsMenuType) => {
|
||||
// Props
|
||||
const { tooltipProps, icon, iconClassName, options, leftAlignMenu, iconButtonProps } = props
|
||||
|
||||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
// Refs
|
||||
const anchorRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen(prevOpen => !prevOpen)
|
||||
}
|
||||
|
||||
const handleClose = (event: Event | SyntheticEvent) => {
|
||||
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButtonWrapper tooltipProps={tooltipProps}>
|
||||
<IconButton ref={anchorRef} size='small' onClick={handleToggle} {...iconButtonProps}>
|
||||
{typeof icon === 'string' ? (
|
||||
<i className={classnames(icon, iconClassName)} />
|
||||
) : (icon as ReactNode) ? (
|
||||
icon
|
||||
) : (
|
||||
<i className={classnames('ri-more-2-line', iconClassName)} />
|
||||
)}
|
||||
</IconButton>
|
||||
</IconButtonWrapper>
|
||||
<Popper
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
placement={leftAlignMenu ? 'bottom-start' : 'bottom-end'}
|
||||
transition
|
||||
disablePortal
|
||||
sx={{ zIndex: 1 }}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Fade {...TransitionProps}>
|
||||
<Paper className='shadow-lg'>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MenuList autoFocusItem={open}>
|
||||
{options.map((option: OptionType, index: number) => {
|
||||
if (typeof option === 'string') {
|
||||
return (
|
||||
<MenuItem key={index} onClick={handleClose}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
)
|
||||
} else if ('divider' in option) {
|
||||
return option.divider && <Divider key={index} {...option.dividerProps} />
|
||||
} else {
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
{...option.menuItemProps}
|
||||
{...(option.href && { className: 'p-0' })}
|
||||
onClick={e => {
|
||||
handleClose(e)
|
||||
option.menuItemProps && option.menuItemProps.onClick
|
||||
? option.menuItemProps.onClick(e)
|
||||
: null
|
||||
}}
|
||||
>
|
||||
<MenuItemWrapper option={option}>
|
||||
{(typeof option.icon === 'string' ? <i className={option.icon} /> : option.icon) || null}
|
||||
{option.text}
|
||||
</MenuItemWrapper>
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Fade>
|
||||
)}
|
||||
</Popper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OptionMenu
|
||||
@@ -0,0 +1,42 @@
|
||||
// React Imports
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import type { LinkProps } from 'next/link'
|
||||
|
||||
// MUI Imports
|
||||
import type { IconButtonProps } from '@mui/material/IconButton'
|
||||
import type { MenuItemProps } from '@mui/material/MenuItem'
|
||||
import type { DividerProps } from '@mui/material/Divider'
|
||||
import type { BoxProps } from '@mui/material/Box'
|
||||
import type { TooltipProps } from '@mui/material/Tooltip'
|
||||
|
||||
export type OptionDividerType = {
|
||||
divider: boolean
|
||||
dividerProps?: DividerProps
|
||||
href?: never
|
||||
icon?: never
|
||||
text?: never
|
||||
linkProps?: never
|
||||
menuItemProps?: never
|
||||
}
|
||||
export type OptionMenuItemType = {
|
||||
text: ReactNode
|
||||
icon?: ReactNode
|
||||
linkProps?: BoxProps
|
||||
href?: LinkProps['href']
|
||||
menuItemProps?: MenuItemProps
|
||||
divider?: never
|
||||
dividerProps?: never
|
||||
}
|
||||
|
||||
export type OptionType = string | OptionDividerType | OptionMenuItemType
|
||||
|
||||
export type OptionsMenuType = {
|
||||
tooltipProps?: Omit<TooltipProps, 'children'>
|
||||
icon?: ReactNode
|
||||
iconClassName?: string
|
||||
options: OptionType[]
|
||||
leftAlignMenu?: boolean
|
||||
iconButtonProps?: IconButtonProps
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import type { ReactNode } from 'react'
|
||||
import { createContext, useMemo, useState } from 'react'
|
||||
|
||||
// Type Imports
|
||||
import type { Mode } from '@core/types'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
// Hook Imports
|
||||
import { useObjectCookie } from '@core/hooks/useObjectCookie'
|
||||
|
||||
// Settings type
|
||||
export type Settings = {
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
// UpdateSettingsOptions type
|
||||
type UpdateSettingsOptions = {
|
||||
updateCookie?: boolean
|
||||
}
|
||||
|
||||
// SettingsContextProps type
|
||||
type SettingsContextProps = {
|
||||
settings: Settings
|
||||
updateSettings: (settings: Partial<Settings>, options?: UpdateSettingsOptions) => void
|
||||
isSettingsChanged: boolean
|
||||
resetSettings: () => void
|
||||
updatePageSettings: (settings: Partial<Settings>) => () => void
|
||||
}
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
settingsCookie: Settings | null
|
||||
mode?: Mode
|
||||
}
|
||||
|
||||
// Initial Settings Context
|
||||
export const SettingsContext = createContext<SettingsContextProps | null>(null)
|
||||
|
||||
// Settings Provider
|
||||
export const SettingsProvider = (props: Props) => {
|
||||
// Initial Settings
|
||||
const initialSettings: Settings = {
|
||||
mode: themeConfig.mode
|
||||
}
|
||||
|
||||
const updatedInitialSettings = {
|
||||
...initialSettings,
|
||||
mode: props.mode || themeConfig.mode
|
||||
}
|
||||
|
||||
// Cookies
|
||||
const [settingsCookie, updateSettingsCookie] = useObjectCookie<Settings>(
|
||||
themeConfig.settingsCookieName,
|
||||
JSON.stringify(props.settingsCookie) !== '{}' ? props.settingsCookie : updatedInitialSettings
|
||||
)
|
||||
|
||||
// State
|
||||
const [_settingsState, _updateSettingsState] = useState<Settings>(
|
||||
JSON.stringify(settingsCookie) !== '{}' ? settingsCookie : updatedInitialSettings
|
||||
)
|
||||
|
||||
const updateSettings = (settings: Partial<Settings>, options?: UpdateSettingsOptions) => {
|
||||
const { updateCookie = true } = options || {}
|
||||
|
||||
_updateSettingsState(prev => {
|
||||
const newSettings = { ...prev, ...settings }
|
||||
|
||||
// Update cookie if needed
|
||||
if (updateCookie) updateSettingsCookie(newSettings)
|
||||
|
||||
return newSettings
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the settings for page with the provided settings object.
|
||||
* Updated settings won't be saved to cookie hence will be reverted once navigating away from the page.
|
||||
*
|
||||
* @param settings - The partial settings object containing the properties to update.
|
||||
* @returns A function to reset the page settings.
|
||||
*
|
||||
* @example
|
||||
* useEffect(() => {
|
||||
* return updatePageSettings({ theme: 'dark' });
|
||||
* }, []);
|
||||
*/
|
||||
const updatePageSettings = (settings: Partial<Settings>): (() => void) => {
|
||||
updateSettings(settings, { updateCookie: false })
|
||||
|
||||
// Returns a function to reset the page settings
|
||||
return () => updateSettings(settingsCookie, { updateCookie: false })
|
||||
}
|
||||
|
||||
const resetSettings = () => {
|
||||
updateSettings(initialSettings)
|
||||
}
|
||||
|
||||
const isSettingsChanged = useMemo(
|
||||
() => JSON.stringify(initialSettings) !== JSON.stringify(_settingsState),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[_settingsState]
|
||||
)
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider
|
||||
value={{
|
||||
settings: _settingsState,
|
||||
updateSettings,
|
||||
isSettingsChanged,
|
||||
resetSettings,
|
||||
updatePageSettings
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SettingsContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import apiClient from '@/lib/apiClient';
|
||||
|
||||
export const downloadInvoicePdf = async (invoiceId: string) => {
|
||||
const response = await apiClient.get(
|
||||
`/invoices/download/${invoiceId}`,
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const blob = new Blob([response.data], { type: 'application/pdf' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `factura-${invoiceId}.pdf`;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// src/@core/hooks/useAccessLogs.ts
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { useUserAuth } from '@/app/context/UserAuth';
|
||||
|
||||
export type AccessLog = {
|
||||
id: string;
|
||||
userId: string;
|
||||
direction: 'INGRESS' | 'EGRESS';
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
export type ResponseAccessLogs = {
|
||||
userId: string;
|
||||
name: string;
|
||||
accessLogs: AccessLog[];
|
||||
};
|
||||
|
||||
export function useAccessLogs(userId?: string, date?: string, enabled: boolean = true) {
|
||||
const { token } = useUserAuth();
|
||||
const [data, setData] = useState<ResponseAccessLogs | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (signal?: AbortSignal) => {
|
||||
if (!enabled || !userId || !date) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await apiClient.get<ResponseAccessLogs>('/attendance', {
|
||||
params: { userId, date },
|
||||
signal,
|
||||
});
|
||||
setData(res.data);
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'CanceledError') return;
|
||||
setError(e?.response?.data?.message ?? 'No se pudieron cargar los registros de acceso.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[enabled, userId, date, token]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const ctrl = new AbortController();
|
||||
fetchData(ctrl.signal);
|
||||
return () => ctrl.abort();
|
||||
}, [fetchData]);
|
||||
|
||||
return { data, loading, error, refresh: fetchData };
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// src/@core/hooks/useAttendeesSnapshot.ts
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { useUserAuth } from '@/app/context/UserAuth';
|
||||
|
||||
export type ResponseAttendeesSnapshot = {
|
||||
dateRange: { start: string; end: string };
|
||||
instructors: { id: string; name: string }[];
|
||||
assistants: { id: string; name: string }[];
|
||||
attendance: { id: string; name: string }[];
|
||||
};
|
||||
|
||||
export function useAttendeesSnapshot(snapshotId?: string, enabled: boolean = true) {
|
||||
const { token } = useUserAuth();
|
||||
const [data, setData] = useState<ResponseAttendeesSnapshot | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (signal?: AbortSignal) => {
|
||||
if (!enabled || !snapshotId) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await apiClient.get<ResponseAttendeesSnapshot>(
|
||||
`/sessions/${snapshotId}/attendees`,
|
||||
{ signal }
|
||||
);
|
||||
setData(res.data);
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'CanceledError') return;
|
||||
setError(e?.response?.data?.message ?? 'No se pudieron cargar los asistentes.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[enabled, snapshotId, token]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const ctrl = new AbortController();
|
||||
fetchData(ctrl.signal);
|
||||
return () => ctrl.abort();
|
||||
}, [fetchData]);
|
||||
|
||||
return { data, loading, error, refresh: fetchData };
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// useClases.ts
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { Clase } from '@/views/clases/DetailClaseDialog';
|
||||
|
||||
type Options = {
|
||||
/** Lanza el fetch al montar el hook (default: true) */
|
||||
auto?: boolean;
|
||||
/** Filtro de estado: 'all', 'active', 'inactive' */
|
||||
status?: 'all' | 'active' | 'inactive';
|
||||
};
|
||||
|
||||
type UseClasesResult = {
|
||||
data: Clase[];
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
/** Vuelve a pedir la lista */
|
||||
refresh: () => Promise<void>;
|
||||
/** Setea manualmente el array (útil para optimist updates) */
|
||||
setData: React.Dispatch<React.SetStateAction<Clase[]>>;
|
||||
};
|
||||
|
||||
export function useClases(opts: Options = {}): UseClasesResult {
|
||||
const { auto = true, status = 'all' } = opts;
|
||||
|
||||
const [data, setData] = useState<Clase[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(auto);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
// guardamos una referencia al abortController para cancelar en unmount
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// cancelar request previa si hubiera
|
||||
if (abortRef.current) abortRef.current.abort();
|
||||
const ac = new AbortController();
|
||||
abortRef.current = ac;
|
||||
|
||||
try {
|
||||
const params = status && status !== 'all' ? { status } : undefined;
|
||||
const res = await apiClient.get<Clase[]>('/sessions', { params, signal: ac.signal });
|
||||
setData(res.data ?? []);
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'CanceledError' || e?.message === 'canceled') return;
|
||||
setError(e);
|
||||
console.error('Error al obtener clases:', e);
|
||||
} finally {
|
||||
if (!ac.signal.aborted) setLoading(false);
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (auto) refresh();
|
||||
return () => {
|
||||
// cancelar si el componente se desmonta
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [auto, refresh]);
|
||||
|
||||
return { data, loading, error, refresh, setData };
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// useDashboard.ts
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import apiClient from '@/lib/apiClient'
|
||||
import { useUserAuth } from '@/app/context/UserAuth'
|
||||
|
||||
export type DashboardUser = {
|
||||
message: string
|
||||
accessLog: { currentAccess: string; lastAccess: string }
|
||||
cardDetails: { sessions: number; users: number; assistants: number; revenue: number }
|
||||
sessions: { id: string; description: string; instructors: string; startDate: string }[]
|
||||
}
|
||||
|
||||
type Options = {
|
||||
auto?: boolean
|
||||
}
|
||||
|
||||
type UseDashboardResult = {
|
||||
data: DashboardUser | null
|
||||
loading: boolean
|
||||
error: unknown
|
||||
refresh: () => Promise<void>
|
||||
}
|
||||
|
||||
export function useDashboard(opts: Options = {}): UseDashboardResult {
|
||||
const { token } = useUserAuth()
|
||||
const { auto = true } = opts
|
||||
|
||||
const [data, setData] = useState<DashboardUser | null>(null)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<unknown>(null)
|
||||
|
||||
const abortRef = useRef<AbortController | null>(null)
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
// si no hay token aún, no pidas nada ni cambies loading
|
||||
if (!token) return
|
||||
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
abortRef.current?.abort()
|
||||
const ac = new AbortController()
|
||||
abortRef.current = ac
|
||||
|
||||
try {
|
||||
const res = await apiClient.get<DashboardUser>('/dashboard', { signal: ac.signal })
|
||||
setData(res.data ?? null)
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'CanceledError' || e?.message === 'canceled') return
|
||||
setError(e)
|
||||
console.error('Error al obtener dashboard:', e)
|
||||
} finally {
|
||||
if (!ac.signal.aborted) setLoading(false)
|
||||
}
|
||||
}, [token]) // ← incluye token!
|
||||
|
||||
useEffect(() => {
|
||||
if (auto && token) refresh() // ← solo cuando haya token
|
||||
return () => abortRef.current?.abort()
|
||||
}, [auto, token, refresh]) // ← depende de token también
|
||||
|
||||
return { data, loading, error, refresh }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// React Imports
|
||||
import { useMemo } from 'react'
|
||||
|
||||
// Third-party imports
|
||||
import { useColorScheme } from '@mui/material'
|
||||
|
||||
// Type imports
|
||||
import type { Mode } from '@core/types'
|
||||
|
||||
export const useImageVariant = (mode: Mode, imgLight: string, imgDark: string): string => {
|
||||
// Hooks
|
||||
const { mode: muiMode } = useColorScheme()
|
||||
|
||||
return useMemo(() => {
|
||||
const isServer = typeof window === 'undefined'
|
||||
|
||||
const currentMode = (() => {
|
||||
if (isServer) return mode
|
||||
|
||||
return muiMode
|
||||
})()
|
||||
|
||||
const isDarkMode = currentMode === 'dark'
|
||||
|
||||
return isDarkMode ? imgDark : imgLight
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [mode, muiMode])
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// src/@core/hooks/useInstructors.ts
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { useUserAuth } from '@/app/context/UserAuth';
|
||||
import type { Instructor } from '@/app/modules/users/dto/instructor.dto';
|
||||
|
||||
type UseInstructorsResult = {
|
||||
data: Instructor[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook para obtener la lista de instructores.
|
||||
* - Lee el token del contexto.
|
||||
* - Maneja estados data/loading/error.
|
||||
* - Cancela la request al desmontar.
|
||||
*/
|
||||
export function useInstructors(enabled: boolean = true): UseInstructorsResult {
|
||||
const { token } = useUserAuth();
|
||||
|
||||
const [data, setData] = useState<Instructor[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (signal?: AbortSignal) => {
|
||||
if (!token) {
|
||||
setData([]);
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const res = await apiClient.get<Instructor[]>('/users/instructor', { signal });
|
||||
|
||||
setData(res.data ?? []);
|
||||
} catch (err: any) {
|
||||
if (err?.name !== 'CanceledError') {
|
||||
const msg =
|
||||
err?.response?.data?.message ||
|
||||
err?.message ||
|
||||
'Error al obtener instructores.';
|
||||
setError(msg);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[token]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const ctrl = new AbortController();
|
||||
fetchData(ctrl.signal);
|
||||
return () => ctrl.abort();
|
||||
}, [enabled, fetchData]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
return { data, loading, error, refresh };
|
||||
}
|
||||
|
||||
export default useInstructors;
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
export type InvoiceStatus = 'PENDING' | 'PAID' | 'FAILED' | 'CANCELED';
|
||||
|
||||
export type InvoiceWithDescription = {
|
||||
id: string;
|
||||
subscriptionId: string;
|
||||
base64Invoice: string | null;
|
||||
linkPayment: string | null;
|
||||
status: InvoiceStatus | string;
|
||||
amount: number;
|
||||
createdAt: string | Date;
|
||||
updatedAt: string | Date;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type UseInvoicesOptions = {
|
||||
auto?: boolean;
|
||||
};
|
||||
|
||||
export type UseInvoicesResult = {
|
||||
data: InvoiceWithDescription[];
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
refresh: () => Promise<void>;
|
||||
setData: React.Dispatch<React.SetStateAction<InvoiceWithDescription[]>>;
|
||||
};
|
||||
|
||||
export function useInvoices(
|
||||
userId: string,
|
||||
opts: UseInvoicesOptions = {}
|
||||
): UseInvoicesResult {
|
||||
const { auto = true } = opts;
|
||||
|
||||
const [data, setData] = useState<InvoiceWithDescription[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(auto);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!userId) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
if (abortRef.current) abortRef.current.abort();
|
||||
const ac = new AbortController();
|
||||
abortRef.current = ac;
|
||||
|
||||
try {
|
||||
const res = await apiClient.get<InvoiceWithDescription[] | null>(`/invoices/${encodeURIComponent(userId)}`, { signal: ac.signal });
|
||||
setData((res.data ?? []) as InvoiceWithDescription[]);
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'CanceledError' || e?.message === 'canceled') return;
|
||||
setError(e);
|
||||
} finally {
|
||||
if (!ac.signal.aborted) setLoading(false);
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (auto) refresh();
|
||||
return () => abortRef.current?.abort();
|
||||
}, [auto, refresh, userId]);
|
||||
|
||||
return { data, loading, error, refresh, setData };
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// React Imports
|
||||
import { useMemo } from 'react'
|
||||
|
||||
// Third-party Imports
|
||||
import { useCookie } from 'react-use'
|
||||
|
||||
export const useObjectCookie = <T>(key: string, fallback?: T | null): [T, (newVal: T) => void] => {
|
||||
// Hooks
|
||||
const [valStr, updateCookie] = useCookie(key)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const value = useMemo<T>(() => (valStr ? JSON.parse(valStr) : fallback), [valStr])
|
||||
|
||||
const updateValue = (newVal: T) => {
|
||||
updateCookie(JSON.stringify(newVal))
|
||||
}
|
||||
|
||||
return [value, updateValue]
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
|
||||
export type ProfilePictureResponse = {
|
||||
profilePicture: string | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
|
||||
export type UseProfilePictureOptions = {
|
||||
auto?: boolean;
|
||||
};
|
||||
|
||||
export type UseProfilePictureResult = {
|
||||
profilePicture: string | null;
|
||||
updatedAt: string | null;
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
refresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook para obtener la foto de perfil de un usuario
|
||||
*
|
||||
* @param userId - ID del usuario
|
||||
* @param options - Opciones de configuración
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { profilePicture, loading, error, refresh } = useProfilePicture('user-123', { auto: true });
|
||||
*
|
||||
* return (
|
||||
* <img src={profilePicture || '/default-avatar.png'} alt="Profile" />
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function useProfilePicture(
|
||||
userId: string,
|
||||
options: UseProfilePictureOptions = {}
|
||||
): UseProfilePictureResult {
|
||||
const { auto = true } = options;
|
||||
|
||||
const [profilePicture, setProfilePicture] = useState<string | null>(null);
|
||||
const [updatedAt, setUpdatedAt] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(auto);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!userId) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await apiClient.get<ProfilePictureResponse>(`/users/${encodeURIComponent(userId)}/profile-picture`);
|
||||
|
||||
setProfilePicture(res.data.profilePicture);
|
||||
setUpdatedAt(res.data.updatedAt);
|
||||
} catch (e: any) {
|
||||
setError(e);
|
||||
setProfilePicture(null);
|
||||
setUpdatedAt(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (auto && userId) {
|
||||
refresh();
|
||||
}
|
||||
}, [auto, userId, refresh]);
|
||||
|
||||
return {
|
||||
profilePicture,
|
||||
updatedAt,
|
||||
loading,
|
||||
error,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// useSessionInvoicesByMonth.ts
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
|
||||
export type InvoiceStatus = 'PENDING' | 'PAID' | 'CANCELED';
|
||||
|
||||
export type AssistantInvoice = {
|
||||
id: string;
|
||||
name: string;
|
||||
hasInvoice: boolean;
|
||||
invoiceStatus: InvoiceStatus | null;
|
||||
invoiceId: string | null;
|
||||
amount: number | null;
|
||||
};
|
||||
|
||||
export type SessionInvoicesByMonth = {
|
||||
sessionId: string;
|
||||
sessionCustomId: string;
|
||||
sessionDescription: string;
|
||||
sessionType: string;
|
||||
month: string;
|
||||
year: string;
|
||||
assistants: AssistantInvoice[];
|
||||
};
|
||||
|
||||
type Options = {
|
||||
sessionId: string;
|
||||
month: string; // "01" a "12"
|
||||
year: string; // "YYYY"
|
||||
auto?: boolean;
|
||||
};
|
||||
|
||||
type UseSessionInvoicesByMonthResult = {
|
||||
data: SessionInvoicesByMonth | null;
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
refresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
export function useSessionInvoicesByMonth(
|
||||
opts: Options
|
||||
): UseSessionInvoicesByMonthResult {
|
||||
const { sessionId, month, year, auto = true } = opts;
|
||||
|
||||
const [data, setData] = useState<SessionInvoicesByMonth | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(auto);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!sessionId || !month || !year) {
|
||||
setData(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Cancelar request previa si hubiera
|
||||
if (abortRef.current) abortRef.current.abort();
|
||||
const ac = new AbortController();
|
||||
abortRef.current = ac;
|
||||
|
||||
try {
|
||||
const res = await apiClient.get<SessionInvoicesByMonth>(
|
||||
`/sessions/${sessionId}/invoices-by-month`,
|
||||
{ params: { month, year }, signal: ac.signal }
|
||||
);
|
||||
setData(res.data);
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'CanceledError' || e?.message === 'canceled') return;
|
||||
setError(e);
|
||||
console.error('Error al obtener facturas por mes:', e);
|
||||
} finally {
|
||||
if (!ac.signal.aborted) setLoading(false);
|
||||
}
|
||||
}, [sessionId, month, year]);
|
||||
|
||||
useEffect(() => {
|
||||
if (auto) refresh();
|
||||
return () => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [auto, refresh]);
|
||||
|
||||
return { data, loading, error, refresh };
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// useSessionParticipants.ts
|
||||
'use client';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
|
||||
export type Participant = { id: string; name: string; customId?: string };
|
||||
|
||||
export function useSessionParticipants(token: string, sessionId: string, auto = true) {
|
||||
const [selected, setSelected] = useState<Participant[]>([]);
|
||||
const [available, setAvailable] = useState<Participant[]>([]);
|
||||
const [loading, setLoading] = useState(auto);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true); setError(null);
|
||||
try {
|
||||
const { data } = await apiClient.get(`/sessions/${sessionId}/participants`);
|
||||
setSelected(data.selected);
|
||||
setAvailable(data.available);
|
||||
} catch (e) { setError(e); } finally { setLoading(false); }
|
||||
}, [sessionId]);
|
||||
|
||||
const save = useCallback(async () => {
|
||||
await apiClient.put(`/sessions/${sessionId}/participants`, {
|
||||
userIds: selected.map(s => s.id),
|
||||
});
|
||||
}, [sessionId, selected]);
|
||||
|
||||
useEffect(() => { if (auto) refresh(); }, [auto, refresh]);
|
||||
|
||||
// helpers UI
|
||||
const add = (p: Participant) => {
|
||||
setAvailable(prev => prev.filter(x => x.id !== p.id));
|
||||
setSelected(prev => [...prev, p]);
|
||||
};
|
||||
const remove = (p: Participant) => {
|
||||
setSelected(prev => prev.filter(x => x.id !== p.id));
|
||||
setAvailable(prev => [...prev, p]);
|
||||
};
|
||||
|
||||
return { selected, available, setSelected, setAvailable, loading, error, refresh, save, add, remove };
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// React Imports
|
||||
import { useContext } from 'react'
|
||||
|
||||
// Context Imports
|
||||
import { SettingsContext } from '@core/contexts/settingsContext'
|
||||
|
||||
export const useSettings = () => {
|
||||
// Hooks
|
||||
const context = useContext(SettingsContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useSettingsContext must be used within a SettingsProvider')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
import type { SystemConfig, UpdateSystemConfigDto } from '@/@core/types/system-config';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export type UseSystemConfigOptions = {};
|
||||
|
||||
export type UseSystemConfigResult = {
|
||||
data: SystemConfig | null;
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
fetchConfig: () => Promise<void>;
|
||||
updateConfig: (dto: UpdateSystemConfigDto) => Promise<void>;
|
||||
reloadConfig: () => Promise<void>;
|
||||
updating: boolean;
|
||||
reloading: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook para gestionar la configuración del sistema
|
||||
*/
|
||||
export function useSystemConfig(
|
||||
options: UseSystemConfigOptions = {}
|
||||
): UseSystemConfigResult {
|
||||
|
||||
const [data, setData] = useState<SystemConfig | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const [reloading, setReloading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
// Obtener configuración actual
|
||||
const fetchConfig = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await apiClient.get<SystemConfig>('/system-config');
|
||||
setData(res.data);
|
||||
} catch (e: any) {
|
||||
console.log('Error fetching system config:', e);
|
||||
setError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Actualizar configuración
|
||||
const updateConfig = useCallback(
|
||||
async (dto: UpdateSystemConfigDto) => {
|
||||
setUpdating(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await apiClient.patch<SystemConfig>('/system-config', dto);
|
||||
setData(res.data);
|
||||
} catch (e: any) {
|
||||
setError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
setUpdating(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Recargar configuración desde BD
|
||||
const reloadConfig = useCallback(async () => {
|
||||
setReloading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await apiClient.get<SystemConfig>('/system-config/reload');
|
||||
setData(res.data);
|
||||
} catch (e: any) {
|
||||
setError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
setReloading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
fetchConfig,
|
||||
updateConfig,
|
||||
reloadConfig,
|
||||
updating,
|
||||
reloading,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useUserAuth } from '@/app/context/UserAuth';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const useToggleUserActive = () => {
|
||||
const { token } = useUserAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const toggleActive = async (userId: string, isActive: boolean) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
<<<<<<< Updated upstream
|
||||
const deleted = isActive ? null : new Date().toISOString();
|
||||
const response = await apiClient.put(`/users/${userId}/active`, { deleted });
|
||||
console.log('✅ User active status updated successfully:', response.data);
|
||||
=======
|
||||
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL ?? ''}/users/${userId}/active`;
|
||||
const deleted = isActive ? null : new Date().toISOString();
|
||||
const response = await axios.put(
|
||||
url,
|
||||
{ deleted },
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
>>>>>>> Stashed changes
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('❌ Error al cambiar estado del usuario', err);
|
||||
setError('No se pudo cambiar el estado del usuario');
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { toggleActive, loading, error };
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import type { InvoiceStatus } from './useInvoices';
|
||||
|
||||
export type UpdateInvoiceStatusParams = {
|
||||
userId: string;
|
||||
invoiceId: string;
|
||||
status: InvoiceStatus;
|
||||
};
|
||||
|
||||
export type UpdateInvoiceStatusOptions = {
|
||||
onSuccess?: () => void | Promise<void>;
|
||||
onError?: (error: any) => void;
|
||||
};
|
||||
|
||||
export type UpdateInvoiceStatusResult = {
|
||||
mutate: (params: UpdateInvoiceStatusParams) => Promise<void>;
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
isSuccess: boolean;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook para actualizar el estado de una factura
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { mutate, loading, error, isSuccess } = useUpdateInvoiceStatus({
|
||||
* onSuccess: () => {
|
||||
* toast.success('Factura actualizada');
|
||||
* refetchInvoices();
|
||||
* },
|
||||
* onError: (error) => {
|
||||
* toast.error('Error al actualizar factura');
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Usar la mutación
|
||||
* await mutate({
|
||||
* userId: 'user-123',
|
||||
* invoiceId: 'invoice-456',
|
||||
* status: 'PAID'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function useUpdateInvoiceStatus(
|
||||
options: UpdateInvoiceStatusOptions = {}
|
||||
): UpdateInvoiceStatusResult {
|
||||
const { onSuccess, onError } = options;
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
const mutate = useCallback(
|
||||
async ({ userId, invoiceId, status }: UpdateInvoiceStatusParams) => {
|
||||
if (!userId || !invoiceId || !status) {
|
||||
const validationError = new Error('userId, invoiceId y status son requeridos');
|
||||
setError(validationError);
|
||||
onError?.(validationError);
|
||||
throw validationError;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setIsSuccess(false);
|
||||
|
||||
try {
|
||||
await apiClient.put(
|
||||
`/invoices/${encodeURIComponent(userId)}/${encodeURIComponent(invoiceId)}`,
|
||||
{ status }
|
||||
);
|
||||
|
||||
setIsSuccess(true);
|
||||
|
||||
// Ejecutar callback de éxito
|
||||
await onSuccess?.();
|
||||
} catch (e: any) {
|
||||
setError(e);
|
||||
setIsSuccess(false);
|
||||
onError?.(e);
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[onSuccess, onError]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
setIsSuccess(false);
|
||||
}, []);
|
||||
|
||||
return { mutate, loading, error, isSuccess, reset };
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// useUsuarios.ts
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { Usuario } from '@/views/usuarios/usuarios.dto';
|
||||
|
||||
type Options = {
|
||||
/** Lanza el fetch al montar el hook (default: true) */
|
||||
auto?: boolean;
|
||||
};
|
||||
|
||||
type UseUsuariosResult = {
|
||||
data: Usuario[];
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
/** Vuelve a pedir la lista */
|
||||
refresh: () => Promise<void>;
|
||||
/** Setea manualmente el array (útil para optimist updates) */
|
||||
setData: React.Dispatch<React.SetStateAction<Usuario[]>>;
|
||||
};
|
||||
|
||||
export function useUsuarios(opts: Options = {}): UseUsuariosResult {
|
||||
const { auto = true } = opts;
|
||||
|
||||
const [data, setData] = useState<Usuario[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(auto);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
// guardamos una referencia al abortController para cancelar en unmount
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// cancelar request previa si hubiera
|
||||
if (abortRef.current) abortRef.current.abort();
|
||||
const ac = new AbortController();
|
||||
abortRef.current = ac;
|
||||
|
||||
try {
|
||||
<<<<<<< Updated upstream
|
||||
const res = await apiClient.get<Usuario[]>('/users', { signal: ac.signal });
|
||||
=======
|
||||
if (!token) throw new Error('Token no encontrado');
|
||||
|
||||
// Asegurar que baseUrl no esté vacío
|
||||
const backendUrl = baseUrl
|
||||
const url = `${backendUrl}/users`;
|
||||
|
||||
|
||||
const res = await axios.get<Usuario[]>(url, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
signal: ac.signal, // axios v1 soporta AbortController
|
||||
});
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
setData(res.data ?? []);
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'CanceledError' || e?.message === 'canceled') return;
|
||||
setError(e);
|
||||
console.error('Error al obtener usuarios:', e);
|
||||
} finally {
|
||||
if (!ac.signal.aborted) setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (auto) refresh();
|
||||
return () => {
|
||||
// cancelar si el componente se desmonta
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [auto, refresh]);
|
||||
|
||||
return { data, loading, error, refresh, setData };
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// hooks/useWhatsappStatus.ts
|
||||
'use client';
|
||||
import useSWR from 'swr';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
|
||||
const WHATSAPP_STATUS_PATH = '/whatsapp/status';
|
||||
|
||||
const fetcher = async (path: string) => {
|
||||
const res = await apiClient.get(path);
|
||||
return res.data; // { status, phone, qr }
|
||||
};
|
||||
|
||||
export type UseWhatsappStatusOptions = {};
|
||||
|
||||
export function useWhatsappStatus(_options: UseWhatsappStatusOptions = {}) {
|
||||
const { data, error, isLoading, mutate } = useSWR(
|
||||
WHATSAPP_STATUS_PATH,
|
||||
fetcher,
|
||||
{
|
||||
// más frecuencia si NO hay sesión; más relajado si ya está conectada
|
||||
refreshInterval: (data) =>
|
||||
data?.status === 'Correcto' ? 30_000 : 3_000,
|
||||
revalidateOnFocus: false,
|
||||
shouldRetryOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
status: data?.status ?? 'Sin session',
|
||||
phone: data?.phone ?? 'No disponible',
|
||||
qr: data?.qr ?? null,
|
||||
loading: isLoading,
|
||||
error,
|
||||
refresh: () => mutate(), // botón "Actualizar"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// MUI imports
|
||||
import Box from '@mui/material/Box'
|
||||
import { styled } from '@mui/material/styles'
|
||||
import type { BoxProps } from '@mui/material/Box'
|
||||
|
||||
const StepperWrapper = styled(Box)<BoxProps>(({ theme }) => {
|
||||
return {
|
||||
[theme.breakpoints.down('md')]: {
|
||||
'& .MuiStepper-horizontal:not(.MuiStepper-alternativeLabel)': {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start'
|
||||
}
|
||||
},
|
||||
'& .MuiStep-root': {
|
||||
'& .MuiStepLabel-iconContainer:empty': {
|
||||
display: 'none'
|
||||
},
|
||||
'& .step-label': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
'& .step-number': {
|
||||
...theme.typography.h4,
|
||||
marginRight: theme.spacing(2)
|
||||
},
|
||||
'&:not(:has(.step-subtitle)) .step-number': {
|
||||
...theme.typography.h6
|
||||
},
|
||||
'& .step-title': {
|
||||
...theme.typography.body1,
|
||||
letterSpacing: 0.15,
|
||||
fontWeight: 500
|
||||
},
|
||||
'& .step-subtitle': {
|
||||
...theme.typography.body2,
|
||||
color: 'var(--mui-palette-text-secondary)'
|
||||
},
|
||||
'& .MuiStepLabel-root.Mui-disabled': {
|
||||
'& .step-number': {
|
||||
color: 'var(--mui-palette-text-disabled)'
|
||||
}
|
||||
},
|
||||
'& .Mui-error': {
|
||||
'& .MuiStepLabel-labelContainer, & .step-number, & .step-title, & .step-subtitle': {
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .MuiStepConnector-root': {
|
||||
'& .MuiStepConnector-line': {
|
||||
borderBlockStartWidth: 3,
|
||||
borderRadius: 3
|
||||
},
|
||||
'&.Mui-active, &.Mui-completed': {
|
||||
'& .MuiStepConnector-line': {
|
||||
borderColor: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
},
|
||||
'&.Mui-disabled .MuiStepConnector-line': {
|
||||
borderColor: 'var(--mui-palette-primary-lightOpacity)'
|
||||
}
|
||||
},
|
||||
'& .MuiStepper-alternativeLabel': {
|
||||
'& .MuiStepConnector-root': {
|
||||
top: 9
|
||||
},
|
||||
'& .MuiStepLabel-labelContainer': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column'
|
||||
}
|
||||
},
|
||||
'& .MuiStepper-vertical': {
|
||||
'& .MuiStep-root': {
|
||||
'& .step-label': {
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
'& .MuiStepContent-root': {
|
||||
borderInlineStartWidth: 3,
|
||||
marginLeft: theme.spacing(2.25),
|
||||
borderColor: 'var(--mui-palette-primary-main)'
|
||||
},
|
||||
'& .button-wrapper': {
|
||||
marginTop: theme.spacing(4)
|
||||
},
|
||||
'&.active + .MuiStepConnector-root .MuiStepConnector-line': {
|
||||
borderColor: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
},
|
||||
'& .MuiStepConnector-root': {
|
||||
marginLeft: theme.spacing(2.25),
|
||||
'& .MuiStepConnector-line': {
|
||||
borderBlockStartWidth: 0,
|
||||
borderInlineStartWidth: 3,
|
||||
borderRadius: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default StepperWrapper
|
||||
@@ -0,0 +1,92 @@
|
||||
.table {
|
||||
inline-size: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875rem;
|
||||
white-space: nowrap;
|
||||
|
||||
[align='right'] > * {
|
||||
text-align: end;
|
||||
}
|
||||
[align='center'] > * {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
thead {
|
||||
text-transform: uppercase;
|
||||
color: var(--mui-palette-text-primary);
|
||||
|
||||
th {
|
||||
font-weight: 500;
|
||||
font-size: 0.8125rem;
|
||||
letter-spacing: 0.2px;
|
||||
line-height: 1.8462;
|
||||
text-align: start;
|
||||
block-size: 56px;
|
||||
background-color: var(--mui-palette-customColors-tableHeaderBg);
|
||||
&:not(:first-of-type):not(:last-of-type) {
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
&:first-of-type {
|
||||
&:not(:has(input[type='checkbox'])) {
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1.25rem 1rem;
|
||||
}
|
||||
&:has(input[type='checkbox']) {
|
||||
padding-inline-start: 0.6875rem;
|
||||
}
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1rem 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
color: var(--mui-palette-text-secondary);
|
||||
|
||||
th,
|
||||
td {
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.4667;
|
||||
block-size: 50px;
|
||||
&:not(:first-of-type):not(:last-of-type) {
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
&:first-of-type {
|
||||
&:not(:has(input[type='checkbox'])) {
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1.25rem 1rem;
|
||||
}
|
||||
&:has(input[type='checkbox']) {
|
||||
padding-inline-start: 0.6875rem;
|
||||
}
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1rem 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
tr:not(:last-child) {
|
||||
border-block-end: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cellWithInput input {
|
||||
inline-size: 100%;
|
||||
background-color: transparent;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
border-radius: var(--mui-shape-customBorderRadius-sm);
|
||||
padding-block: 6px;
|
||||
padding-inline: 10px;
|
||||
margin-inline: -10px;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid var(--mui-palette-primary-main);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// MUI Imports
|
||||
import { lighten } from '@mui/material/styles'
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { MenuItemStyles } from '@menu/types'
|
||||
|
||||
// Util Imports
|
||||
import { menuClasses } from '@menu/utils/menuClasses'
|
||||
|
||||
const menuItemStyles = (theme: Theme): MenuItemStyles => {
|
||||
return {
|
||||
root: {
|
||||
marginBlockStart: theme.spacing(1.5),
|
||||
[`&.${menuClasses.subMenuRoot}.${menuClasses.open} > .${menuClasses.button}, &.${menuClasses.subMenuRoot} > .${menuClasses.button}.${menuClasses.active}`]:
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-action-selected) !important'
|
||||
},
|
||||
[`&.${menuClasses.disabled} > .${menuClasses.button}`]: {
|
||||
color: 'var(--mui-palette-text-disabled)',
|
||||
[`& .${menuClasses.icon}`]: {
|
||||
color: 'inherit'
|
||||
}
|
||||
},
|
||||
[`&:not(.${menuClasses.subMenuRoot}) > .${menuClasses.button}.${menuClasses.active}`]: {
|
||||
color: 'var(--mui-palette-primary-contrastText)',
|
||||
background:
|
||||
theme.direction === 'ltr'
|
||||
? `linear-gradient(270deg, var(--mui-palette-primary-main), ${lighten(
|
||||
theme.palette.primary.main,
|
||||
0.5
|
||||
)} 100%)`
|
||||
: `linear-gradient(270deg, ${lighten(
|
||||
theme.palette.primary.main,
|
||||
0.5
|
||||
)}, var(--mui-palette-primary-main) 100%)`,
|
||||
[`& .${menuClasses.icon}`]: {
|
||||
color: 'inherit'
|
||||
}
|
||||
}
|
||||
},
|
||||
button: ({ active }) => ({
|
||||
paddingBlock: theme.spacing(2),
|
||||
'&:has(.MuiChip-root)': {
|
||||
paddingBlock: theme.spacing(1.75)
|
||||
},
|
||||
paddingInlineStart: theme.spacing(5.5),
|
||||
paddingInlineEnd: theme.spacing(3.5),
|
||||
borderStartEndRadius: 50,
|
||||
borderEndEndRadius: 50,
|
||||
...(!active && {
|
||||
'&:hover, &:focus-visible': {
|
||||
backgroundColor: 'var(--mui-palette-action-hover)'
|
||||
},
|
||||
'&[aria-expanded="true"]': {
|
||||
backgroundColor: 'var(--mui-palette-action-selected)'
|
||||
}
|
||||
})
|
||||
}),
|
||||
icon: ({ level }) => ({
|
||||
...(level === 0 && {
|
||||
fontSize: '1.375rem',
|
||||
marginInlineEnd: theme.spacing(2)
|
||||
}),
|
||||
...(level > 0 && {
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--mui-palette-text-secondary)',
|
||||
marginInlineEnd: theme.spacing(3.5)
|
||||
}),
|
||||
...(level === 1 && {
|
||||
marginInlineStart: theme.spacing(1.5)
|
||||
}),
|
||||
...(level > 1 && {
|
||||
marginInlineStart: theme.spacing(1.5 + 2.5 * (level - 1))
|
||||
}),
|
||||
'& > i, & > svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
}),
|
||||
prefix: {
|
||||
marginInlineEnd: theme.spacing(2)
|
||||
},
|
||||
suffix: {
|
||||
marginInlineStart: theme.spacing(2)
|
||||
},
|
||||
subMenuExpandIcon: {
|
||||
fontSize: '1.375rem',
|
||||
marginInlineStart: theme.spacing(2),
|
||||
'& i, & svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
},
|
||||
subMenuContent: {
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default menuItemStyles
|
||||
@@ -0,0 +1,42 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { MenuProps } from '@menu/vertical-menu'
|
||||
|
||||
// Util Imports
|
||||
import { menuClasses } from '@menu/utils/menuClasses'
|
||||
|
||||
const menuSectionStyles = (theme: Theme): MenuProps['menuSectionStyles'] => {
|
||||
return {
|
||||
root: {
|
||||
marginBlockStart: theme.spacing(7),
|
||||
[`& .${menuClasses.menuSectionContent}`]: {
|
||||
color: 'var(--mui-palette-text-disabled)',
|
||||
paddingInline: '0 !important',
|
||||
paddingBlock: `${theme.spacing(1.75)} !important`,
|
||||
gap: theme.spacing(2.5),
|
||||
|
||||
'&:before': {
|
||||
content: '""',
|
||||
blockSize: 1,
|
||||
inlineSize: '0.875rem',
|
||||
backgroundColor: 'var(--mui-palette-divider)'
|
||||
},
|
||||
'&:after': {
|
||||
content: '""',
|
||||
blockSize: 1,
|
||||
flexGrow: 1,
|
||||
backgroundColor: 'var(--mui-palette-divider)'
|
||||
}
|
||||
},
|
||||
[`& .${menuClasses.menuSectionLabel}`]: {
|
||||
flexGrow: 0,
|
||||
fontSize: '13px',
|
||||
lineHeight: 1.38462
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default menuSectionStyles
|
||||
@@ -0,0 +1,35 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Util Imports
|
||||
import { menuClasses, verticalNavClasses } from '@menu/utils/menuClasses'
|
||||
|
||||
const navigationCustomStyles = (theme: Theme) => {
|
||||
return {
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
zIndex: 'var(--drawer-z-index) !important',
|
||||
[`& .${verticalNavClasses.bgColorContainer}`]: {
|
||||
backgroundColor: 'var(--mui-palette-background-default)'
|
||||
},
|
||||
[`& .${verticalNavClasses.header}`]: {
|
||||
paddingBlock: theme.spacing(5),
|
||||
paddingInline: theme.spacing(5.5, 4)
|
||||
},
|
||||
[`& .${verticalNavClasses.container}`]: {
|
||||
transition: 'none',
|
||||
borderColor: 'transparent',
|
||||
[`& .${verticalNavClasses.toggled}`]: {
|
||||
boxShadow: 'var(--mui-customShadows-lg)'
|
||||
}
|
||||
},
|
||||
[`& .${menuClasses.root}`]: {
|
||||
paddingBlockEnd: theme.spacing(2),
|
||||
paddingInlineEnd: theme.spacing(4)
|
||||
},
|
||||
[`& .${verticalNavClasses.backdrop}`]: {
|
||||
backgroundColor: 'var(--backdrop-color)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default navigationCustomStyles
|
||||
@@ -0,0 +1,77 @@
|
||||
// React Imports
|
||||
import type { SVGAttributes } from 'react'
|
||||
|
||||
const Logo = (props: SVGAttributes<SVGElement>) => {
|
||||
return (
|
||||
<svg width='1.2658em' height='1em' viewBox='0 0 100 79' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M4.92008 0.501904L22.662 11.4573C23.614 12.0451 24.1936 13.0844 24.1936 14.2036V64.2521C24.1936 65.3871 23.5976 66.4387 22.6241 67.0214L4.8822 77.6429C3.35344 78.5581 1.37254 78.0602 0.457741 76.5307C0.158194 76.0299 0 75.4572 0 74.8736V3.24818C0 1.46582 1.44424 0.0209274 3.22581 0.0209274C3.82422 0.0209274 4.41085 0.18746 4.92008 0.501904Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
opacity='0.077704'
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M0 26.1063L24.1936 39.9852V53.5915L0 26.1063Z'
|
||||
fill='black'
|
||||
/>
|
||||
<path
|
||||
opacity='0.077704'
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M0 26.1063L24.1936 39.6319V47.9438L0 26.1063Z'
|
||||
fill='black'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M95.084 0.489601L77.3421 11.4083C76.3878 11.9956 75.8064 13.0362 75.8064 14.1571V64.2526C75.8064 65.3875 76.4024 66.4391 77.3759 67.0219L95.1178 77.6433C96.6466 78.5585 98.6275 78.0606 99.5423 76.5312C99.8418 76.0303 100 75.4576 100 74.874V3.23842C100 1.45605 98.5558 0.0111618 96.7742 0.0111618C96.1774 0.0111618 95.5923 0.176782 95.084 0.489601Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
opacity='0.077704'
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M100 26.1063L75.8064 39.956V54.0023L100 26.1063Z'
|
||||
fill='black'
|
||||
/>
|
||||
<path
|
||||
opacity='0.077704'
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M100 26.1063L75.8064 39.6199V48.3546L100 26.1063Z'
|
||||
fill='black'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M4.91149 0.475694L50 28.123V54.7479L0 26.0986V3.22726C0 1.44489 1.44424 0 3.22581 0C3.8208 0 4.4042 0.164633 4.91149 0.475694Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M4.91149 0.475694L50 28.123V54.7479L0 26.0986V3.22726C0 1.44489 1.44424 0 3.22581 0C3.8208 0 4.4042 0.164633 4.91149 0.475694Z'
|
||||
fill='white'
|
||||
fillOpacity='0.15'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M95.0885 0.475694L50 28.123V54.7479L100 26.0986V3.22726C100 1.44489 98.5558 0 96.7742 0C96.1792 0 95.5958 0.164633 95.0885 0.475694Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M95.0885 0.475694L50 28.123V54.7479L100 26.0986V3.22726C100 1.44489 98.5558 0 96.7742 0C96.1792 0 95.5958 0.164633 95.0885 0.475694Z'
|
||||
fill='white'
|
||||
fillOpacity='0.3'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo
|
||||
@@ -0,0 +1,74 @@
|
||||
import plugin from 'tailwindcss/plugin'
|
||||
|
||||
module.exports = plugin(function () {}, {
|
||||
theme: {
|
||||
borderColor: ({ theme }) => ({
|
||||
...theme('colors'),
|
||||
DEFAULT: 'var(--border-color, currentColor)'
|
||||
}),
|
||||
borderRadius: {
|
||||
none: '0px',
|
||||
xs: 'var(--mui-shape-customBorderRadius-xs)',
|
||||
sm: 'var(--mui-shape-customBorderRadius-sm)',
|
||||
DEFAULT: '0.375rem',
|
||||
md: 'var(--mui-shape-customBorderRadius-md)',
|
||||
lg: 'var(--mui-shape-customBorderRadius-lg)',
|
||||
xl: 'var(--mui-shape-customBorderRadius-xl)',
|
||||
'2xl': '0.75rem',
|
||||
'3xl': '1rem',
|
||||
'4xl': '1.5rem',
|
||||
full: '9999px'
|
||||
},
|
||||
screens: {
|
||||
sm: '600px',
|
||||
md: '900px',
|
||||
lg: '1200px',
|
||||
xl: '1536px',
|
||||
'2xl': '1920px'
|
||||
},
|
||||
extend: {
|
||||
boxShadow: {
|
||||
xs: 'var(--mui-customShadows-xs)',
|
||||
sm: 'var(--mui-customShadows-sm)',
|
||||
DEFAULT: 'var(--mui-customShadows-md)',
|
||||
md: 'var(--mui-customShadows-md)',
|
||||
lg: 'var(--mui-customShadows-lg)',
|
||||
xl: 'var(--mui-customShadows-xl)'
|
||||
},
|
||||
colors: {
|
||||
primary: 'var(--primary-color)',
|
||||
primaryLight: 'var(--mui-palette-primary-lightOpacity)',
|
||||
primaryLighter: 'var(--mui-palette-primary-lighterOpacity)',
|
||||
secondary: 'var(--mui-palette-secondary-main)',
|
||||
error: 'var(--mui-palette-error-main)',
|
||||
errorLight: 'var(--mui-palette-error-lightOpacity)',
|
||||
errorLighter: 'var(--mui-palette-error-lighterOpacity)',
|
||||
warning: 'var(--mui-palette-warning-main)',
|
||||
info: 'var(--mui-palette-info-main)',
|
||||
success: 'var(--mui-palette-success-main)',
|
||||
textPrimary: 'var(--mui-palette-text-primary)',
|
||||
textSecondary: 'var(--mui-palette-text-secondary)',
|
||||
textDisabled: 'var(--mui-palette-text-disabled)',
|
||||
actionActive: 'var(--mui-palette-action-active)',
|
||||
actionHover: 'var(--mui-palette-action-hover)',
|
||||
actionSelected: 'var(--mui-palette-action-selected)',
|
||||
actionFocus: 'var(--mui-palette-action-focus)',
|
||||
backgroundPaper: 'var(--mui-palette-background-paper)',
|
||||
backgroundDefault: 'var(--mui-palette-background-default)',
|
||||
track: 'var(--mui-palette-customColors-trackBg)',
|
||||
backdrop: 'var(--backdrop-color)',
|
||||
facebook: '#497ce2',
|
||||
twitter: '#1da1f2',
|
||||
github: '#272727',
|
||||
googlePlus: '#db4437'
|
||||
},
|
||||
zIndex: {
|
||||
header: 'var(--header-z-index)',
|
||||
footer: 'var(--footer-z-index)',
|
||||
customizer: 'var(--customizer-z-index)',
|
||||
search: 'var(--search-z-index)',
|
||||
drawer: 'var(--drawer-z-index)'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,329 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const colorSchemes = (): Theme['colorSchemes'] => {
|
||||
const skin = 'default' as string
|
||||
|
||||
return {
|
||||
light: {
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#8C57FF',
|
||||
light: '#A379FF',
|
||||
dark: '#7E4EE6',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.38)'
|
||||
},
|
||||
secondary: {
|
||||
main: '#8A8D93',
|
||||
light: '#A1A4A9',
|
||||
dark: '#7C7F84',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.38)'
|
||||
},
|
||||
error: {
|
||||
main: '#FF4C51',
|
||||
light: '#FF7074',
|
||||
dark: '#E64449',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.38)'
|
||||
},
|
||||
warning: {
|
||||
main: '#FFB400',
|
||||
light: '#FFC333',
|
||||
dark: '#E6A200',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.38)'
|
||||
},
|
||||
info: {
|
||||
main: '#16B1FF',
|
||||
light: '#45C1FF',
|
||||
dark: '#149FE6',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.38)'
|
||||
},
|
||||
success: {
|
||||
main: '#56CA00',
|
||||
light: '#78D533',
|
||||
dark: '#4DB600',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.38)'
|
||||
},
|
||||
text: {
|
||||
primary: `rgb(var(--mui-mainColorChannels-light) / 0.9)`,
|
||||
secondary: `rgb(var(--mui-mainColorChannels-light) / 0.7)`,
|
||||
disabled: `rgb(var(--mui-mainColorChannels-light) / 0.4)`,
|
||||
primaryChannel: 'var(--mui-mainColorChannels-light)',
|
||||
secondaryChannel: 'var(--mui-mainColorChannels-light)'
|
||||
},
|
||||
divider: `rgb(var(--mui-mainColorChannels-light) / 0.12)`,
|
||||
dividerChannel: 'var(--mui-mainColorChannels-light)',
|
||||
background: {
|
||||
default: skin === 'bordered' ? '#FFFFFF' : '#F4F5FA',
|
||||
paper: '#FFFFFF'
|
||||
},
|
||||
action: {
|
||||
active: `rgb(var(--mui-mainColorChannels-light) / 0.6)`,
|
||||
hover: `rgb(var(--mui-mainColorChannels-light) / 0.04)`,
|
||||
selected: `rgb(var(--mui-mainColorChannels-light) / 0.08)`,
|
||||
disabled: `rgb(var(--mui-mainColorChannels-light) / 0.3)`,
|
||||
disabledBackground: `rgb(var(--mui-mainColorChannels-light) / 0.12)`,
|
||||
focus: `rgb(var(--mui-mainColorChannels-light) / 0.1)`,
|
||||
focusOpacity: 0.1,
|
||||
activeChannel: 'var(--mui-mainColorChannels-light)',
|
||||
selectedChannel: 'var(--mui-mainColorChannels-light)'
|
||||
},
|
||||
Alert: {
|
||||
errorColor: 'var(--mui-palette-error-main)',
|
||||
warningColor: 'var(--mui-palette-warning-main)',
|
||||
infoColor: 'var(--mui-palette-info-main)',
|
||||
successColor: 'var(--mui-palette-success-main)',
|
||||
errorStandardBg: 'var(--mui-palette-error-lightOpacity)',
|
||||
warningStandardBg: 'var(--mui-palette-warning-lightOpacity)',
|
||||
infoStandardBg: 'var(--mui-palette-info-lightOpacity)',
|
||||
successStandardBg: 'var(--mui-palette-success-lightOpacity)',
|
||||
errorFilledColor: 'var(--mui-palette-error-contrastText)',
|
||||
warningFilledColor: 'var(--mui-palette-warning-contrastText)',
|
||||
infoFilledColor: 'var(--mui-palette-info-contrastText)',
|
||||
successFilledColor: 'var(--mui-palette-success-contrastText)',
|
||||
errorFilledBg: 'var(--mui-palette-error-main)',
|
||||
warningFilledBg: 'var(--mui-palette-warning-main)',
|
||||
infoFilledBg: 'var(--mui-palette-info-main)',
|
||||
successFilledBg: 'var(--mui-palette-success-main)'
|
||||
},
|
||||
Avatar: {
|
||||
defaultBg: '#F0EFF0'
|
||||
},
|
||||
Chip: {
|
||||
defaultBorder: 'var(--mui-palette-divider)'
|
||||
},
|
||||
FilledInput: {
|
||||
bg: `rgb(var(--mui-mainColorChannels-light) / 0.06)`,
|
||||
hoverBg: `rgb(var(--mui-mainColorChannels-light) / 0.08)`,
|
||||
disabledBg: `rgb(var(--mui-mainColorChannels-light) / 0.06)`
|
||||
},
|
||||
LinearProgress: {
|
||||
primaryBg: 'var(--mui-palette-primary-mainOpacity)',
|
||||
secondaryBg: 'var(--mui-palette-secondary-mainOpacity)',
|
||||
errorBg: 'var(--mui-palette-error-mainOpacity)',
|
||||
warningBg: 'var(--mui-palette-warning-mainOpacity)',
|
||||
infoBg: 'var(--mui-palette-info-mainOpacity)',
|
||||
successBg: 'var(--mui-palette-success-mainOpacity)'
|
||||
},
|
||||
SnackbarContent: {
|
||||
bg: '#1A0E33',
|
||||
color: 'var(--mui-palette-background-paper)'
|
||||
},
|
||||
Switch: {
|
||||
defaultColor: 'var(--mui-palette-common-white)',
|
||||
defaultDisabledColor: 'var(--mui-palette-common-white)',
|
||||
primaryDisabledColor: 'var(--mui-palette-common-white)',
|
||||
secondaryDisabledColor: 'var(--mui-palette-common-white)',
|
||||
errorDisabledColor: 'var(--mui-palette-common-white)',
|
||||
warningDisabledColor: 'var(--mui-palette-common-white)',
|
||||
infoDisabledColor: 'var(--mui-palette-common-white)',
|
||||
successDisabledColor: 'var(--mui-palette-common-white)'
|
||||
},
|
||||
Tooltip: {
|
||||
bg: '#1A0E33'
|
||||
},
|
||||
TableCell: {
|
||||
border: 'var(--mui-palette-divider)'
|
||||
},
|
||||
customColors: {
|
||||
bodyBg: '#F4F5FA',
|
||||
chatBg: '#F7F6FA',
|
||||
greyLightBg: '#FAFAFA',
|
||||
inputBorder: `rgb(var(--mui-mainColorChannels-light) / 0.22)`,
|
||||
tableHeaderBg: '#F6F7FB',
|
||||
tooltipText: '#FFFFFF',
|
||||
trackBg: '#F0F2F8'
|
||||
}
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#8C57FF',
|
||||
light: '#A379FF',
|
||||
dark: '#7E4EE6',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-primary-mainChannel) / 0.38)'
|
||||
},
|
||||
secondary: {
|
||||
main: '#8A8D93',
|
||||
light: '#A1A4A9',
|
||||
dark: '#7C7F84',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.38)'
|
||||
},
|
||||
error: {
|
||||
main: '#FF4C51',
|
||||
light: '#FF7074',
|
||||
dark: '#E64449',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-error-mainChannel) / 0.38)'
|
||||
},
|
||||
warning: {
|
||||
main: '#FFB400',
|
||||
light: '#FFC333',
|
||||
dark: '#E6A200',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-warning-mainChannel) / 0.38)'
|
||||
},
|
||||
info: {
|
||||
main: '#16B1FF',
|
||||
light: '#45C1FF',
|
||||
dark: '#149FE6',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-info-mainChannel) / 0.38)'
|
||||
},
|
||||
success: {
|
||||
main: '#56CA00',
|
||||
light: '#78D533',
|
||||
dark: '#4DB600',
|
||||
contrastText: '#fff',
|
||||
lighterOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.08)',
|
||||
lightOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.16)',
|
||||
mainOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.24)',
|
||||
darkOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.32)',
|
||||
darkerOpacity: 'rgb(var(--mui-palette-success-mainChannel) / 0.38)'
|
||||
},
|
||||
text: {
|
||||
primary: `rgb(var(--mui-mainColorChannels-dark) / 0.9)`,
|
||||
secondary: `rgb(var(--mui-mainColorChannels-dark) / 0.7)`,
|
||||
disabled: `rgb(var(--mui-mainColorChannels-dark) / 0.4)`,
|
||||
primaryChannel: 'var(--mui-mainColorChannels-dark)',
|
||||
secondaryChannel: 'var(--mui-mainColorChannels-dark)'
|
||||
},
|
||||
divider: `rgb(var(--mui-mainColorChannels-dark) / 0.12)`,
|
||||
dividerChannel: 'var(--mui-mainColorChannels-dark)',
|
||||
background: {
|
||||
default: skin === 'bordered' ? '#312D4B' : '#28243D',
|
||||
paper: '#312D4B'
|
||||
},
|
||||
action: {
|
||||
active: `rgb(var(--mui-mainColorChannels-dark) / 0.6)`,
|
||||
hover: `rgb(var(--mui-mainColorChannels-dark) / 0.04)`,
|
||||
selected: `rgb(var(--mui-mainColorChannels-dark) / 0.08)`,
|
||||
disabled: `rgb(var(--mui-mainColorChannels-dark) / 0.3)`,
|
||||
disabledBackground: `rgb(var(--mui-mainColorChannels-dark) / 0.12)`,
|
||||
focus: `rgb(var(--mui-mainColorChannels-dark) / 0.1)`,
|
||||
focusOpacity: 0.1,
|
||||
activeChannel: 'var(--mui-mainColorChannels-dark)',
|
||||
selectedChannel: 'var(--mui-mainColorChannels-dark)'
|
||||
},
|
||||
Alert: {
|
||||
errorColor: 'var(--mui-palette-error-main)',
|
||||
warningColor: 'var(--mui-palette-warning-main)',
|
||||
infoColor: 'var(--mui-palette-info-main)',
|
||||
successColor: 'var(--mui-palette-success-main)',
|
||||
errorStandardBg: 'var(--mui-palette-error-lightOpacity)',
|
||||
warningStandardBg: 'var(--mui-palette-warning-lightOpacity)',
|
||||
infoStandardBg: 'var(--mui-palette-info-lightOpacity)',
|
||||
successStandardBg: 'var(--mui-palette-success-lightOpacity)',
|
||||
errorFilledColor: 'var(--mui-palette-error-contrastText)',
|
||||
warningFilledColor: 'var(--mui-palette-warning-contrastText)',
|
||||
infoFilledColor: 'var(--mui-palette-info-contrastText)',
|
||||
successFilledColor: 'var(--mui-palette-success-contrastText)',
|
||||
errorFilledBg: 'var(--mui-palette-error-main)',
|
||||
warningFilledBg: 'var(--mui-palette-warning-main)',
|
||||
infoFilledBg: 'var(--mui-palette-info-main)',
|
||||
successFilledBg: 'var(--mui-palette-success-main)'
|
||||
},
|
||||
Avatar: {
|
||||
defaultBg: '#3F3B59'
|
||||
},
|
||||
Chip: {
|
||||
defaultBorder: 'var(--mui-palette-divider)'
|
||||
},
|
||||
FilledInput: {
|
||||
bg: `rgb(var(--mui-mainColorChannels-dark) / 0.06)`,
|
||||
hoverBg: `rgb(var(--mui-mainColorChannels-dark) / 0.08)`,
|
||||
disabledBg: `rgb(var(--mui-mainColorChannels-dark) / 0.06)`
|
||||
},
|
||||
LinearProgress: {
|
||||
primaryBg: 'var(--mui-palette-primary-mainOpacity)',
|
||||
secondaryBg: 'var(--mui-palette-secondary-mainOpacity)',
|
||||
errorBg: 'var(--mui-palette-error-mainOpacity)',
|
||||
warningBg: 'var(--mui-palette-warning-mainOpacity)',
|
||||
infoBg: 'var(--mui-palette-info-mainOpacity)',
|
||||
successBg: 'var(--mui-palette-success-mainOpacity)'
|
||||
},
|
||||
SnackbarContent: {
|
||||
bg: '#F7F4FF',
|
||||
color: 'var(--mui-palette-background-paper)'
|
||||
},
|
||||
Switch: {
|
||||
defaultColor: 'var(--mui-palette-common-white)',
|
||||
defaultDisabledColor: 'var(--mui-palette-common-white)',
|
||||
primaryDisabledColor: 'var(--mui-palette-common-white)',
|
||||
secondaryDisabledColor: 'var(--mui-palette-common-white)',
|
||||
errorDisabledColor: 'var(--mui-palette-common-white)',
|
||||
warningDisabledColor: 'var(--mui-palette-common-white)',
|
||||
infoDisabledColor: 'var(--mui-palette-common-white)',
|
||||
successDisabledColor: 'var(--mui-palette-common-white)'
|
||||
},
|
||||
Tooltip: {
|
||||
bg: '#F7F4FF'
|
||||
},
|
||||
TableCell: {
|
||||
border: 'var(--mui-palette-divider)'
|
||||
},
|
||||
customColors: {
|
||||
bodyBg: '#28243D',
|
||||
chatBg: '#373452',
|
||||
greyLightBg: '#373350',
|
||||
inputBorder: `rgb(var(--mui-mainColorChannels-dark) / 0.22)`,
|
||||
tableHeaderBg: '#3D3759',
|
||||
tooltipText: '#312D4B',
|
||||
trackBg: '#474360'
|
||||
}
|
||||
}
|
||||
}
|
||||
} as Theme['colorSchemes']
|
||||
}
|
||||
|
||||
export default colorSchemes
|
||||
@@ -0,0 +1,17 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { SystemMode } from '@core/types'
|
||||
|
||||
const customShadows = (mode: SystemMode): Theme['customShadows'] => {
|
||||
return {
|
||||
xs: `0px 2px 4px rgb(var(--mui-mainColorChannels-${mode}Shadow) / ${mode === 'light' ? 0.16 : 0.2})`,
|
||||
sm: `0px 3px 6px rgb(var(--mui-mainColorChannels-${mode}Shadow) / ${mode === 'light' ? 0.18 : 0.22})`,
|
||||
md: `0px 4px 10px rgb(var(--mui-mainColorChannels-${mode}Shadow) / ${mode === 'light' ? 0.2 : 0.24})`,
|
||||
lg: `0px 6px 16px rgb(var(--mui-mainColorChannels-${mode}Shadow) / ${mode === 'light' ? 0.22 : 0.26})`,
|
||||
xl: `0px 8px 28px rgb(var(--mui-mainColorChannels-${mode}Shadow) / ${mode === 'light' ? 0.24 : 0.28})`
|
||||
}
|
||||
}
|
||||
|
||||
export default customShadows
|
||||
@@ -0,0 +1,48 @@
|
||||
// Next Imports
|
||||
import { Inter } from 'next/font/google'
|
||||
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { SystemMode } from '@core/types'
|
||||
|
||||
// Theme Options Imports
|
||||
import overrides from './overrides'
|
||||
import colorSchemes from './colorSchemes'
|
||||
import spacing from './spacing'
|
||||
import shadows from './shadows'
|
||||
import customShadows from './customShadows'
|
||||
import typography from './typography'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'], weight: ['300', '400', '500', '600', '700', '800', '900'] })
|
||||
|
||||
const theme = (mode: SystemMode, direction: Theme['direction']): Theme => {
|
||||
return {
|
||||
direction,
|
||||
components: overrides(),
|
||||
colorSchemes: colorSchemes(),
|
||||
...spacing,
|
||||
shape: {
|
||||
borderRadius: 6,
|
||||
customBorderRadius: {
|
||||
xs: 2,
|
||||
sm: 4,
|
||||
md: 6,
|
||||
lg: 8,
|
||||
xl: 10
|
||||
}
|
||||
},
|
||||
shadows: shadows(mode),
|
||||
typography: typography(inter.style.fontFamily),
|
||||
customShadows: customShadows(mode),
|
||||
mainColorChannels: {
|
||||
light: '46 38 61',
|
||||
dark: '231 227 252',
|
||||
lightShadow: '46 38 61',
|
||||
darkShadow: '19 17 32'
|
||||
}
|
||||
} as Theme
|
||||
}
|
||||
|
||||
export default theme
|
||||
@@ -0,0 +1,90 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const accordion = (skin: Skin): Theme['components'] => ({
|
||||
MuiAccordion: {
|
||||
defaultProps: {
|
||||
...(skin === 'bordered' && {
|
||||
variant: 'outlined'
|
||||
})
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
transition: theme.transitions.create(['margin', 'border-radius', 'box-shadow']),
|
||||
...(skin !== 'bordered'
|
||||
? {
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
}
|
||||
: {
|
||||
'&:not(.Mui-expanded) + &:not(.Mui-expanded)': {
|
||||
borderBlockStart: 0
|
||||
},
|
||||
'&:not(.Mui-expanded):has(+ &:not(.Mui-expanded))': {
|
||||
borderBlockEnd: 0
|
||||
}
|
||||
}),
|
||||
'&:not(.Mui-expanded):has(+ .Mui-expanded)': {
|
||||
borderBottomLeftRadius: 'var(--mui-shape-borderRadius)',
|
||||
borderBottomRightRadius: 'var(--mui-shape-borderRadius)'
|
||||
},
|
||||
'&.Mui-expanded': {
|
||||
borderRadius: 'var(--mui-shape-borderRadius)',
|
||||
...(skin !== 'bordered' && {
|
||||
boxShadow: 'var(--mui-customShadows-md)'
|
||||
}),
|
||||
margin: theme.spacing(2, 0),
|
||||
'& + .MuiAccordion-root': {
|
||||
borderTopLeftRadius: 'var(--mui-shape-borderRadius)',
|
||||
borderTopRightRadius: 'var(--mui-shape-borderRadius)',
|
||||
'&:before': {
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiAccordionSummary: {
|
||||
defaultProps: {
|
||||
expandIcon: <i className='ri-arrow-down-s-line' />
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(3, 5),
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
'&.Mui-expanded': {
|
||||
minHeight: 48
|
||||
},
|
||||
'& .MuiTypography-root': {
|
||||
color: 'inherit',
|
||||
fontWeight: theme.typography.fontWeightMedium
|
||||
}
|
||||
}),
|
||||
content: {
|
||||
margin: '0 !important'
|
||||
},
|
||||
expandIconWrapper: {
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
fontSize: '1.25rem',
|
||||
'& i, & svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiAccordionDetails: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(0, 5, 5),
|
||||
'& .MuiTypography-root': {
|
||||
color: 'var(--mui-palette-text-secondary)'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default accordion
|
||||
@@ -0,0 +1,179 @@
|
||||
// React Imports
|
||||
import React from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const alerts: Theme['components'] = {
|
||||
MuiAlert: {
|
||||
defaultProps: {
|
||||
iconMapping: {
|
||||
error: <i className='ri-error-warning-line' />,
|
||||
warning: <i className='ri-alert-line' />,
|
||||
info: <i className='ri-information-line' />,
|
||||
success: <i className='ri-checkbox-circle-line' />
|
||||
}
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(4),
|
||||
gap: theme.spacing(4),
|
||||
...theme.typography.body1,
|
||||
'&:not(:has(.MuiAlertTitle-root))': {
|
||||
'& .MuiAlert-icon + .MuiAlert-message': {
|
||||
alignSelf: 'center'
|
||||
}
|
||||
}
|
||||
}),
|
||||
icon: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
minWidth: 30,
|
||||
height: 30,
|
||||
borderRadius: 'var(--mui-shape-borderRadius)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
'& i, & svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
},
|
||||
message: {
|
||||
padding: 0
|
||||
},
|
||||
action: {
|
||||
padding: 0,
|
||||
marginRight: 0
|
||||
}
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { variant: 'standard', severity: 'error' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-error-main)',
|
||||
color: 'var(--mui-palette-error-contrastText)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'standard', severity: 'warning' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-warning-main)',
|
||||
color: 'var(--mui-palette-warning-contrastText)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'standard', severity: 'info' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-info-main)',
|
||||
color: 'var(--mui-palette-info-contrastText)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'standard', severity: 'success' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-success-main)',
|
||||
color: 'var(--mui-palette-success-contrastText)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', severity: 'error' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-error-main)',
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-error-mainOpacity)',
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', severity: 'warning' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-warning-main)',
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-warning-mainOpacity)',
|
||||
color: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', severity: 'info' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-info-main)',
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-info-mainOpacity)',
|
||||
color: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', severity: 'success' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-success-main)',
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-success-mainOpacity)',
|
||||
color: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', severity: 'error' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-common-white)',
|
||||
color: 'var(--mui-palette-error-main)',
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', severity: 'warning' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-common-white)',
|
||||
color: 'var(--mui-palette-warning-main)',
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', severity: 'info' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-common-white)',
|
||||
color: 'var(--mui-palette-info-main)',
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', severity: 'success' },
|
||||
style: {
|
||||
'& .MuiAlert-icon': {
|
||||
backgroundColor: 'var(--mui-palette-common-white)',
|
||||
color: 'var(--mui-palette-success-main)',
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
MuiAlertTitle: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
...theme.typography.h5,
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing(1),
|
||||
color: 'inherit'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default alerts
|
||||
@@ -0,0 +1,76 @@
|
||||
// React Imports
|
||||
import React from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const autocomplete = (skin: Skin): Theme['components'] => ({
|
||||
MuiAutocomplete: {
|
||||
defaultProps: {
|
||||
...(skin === 'bordered' && {
|
||||
slotProps: {
|
||||
paper: {
|
||||
variant: 'outlined'
|
||||
}
|
||||
}
|
||||
}),
|
||||
ChipProps: {
|
||||
size: 'small'
|
||||
},
|
||||
popupIcon: <i className='ri-arrow-down-s-line' />
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiButtonBase-root.Mui-disabled i, & .MuiButtonBase-root.Mui-disabled svg': {
|
||||
color: 'var(--mui-palette-action-disabled)'
|
||||
},
|
||||
'& .MuiOutlinedInput-input': {
|
||||
height: '1.4375em'
|
||||
}
|
||||
},
|
||||
input: {
|
||||
'& + .MuiAutocomplete-endAdornment': {
|
||||
right: '1rem',
|
||||
'& i, & svg': {
|
||||
fontSize: '1.5rem',
|
||||
color: 'var(--mui-palette-text-primary)'
|
||||
},
|
||||
'& .MuiAutocomplete-clearIndicator': {
|
||||
padding: 2
|
||||
}
|
||||
},
|
||||
'&.MuiInputBase-inputSizeSmall + .MuiAutocomplete-endAdornment': {
|
||||
'& i, & svg': {
|
||||
fontSize: '1.375rem'
|
||||
}
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
...(skin !== 'bordered' && {
|
||||
boxShadow: 'var(--mui-customShadows-lg)',
|
||||
marginBlockStart: '0.125rem'
|
||||
})
|
||||
},
|
||||
listbox: ({ theme }) => ({
|
||||
'& .MuiAutocomplete-option': {
|
||||
padding: theme.spacing(2, 5),
|
||||
'&[aria-selected="true"]': {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
'&.Mui-focused, &.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-primary-mainOpacity)'
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .MuiAutocomplete-option.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-action-hover)'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default autocomplete
|
||||
@@ -0,0 +1,38 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const avatar: Theme['components'] = {
|
||||
MuiAvatarGroup: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
justifyContent: 'flex-end',
|
||||
'& .MuiAvatar-root': {
|
||||
borderColor: 'var(--mui-palette-background-paper)'
|
||||
},
|
||||
'&.pull-up .MuiAvatar-root': {
|
||||
cursor: 'pointer',
|
||||
transition: theme.transitions.create(['box-shadow', 'transform'], {
|
||||
easing: 'ease',
|
||||
duration: theme.transitions.duration.shorter
|
||||
}),
|
||||
'&:hover': {
|
||||
zIndex: 2,
|
||||
boxShadow: 'var(--mui-customShadows-md)',
|
||||
transform: 'translateY(-5px)'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiAvatar: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
lineHeight: 1.2
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default avatar
|
||||
@@ -0,0 +1,16 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const backdrop: Theme['components'] = {
|
||||
MuiBackdrop: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:not(.MuiBackdrop-invisible)': {
|
||||
backgroundColor: 'var(--backdrop-color)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default backdrop
|
||||
@@ -0,0 +1,19 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const badges: Theme['components'] = {
|
||||
MuiBadge: {
|
||||
styleOverrides: {
|
||||
standard: ({ theme }) => ({
|
||||
height: 22,
|
||||
minWidth: 22,
|
||||
borderRadius: 20,
|
||||
fontSize: theme.typography.subtitle2.fontSize,
|
||||
lineHeight: 1.07,
|
||||
padding: theme.spacing(1, 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default badges
|
||||
@@ -0,0 +1,29 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const breadcrumbs: Theme['components'] = {
|
||||
MuiBreadcrumbs: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& svg, & i': {
|
||||
fontSize: '1.25rem'
|
||||
},
|
||||
'& a': {
|
||||
textDecoration: 'none',
|
||||
color: 'var(--mui-palette-text-secondary)',
|
||||
'&:hover': {
|
||||
color: 'var(--mui-palette-text-primary)'
|
||||
}
|
||||
}
|
||||
},
|
||||
li: ({ theme }) => ({
|
||||
lineHeight: theme.typography.body1.lineHeight,
|
||||
'& > *:not(a)': {
|
||||
color: 'var(--mui-palette-text-primary)'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default breadcrumbs
|
||||
@@ -0,0 +1,73 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
const buttonGroup: Theme['components'] = {
|
||||
MuiButtonGroup: {
|
||||
defaultProps: {
|
||||
disableRipple: themeConfig.disableRipple
|
||||
},
|
||||
styleOverrides: {
|
||||
contained: ({ ownerState }) => ({
|
||||
boxShadow: 'var(--mui-customShadows-xs)',
|
||||
...(ownerState.disabled && {
|
||||
boxShadow: 'none'
|
||||
})
|
||||
})
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { variant: 'text', color: 'primary' },
|
||||
style: {
|
||||
'& .MuiButtonGroup-firstButton, & .MuiButtonGroup-middleButton': {
|
||||
borderColor: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'secondary' },
|
||||
style: {
|
||||
'& .MuiButtonGroup-firstButton, & .MuiButtonGroup-middleButton': {
|
||||
borderColor: 'var(--mui-palette-secondary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'error' },
|
||||
style: {
|
||||
'& .MuiButtonGroup-firstButton, & .MuiButtonGroup-middleButton': {
|
||||
borderColor: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'warning' },
|
||||
style: {
|
||||
'& .MuiButtonGroup-firstButton, & .MuiButtonGroup-middleButton': {
|
||||
borderColor: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'info' },
|
||||
style: {
|
||||
'& .MuiButtonGroup-firstButton, & .MuiButtonGroup-middleButton': {
|
||||
borderColor: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'success' },
|
||||
style: {
|
||||
'& .MuiButtonGroup-firstButton, & .MuiButtonGroup-middleButton': {
|
||||
borderColor: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default buttonGroup
|
||||
@@ -0,0 +1,377 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
const iconStyles = (size?: string) => ({
|
||||
'& > *:nth-of-type(1)': {
|
||||
...(size === 'small'
|
||||
? {
|
||||
fontSize: '14px'
|
||||
}
|
||||
: {
|
||||
...(size === 'medium'
|
||||
? {
|
||||
fontSize: '16px'
|
||||
}
|
||||
: {
|
||||
fontSize: '20px'
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const button: Theme['components'] = {
|
||||
MuiButtonBase: {
|
||||
defaultProps: {
|
||||
disableRipple: themeConfig.disableRipple
|
||||
}
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme, ownerState }) => ({
|
||||
...(ownerState.variant === 'text'
|
||||
? {
|
||||
...(ownerState.size === 'small' && {
|
||||
padding: theme.spacing(2, 2.5)
|
||||
}),
|
||||
...(ownerState.size === 'medium' && {
|
||||
padding: theme.spacing(2, 3.5)
|
||||
}),
|
||||
...(ownerState.size === 'large' && {
|
||||
padding: theme.spacing(2, 4.5)
|
||||
})
|
||||
}
|
||||
: {
|
||||
...(ownerState.variant === 'outlined'
|
||||
? {
|
||||
...(ownerState.size === 'small' && {
|
||||
padding: theme.spacing(1.75, 3.25)
|
||||
}),
|
||||
...(ownerState.size === 'medium' && {
|
||||
padding: theme.spacing(1.75, 4.25)
|
||||
}),
|
||||
...(ownerState.size === 'large' && {
|
||||
padding: theme.spacing(1.75, 5.25)
|
||||
})
|
||||
}
|
||||
: {
|
||||
...(ownerState.size === 'small' && {
|
||||
padding: theme.spacing(2, 3.5)
|
||||
}),
|
||||
...(ownerState.size === 'medium' && {
|
||||
padding: theme.spacing(2, 4.5)
|
||||
}),
|
||||
...(ownerState.size === 'large' && {
|
||||
padding: theme.spacing(2, 5.5)
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
contained: ({ ownerState }) => ({
|
||||
boxShadow: 'var(--mui-customShadows-xs)',
|
||||
...(!ownerState.disabled && {
|
||||
'&:hover, &.Mui-focusVisible': {
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
},
|
||||
'&:active': {
|
||||
boxShadow: 'none'
|
||||
}
|
||||
})
|
||||
}),
|
||||
sizeSmall: ({ theme }) => ({
|
||||
lineHeight: 1.38462,
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-sm)'
|
||||
}),
|
||||
sizeLarge: {
|
||||
fontSize: '1.0625rem',
|
||||
lineHeight: 1.529412,
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-lg)'
|
||||
},
|
||||
startIcon: ({ theme, ownerState }) => ({
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
marginInlineEnd: theme.spacing(1.5)
|
||||
}
|
||||
: {
|
||||
...(ownerState.size === 'medium'
|
||||
? {
|
||||
marginInlineEnd: theme.spacing(2)
|
||||
}
|
||||
: {
|
||||
marginInlineEnd: theme.spacing(2.5)
|
||||
})
|
||||
}),
|
||||
...iconStyles(ownerState.size)
|
||||
}),
|
||||
endIcon: ({ theme, ownerState }) => ({
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
marginInlineStart: theme.spacing(1.5)
|
||||
}
|
||||
: {
|
||||
...(ownerState.size === 'medium'
|
||||
? {
|
||||
marginInlineStart: theme.spacing(2)
|
||||
}
|
||||
: {
|
||||
marginInlineStart: theme.spacing(2.5)
|
||||
})
|
||||
}),
|
||||
...iconStyles(ownerState.size)
|
||||
})
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { variant: 'text', color: 'primary' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-primary-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'secondary' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-secondary-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-secondary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'error' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-error-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'warning' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-warning-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'info' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-info-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'success' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-success-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'primary' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-primary-main)',
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-primary-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
borderColor: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'secondary' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-secondary-main)',
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-secondary-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-secondary-main)',
|
||||
borderColor: 'var(--mui-palette-secondary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'error' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-error-main)',
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-error-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-error-main)',
|
||||
borderColor: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'warning' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-warning-main)',
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-warning-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-warning-main)',
|
||||
borderColor: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'info' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-info-main)',
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-info-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-info-main)',
|
||||
borderColor: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'success' },
|
||||
style: {
|
||||
borderColor: 'var(--mui-palette-success-main)',
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))':
|
||||
{
|
||||
backgroundColor: 'var(--mui-palette-success-lighterOpacity)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-success-main)',
|
||||
borderColor: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'contained', color: 'primary' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-primary-dark)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-primary-contrastText)',
|
||||
backgroundColor: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'contained', color: 'secondary' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-dark)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-secondary-contrastText)',
|
||||
backgroundColor: 'var(--mui-palette-secondary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'contained', color: 'error' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-error-dark)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-error-contrastText)',
|
||||
backgroundColor: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'contained', color: 'warning' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-warning-dark)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-warning-contrastText)',
|
||||
backgroundColor: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'contained', color: 'info' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-info-dark)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-info-contrastText)',
|
||||
backgroundColor: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'contained', color: 'success' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):active, &.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-success-dark)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-success-contrastText)',
|
||||
backgroundColor: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default button
|
||||
@@ -0,0 +1,91 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const card = (skin: Skin): Theme['components'] => {
|
||||
return {
|
||||
MuiCard: {
|
||||
defaultProps: {
|
||||
...(skin === 'bordered' && {
|
||||
variant: 'outlined'
|
||||
})
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ ownerState }) => ({
|
||||
...(ownerState.variant !== 'outlined' && {
|
||||
boxShadow: 'var(--mui-customShadows-md)'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiCardHeader: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(5),
|
||||
'& + .MuiCardContent-root, & + .MuiCardActions-root': {
|
||||
paddingBlockStart: 0
|
||||
},
|
||||
'& + .MuiCollapse-root .MuiCardContent-root:first-child, & + .MuiCollapse-root .MuiCardActions-root:first-child':
|
||||
{
|
||||
paddingBlockStart: 0
|
||||
}
|
||||
}),
|
||||
subheader: ({ theme }) => ({
|
||||
...theme.typography.subtitle1,
|
||||
color: 'rgb(var(--mui-palette-text-primaryChannel) / 0.55)'
|
||||
}),
|
||||
action: ({ theme }) => ({
|
||||
...theme.typography.body1,
|
||||
color: 'var(--mui-palette-text-disabled)',
|
||||
marginBlock: 0,
|
||||
marginInlineEnd: 0,
|
||||
'& .MuiIconButton-root': {
|
||||
color: 'inherit'
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiCardContent: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(5),
|
||||
color: 'var(--mui-palette-text-secondary)',
|
||||
'&:last-child': {
|
||||
paddingBlockEnd: theme.spacing(5)
|
||||
},
|
||||
'& + .MuiCardHeader-root, & + .MuiCardContent-root, & + .MuiCardActions-root': {
|
||||
paddingBlockStart: 0
|
||||
},
|
||||
'& + .MuiCollapse-root .MuiCardHeader-root:first-child, & + .MuiCollapse-root .MuiCardContent-root:first-child, & + .MuiCollapse-root .MuiCardActions-root:first-child':
|
||||
{
|
||||
paddingBlockStart: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiCardActions: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(5),
|
||||
'&:where(.card-actions-dense)': {
|
||||
padding: theme.spacing(2.5),
|
||||
'& .MuiButton-text': {
|
||||
paddingInline: theme.spacing(2.5)
|
||||
}
|
||||
},
|
||||
'& + .MuiCardHeader-root, & + .MuiCardContent-root, & + .MuiCardActions-root': {
|
||||
paddingBlockStart: 0
|
||||
},
|
||||
'& + .MuiCollapse-root .MuiCardHeader-root:first-child, & + .MuiCollapse-root .MuiCardContent-root:first-child, & + .MuiCollapse-root .MuiCardActions-root:first-child':
|
||||
{
|
||||
paddingBlockStart: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default card
|
||||
@@ -0,0 +1,94 @@
|
||||
// React Imports
|
||||
import React from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const Icon = () => {
|
||||
return (
|
||||
<svg width='1em' height='1em' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M8 4h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z'
|
||||
stroke='var(--mui-palette-text-secondary)'
|
||||
strokeWidth='2'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const IndeterminateIcon = () => {
|
||||
return (
|
||||
<svg width='1em' height='1em' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path d='M3 8a5 5 0 0 1 5-5h8a5 5 0 0 1 5 5v8a5 5 0 0 1-5 5H8a5 5 0 0 1-5-5V8Z' fill='currentColor' />
|
||||
<path d='M8.5 11.5h7v1h-7v-1Z' fill='var(--mui-palette-common-white)' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const CheckedIcon = () => {
|
||||
return (
|
||||
<svg width='1em' height='1em' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path d='M3 8a5 5 0 0 1 5-5h8a5 5 0 0 1 5 5v8a5 5 0 0 1-5 5H8a5 5 0 0 1-5-5V8Z' fill='currentColor' />
|
||||
<path
|
||||
d='m11 13.586 4.596-4.597.707.707L11 15l-3.182-3.182.707-.707L11 13.586Z'
|
||||
fill='var(--mui-palette-common-white)'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const checkbox: Theme['components'] = {
|
||||
MuiCheckbox: {
|
||||
defaultProps: {
|
||||
icon: <Icon />,
|
||||
indeterminateIcon: <IndeterminateIcon />,
|
||||
checkedIcon: <CheckedIcon />
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme, ownerState }) => ({
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
padding: theme.spacing(1),
|
||||
'& svg': {
|
||||
fontSize: '1.25rem'
|
||||
}
|
||||
}
|
||||
: {
|
||||
padding: theme.spacing(1.5),
|
||||
'& svg': {
|
||||
fontSize: '1.5rem'
|
||||
}
|
||||
}),
|
||||
'&.Mui-checked:not(.Mui-disabled) svg': {
|
||||
filter: 'drop-shadow(var(--mui-customShadows-xs))'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
'&:not(.Mui-checked)': {
|
||||
color: 'var(--mui-palette-text-secondary)'
|
||||
},
|
||||
'&.Mui-checked.MuiCheckbox-colorPrimary': {
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiCheckbox-colorSecondary': {
|
||||
color: 'var(--mui-palette-secondary-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiCheckbox-colorError': {
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiCheckbox-colorWarning': {
|
||||
color: 'var(--mui-palette-warning-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiCheckbox-colorInfo': {
|
||||
color: 'var(--mui-palette-info-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiCheckbox-colorSuccess': {
|
||||
color: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default checkbox
|
||||
@@ -0,0 +1,190 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const chip: Theme['components'] = {
|
||||
MuiChip: {
|
||||
variants: [
|
||||
{
|
||||
props: { variant: 'tonal', color: 'primary' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
'&.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-primary-mainOpacity)'
|
||||
},
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: 'rgb(var(--mui-palette-primary-mainChannel) / 0.7)',
|
||||
'&:hover': {
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
},
|
||||
'&.MuiChip-clickable:hover': {
|
||||
backgroundColor: 'var(--mui-palette-primary-main)',
|
||||
color: 'var(--mui-palette-common-white)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'secondary' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-secondary-lightOpacity)',
|
||||
color: 'var(--mui-palette-secondary-main)',
|
||||
'&.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-mainOpacity)'
|
||||
},
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: 'rgb(var(--mui-palette-secondary-mainChannel) / 0.7)',
|
||||
'&:hover': {
|
||||
color: 'var(--mui-palette-secondary-main)'
|
||||
}
|
||||
},
|
||||
'&.MuiChip-clickable:hover': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-main)',
|
||||
color: 'var(--mui-palette-common-white)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'error' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-error-lightOpacity)',
|
||||
color: 'var(--mui-palette-error-main)',
|
||||
'&.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-error-mainOpacity)'
|
||||
},
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: 'rgb(var(--mui-palette-error-mainChannel) / 0.7)',
|
||||
'&:hover': {
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
},
|
||||
'&.MuiChip-clickable:hover': {
|
||||
backgroundColor: 'var(--mui-palette-error-main)',
|
||||
color: 'var(--mui-palette-common-white)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'warning' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-warning-lightOpacity)',
|
||||
color: 'var(--mui-palette-warning-main)',
|
||||
'&.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-warning-mainOpacity)'
|
||||
},
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: 'rgb(var(--mui-palette-warning-mainChannel) / 0.7)',
|
||||
'&:hover': {
|
||||
color: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
},
|
||||
'&.MuiChip-clickable:hover': {
|
||||
backgroundColor: 'var(--mui-palette-warning-main)',
|
||||
color: 'var(--mui-palette-common-white)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'info' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-info-lightOpacity)',
|
||||
color: 'var(--mui-palette-info-main)',
|
||||
'&.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-info-mainOpacity)'
|
||||
},
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: 'rgb(var(--mui-palette-info-mainChannel) / 0.7)',
|
||||
'&:hover': {
|
||||
color: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
},
|
||||
'&.MuiChip-clickable:hover': {
|
||||
backgroundColor: 'var(--mui-palette-info-main)',
|
||||
color: 'var(--mui-palette-common-white)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'success' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-success-lightOpacity)',
|
||||
color: 'var(--mui-palette-success-main)',
|
||||
'&.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-success-mainOpacity)'
|
||||
},
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: 'rgb(var(--mui-palette-success-mainChannel) / 0.7)',
|
||||
'&:hover': {
|
||||
color: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
},
|
||||
'&.MuiChip-clickable:hover': {
|
||||
backgroundColor: 'var(--mui-palette-success-main)',
|
||||
color: 'var(--mui-palette-common-white)'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
styleOverrides: {
|
||||
root: ({ ownerState, theme }) => ({
|
||||
...theme.typography.body2,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
|
||||
'& .MuiChip-deleteIcon': {
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
fontSize: '1rem',
|
||||
marginInlineEnd: theme.spacing(1),
|
||||
marginInlineStart: theme.spacing(-2)
|
||||
}
|
||||
: {
|
||||
fontSize: '1.25rem',
|
||||
marginInlineEnd: theme.spacing(2),
|
||||
marginInlineStart: theme.spacing(-3)
|
||||
})
|
||||
},
|
||||
'& .MuiChip-avatar, & .MuiChip-icon': {
|
||||
'& i, & svg': {
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
fontSize: 13
|
||||
}
|
||||
: {
|
||||
fontSize: 15
|
||||
})
|
||||
},
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
height: 16,
|
||||
width: 16,
|
||||
marginInlineStart: theme.spacing(1),
|
||||
marginInlineEnd: theme.spacing(-2)
|
||||
}
|
||||
: {
|
||||
height: 20,
|
||||
width: 20,
|
||||
marginInlineStart: theme.spacing(2),
|
||||
marginInlineEnd: theme.spacing(-3)
|
||||
})
|
||||
}
|
||||
}),
|
||||
label: ({ ownerState, theme }) => ({
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
paddingInline: theme.spacing(3)
|
||||
}
|
||||
: {
|
||||
paddingInline: theme.spacing(4)
|
||||
})
|
||||
}),
|
||||
iconMedium: {
|
||||
fontSize: '1.25rem'
|
||||
},
|
||||
iconSmall: {
|
||||
fontSize: '1rem'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default chip
|
||||
@@ -0,0 +1,67 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const dialog = (skin: Skin): Theme['components'] => ({
|
||||
MuiDialog: {
|
||||
styleOverrides: {
|
||||
paper: ({ theme }) => ({
|
||||
...(skin !== 'bordered'
|
||||
? {
|
||||
boxShadow: 'var(--mui-customShadows-xl)'
|
||||
}
|
||||
: {
|
||||
boxShadow: 'none'
|
||||
}),
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
'&:not(.MuiDialog-paperFullScreen)': {
|
||||
margin: theme.spacing(6)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiDialogTitle: {
|
||||
defaultProps: {
|
||||
variant: 'h5'
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(5),
|
||||
'& + .MuiDialogActions-root': {
|
||||
paddingTop: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiDialogContent: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(5),
|
||||
'& + .MuiDialogContent-root, & + .MuiDialogActions-root': {
|
||||
paddingTop: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiDialogActions: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(5),
|
||||
'& .MuiButtonBase-root:not(:first-of-type)': {
|
||||
marginInlineStart: theme.spacing(4)
|
||||
},
|
||||
'&:where(.dialog-actions-dense)': {
|
||||
padding: theme.spacing(2.5),
|
||||
'& .MuiButton-text': {
|
||||
paddingInline: theme.spacing(2.5)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default dialog
|
||||
@@ -0,0 +1,26 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const drawer = (skin: Skin): Theme['components'] => ({
|
||||
MuiDrawer: {
|
||||
defaultProps: {
|
||||
...(skin === 'bordered' && {
|
||||
PaperProps: {
|
||||
elevation: 0
|
||||
}
|
||||
})
|
||||
},
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
...(skin !== 'bordered' && {
|
||||
boxShadow: 'var(--mui-customShadows-lg)'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default drawer
|
||||
@@ -0,0 +1,68 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const fab: Theme['components'] = {
|
||||
MuiFab: {
|
||||
variants: [
|
||||
{
|
||||
props: { color: 'default' },
|
||||
style: {
|
||||
color: 'rgb(var(--mui-mainColorChannels-light) / 0.9)',
|
||||
'&.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-grey-A100)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'primary' },
|
||||
style: {
|
||||
'&.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-primary-dark)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'secondary' },
|
||||
style: {
|
||||
'&.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-dark)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'error' },
|
||||
style: {
|
||||
'&.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-error-dark)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'warning' },
|
||||
style: {
|
||||
'&.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-warning-dark)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'info' },
|
||||
style: {
|
||||
'&.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-info-dark)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'success' },
|
||||
style: {
|
||||
'&.Mui-focusVisible:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'var(--mui-palette-success-dark)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default fab
|
||||
@@ -0,0 +1,22 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const formControlLabel: Theme['components'] = {
|
||||
MuiFormControlLabel: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
marginInlineStart: theme.spacing(-2)
|
||||
}),
|
||||
label: {
|
||||
'&, &.Mui-disabled': {
|
||||
color: 'var(--mui-palette-text-primary)'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default formControlLabel
|
||||
@@ -0,0 +1,140 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
const iconButton: Theme['components'] = {
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiSvgIcon-root, & i, & svg': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
},
|
||||
sizeSmall: ({ theme }) => ({
|
||||
padding: theme.spacing(1.75),
|
||||
fontSize: '1.25rem'
|
||||
}),
|
||||
sizeMedium: ({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
fontSize: '1.375rem'
|
||||
}),
|
||||
sizeLarge: ({ theme }) => ({
|
||||
padding: theme.spacing(2.25),
|
||||
fontSize: '1.5rem'
|
||||
})
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { color: 'default' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'rgb(var(--mui-palette-text-primaryChannel) / 0.08)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': {
|
||||
backgroundColor: 'rgb(var(--mui-palette-text-primaryChannel) / 0.08)'
|
||||
}
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-action-active)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'primary' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'var(--mui-palette-primary-lighterOpacity)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': { backgroundColor: 'var(--mui-palette-primary-lighterOpacity)' }
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'secondary' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-lighterOpacity)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': { backgroundColor: 'var(--mui-palette-secondary-lighterOpacity)' }
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-secondary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'error' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'var(--mui-palette-error-lighterOpacity)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': { backgroundColor: 'var(--mui-palette-error-lighterOpacity)' }
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'warning' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'var(--mui-palette-warning-lighterOpacity)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': { backgroundColor: 'var(--mui-palette-warning-lighterOpacity)' }
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'info' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'var(--mui-palette-info-lighterOpacity)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': { backgroundColor: 'var(--mui-palette-info-lighterOpacity)' }
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { color: 'success' },
|
||||
style: {
|
||||
'&:not(.Mui-disabled):hover, &:not(.Mui-disabled):active': {
|
||||
backgroundColor: 'var(--mui-palette-success-lighterOpacity)'
|
||||
},
|
||||
...(themeConfig.disableRipple && {
|
||||
'&.Mui-focusVisible:not(.Mui-disabled)': { backgroundColor: 'var(--mui-palette-success-lighterOpacity)' }
|
||||
}),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default iconButton
|
||||
@@ -0,0 +1,83 @@
|
||||
// Override Imports
|
||||
import Accordion from './accordion'
|
||||
import Alerts from './alerts'
|
||||
import Autocomplete from './autocomplete'
|
||||
import avatar from './avatar'
|
||||
import backdrop from './backdrop'
|
||||
import badges from './badges'
|
||||
import breadcrumbs from './breadcrumbs'
|
||||
import button from './button'
|
||||
import buttonGroup from './button-group'
|
||||
import card from './card'
|
||||
import Checkbox from './checkbox'
|
||||
import chip from './chip'
|
||||
import dialog from './dialog'
|
||||
import drawer from './drawer'
|
||||
import fab from './fab'
|
||||
import formControlLabel from './form-control-label'
|
||||
import iconButton from './icon-button'
|
||||
import input from './input'
|
||||
import list from './list'
|
||||
import menu from './menu'
|
||||
import pagination from './pagination'
|
||||
import paper from './paper'
|
||||
import popover from './popover'
|
||||
import progress from './progress'
|
||||
import Radio from './radio'
|
||||
import Rating from './rating'
|
||||
import Select from './select'
|
||||
import slider from './slider'
|
||||
import snackbar from './snackbar'
|
||||
import switchOverrides from './switch'
|
||||
import tablePagination from './table-pagination'
|
||||
import tabs from './tabs'
|
||||
import timeline from './timeline'
|
||||
import toggleButton from './toggle-button'
|
||||
import tooltip from './tooltip'
|
||||
import typography from './typography'
|
||||
|
||||
const overrides = () => {
|
||||
const skin = 'default'
|
||||
|
||||
return Object.assign(
|
||||
{},
|
||||
Accordion(skin),
|
||||
Alerts,
|
||||
Autocomplete(skin),
|
||||
avatar,
|
||||
backdrop,
|
||||
badges,
|
||||
breadcrumbs,
|
||||
button,
|
||||
buttonGroup,
|
||||
card(skin),
|
||||
Checkbox,
|
||||
chip,
|
||||
dialog(skin),
|
||||
drawer(skin),
|
||||
fab,
|
||||
formControlLabel,
|
||||
iconButton,
|
||||
input,
|
||||
list,
|
||||
menu(skin),
|
||||
pagination,
|
||||
paper,
|
||||
popover(skin),
|
||||
progress,
|
||||
Radio,
|
||||
Rating,
|
||||
Select,
|
||||
slider,
|
||||
snackbar(skin),
|
||||
switchOverrides,
|
||||
tablePagination,
|
||||
tabs,
|
||||
timeline,
|
||||
toggleButton,
|
||||
tooltip,
|
||||
typography
|
||||
)
|
||||
}
|
||||
|
||||
export default overrides
|
||||
@@ -0,0 +1,112 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const input: Theme['components'] = {
|
||||
MuiFormControl: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:has(.MuiRadio-root) .MuiFormHelperText-root, &:has(.MuiCheckbox-root) .MuiFormHelperText-root, &:has(.MuiSwitch-root) .MuiFormHelperText-root':
|
||||
{
|
||||
marginInline: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputBase: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
lineHeight: 1.6,
|
||||
'&.MuiInput-underline': {
|
||||
'&:before': {
|
||||
borderColor: 'var(--mui-palette-customColors-inputBorder)'
|
||||
},
|
||||
'&:not(.Mui-disabled, .Mui-error):hover:before': {
|
||||
borderColor: 'var(--mui-palette-action-active)'
|
||||
}
|
||||
},
|
||||
'&.Mui-disabled .MuiInputAdornment-root, &.Mui-disabled .MuiInputAdornment-root > *': {
|
||||
color: 'var(--mui-palette-action-disabled)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiFilledInput: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:before': {
|
||||
borderBottom: '1px solid var(--mui-palette-text-secondary)'
|
||||
},
|
||||
'&.Mui-disabled:before': {
|
||||
borderBottomStyle: 'solid'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputLabel: {
|
||||
styleOverrides: {
|
||||
shrink: ({ ownerState }) => ({
|
||||
...(ownerState.variant === 'outlined' && {
|
||||
color: 'var(--mui-palette-text-secondary)',
|
||||
transform: 'translate(14px, -8px) scale(0.867)'
|
||||
}),
|
||||
...(ownerState.variant === 'filled' && {
|
||||
transform: 'translate(12px, 7px) scale(0.867)'
|
||||
}),
|
||||
...(ownerState.variant === 'standard' && {
|
||||
transform: 'translate(0, -1.5px) scale(0.867)'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:not(.Mui-focused):not(.Mui-error):not(.Mui-disabled):hover .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--mui-palette-action-active)'
|
||||
},
|
||||
'&.Mui-disabled .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--mui-palette-action-disabledBackground)'
|
||||
}
|
||||
},
|
||||
input: ({ theme, ownerState }) => ({
|
||||
...(ownerState?.size === 'medium' && {
|
||||
'&:not(.MuiInputBase-inputMultiline, .MuiInputBase-inputAdornedStart)': {
|
||||
paddingBlock: theme.spacing(4)
|
||||
},
|
||||
height: '1.5em'
|
||||
}),
|
||||
'& ~ .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--mui-palette-customColors-inputBorder)'
|
||||
}
|
||||
}),
|
||||
notchedOutline: {
|
||||
'& legend': {
|
||||
fontSize: '0.867em'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputAdornment: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
'& i, & svg': {
|
||||
fontSize: '1.25rem'
|
||||
},
|
||||
'& *': {
|
||||
color: 'inherit !important'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiFormHelperText: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
lineHeight: 1,
|
||||
letterSpacing: 'unset'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default input
|
||||
@@ -0,0 +1,81 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const list: Theme['components'] = {
|
||||
MuiListItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
gap: theme.spacing(4)
|
||||
}),
|
||||
padding: ({ theme, ownerState }) => ({
|
||||
...(!ownerState.dense && {
|
||||
paddingBlock: theme.spacing(2),
|
||||
paddingInlineStart: theme.spacing(5)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiListItemAvatar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
minWidth: 'unset'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiListItemIcon: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
minWidth: 0,
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
fontSize: '1.375rem',
|
||||
'& > svg, & > i': {
|
||||
fontSize: 'inherit'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiListItemButton: {
|
||||
styleOverrides: {
|
||||
root: ({ theme, ownerState }) => ({
|
||||
gap: theme.spacing(4),
|
||||
...(!ownerState.dense && {
|
||||
paddingBlock: theme.spacing(2)
|
||||
}),
|
||||
paddingInlineStart: theme.spacing(5),
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
'&:hover, &.Mui-focused, &.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-primary-mainOpacity)'
|
||||
},
|
||||
'& .MuiTypography-root': {
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
},
|
||||
'& + .MuiListItemSecondaryAction-root .MuiIconButton-root': {
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiListItemText: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
margin: 0
|
||||
},
|
||||
primary: {
|
||||
color: 'var(--mui-palette-text-primary)'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiListSubheader: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
...theme.typography.subtitle2,
|
||||
paddingBlock: 10,
|
||||
paddingInline: theme.spacing(5)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default list
|
||||
@@ -0,0 +1,57 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const menu = (skin: Skin): Theme['components'] => ({
|
||||
MuiMenu: {
|
||||
defaultProps: {
|
||||
...(skin === 'bordered' && {
|
||||
slotProps: {
|
||||
paper: {
|
||||
elevation: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
styleOverrides: {
|
||||
paper: ({ theme }) => ({
|
||||
marginBlockStart: theme.spacing(0.5),
|
||||
...(skin !== 'bordered' && {
|
||||
boxShadow: 'var(--mui-customShadows-lg)'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiMenuItem: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
paddingBlock: theme.spacing(2),
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
'& i, & svg': {
|
||||
fontSize: '1.375rem'
|
||||
},
|
||||
'& .MuiListItemIcon-root': {
|
||||
minInlineSize: 0
|
||||
},
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
'& .MuiListItemIcon-root': {
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
},
|
||||
'&:hover, &.Mui-focused, &.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--mui-palette-primary-mainOpacity)'
|
||||
}
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
color: 'var(--mui-palette-text-disabled)',
|
||||
opacity: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default menu
|
||||
@@ -0,0 +1,172 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const pagination: Theme['components'] = {
|
||||
MuiPagination: {
|
||||
styleOverrides: {
|
||||
ul: {
|
||||
rowGap: 6
|
||||
}
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { variant: 'text', color: 'primary' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
backgroundColor: 'var(--mui-palette-primary-main)',
|
||||
color: 'var(--mui-palette-primary-contrastText)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'text', color: 'secondary' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-main)',
|
||||
color: 'var(--mui-palette-secondary-contrastText)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'standard' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
borderColor: 'var(--mui-palette-action-selected)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'primary' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
backgroundColor: 'rgb(var(--mui-palette-primary-mainChannel) / var(--mui-palette-action-activatedOpacity))',
|
||||
borderColor: 'rgba(var(--mui-palette-primary-mainChannel) / 0.5)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'outlined', color: 'secondary' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
color: 'var(--mui-palette-secondary-main)',
|
||||
backgroundColor:
|
||||
'rgb(var(--mui-palette-secondary-mainChannel) / var(--mui-palette-action-activatedOpacity))',
|
||||
borderColor: 'rgba(var(--mui-palette-secondary-mainChannel) / 0.5)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root:not(.MuiPaginationItem-ellipsis)': {
|
||||
backgroundColor: 'var(--mui-palette-action-selected)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'standard' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected': {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--mui-palette-primary-mainOpacity)'
|
||||
}
|
||||
},
|
||||
'& .MuiPaginationItem-root:hover:not(.Mui-selected):not(.MuiPaginationItem-ellipsis)': {
|
||||
backgroundColor: 'var(--mui-palette-action-focus)'
|
||||
},
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'primary' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected': {
|
||||
backgroundColor: 'var(--mui-palette-primary-main)',
|
||||
color: 'var(--mui-palette-primary-contrastText)',
|
||||
'&:not(.Mui-disabled)': {
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--mui-palette-primary-dark)'
|
||||
}
|
||||
},
|
||||
'& .MuiPaginationItem-root:hover:not(.Mui-selected):not(.MuiPaginationItem-ellipsis)': {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
},
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
backgroundColor: 'var(--mui-palette-primary-main)',
|
||||
color: 'var(--mui-palette-primary-contrastText)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'secondary' },
|
||||
style: {
|
||||
'& .MuiPaginationItem-root.Mui-selected': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-main)',
|
||||
color: 'var(--mui-palette-secondary-contrastText)',
|
||||
'&:not(.Mui-disabled)': {
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-dark)'
|
||||
}
|
||||
},
|
||||
'& .MuiPaginationItem-root:hover:not(.Mui-selected):not(.MuiPaginationItem-ellipsis)': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-mainOpacity)',
|
||||
color: 'var(--mui-palette-secondary-main)'
|
||||
},
|
||||
'& .MuiPaginationItem-root.Mui-selected.Mui-disabled': {
|
||||
backgroundColor: 'var(--mui-palette-secondary-main)',
|
||||
color: 'var(--mui-palette-secondary-contrastText)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
MuiPaginationItem: {
|
||||
styleOverrides: {
|
||||
root: ({ ownerState }) => ({
|
||||
...(ownerState.size === 'medium' && {
|
||||
height: '2.375rem',
|
||||
minWidth: '2.375rem'
|
||||
}),
|
||||
...(ownerState.shape !== 'rounded' && {
|
||||
borderRadius: '50px'
|
||||
}),
|
||||
'&.Mui-selected.Mui-disabled': {
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
opacity: 0.45
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45
|
||||
},
|
||||
...(ownerState.shape === 'rounded' &&
|
||||
ownerState.size === 'small' && {
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-sm)'
|
||||
}),
|
||||
...(ownerState.shape === 'rounded' &&
|
||||
ownerState.size === 'large' && {
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-lg)'
|
||||
})
|
||||
}),
|
||||
sizeSmall: {
|
||||
height: '2.125rem',
|
||||
minWidth: '2.125rem'
|
||||
},
|
||||
sizeLarge: {
|
||||
height: '2.625rem',
|
||||
minWidth: '2.625rem'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default pagination
|
||||
@@ -0,0 +1,14 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const paper: Theme['components'] = {
|
||||
MuiPaper: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default paper
|
||||
@@ -0,0 +1,21 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const popover = (skin: Skin): Theme['components'] => ({
|
||||
MuiPopover: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
...(skin === 'bordered'
|
||||
? { boxShadow: 'none', border: '1px solid var(--mui-palette-divider)' }
|
||||
: {
|
||||
boxShadow: 'var(--mui-customShadows-sm)'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default popover
|
||||
@@ -0,0 +1,18 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const progress: Theme['components'] = {
|
||||
MuiLinearProgress: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
height: 6,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
'& .MuiLinearProgress-bar': {
|
||||
borderRadius: theme.shape.borderRadius
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default progress
|
||||
@@ -0,0 +1,81 @@
|
||||
// React Imports
|
||||
import React from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const IconChecked = () => {
|
||||
return (
|
||||
<svg width='1em' height='1em' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M12 18.5a6.5 6.5 0 1 1 0-13 6.5 6.5 0 0 1 0 13Z'
|
||||
fill='var(--mui-palette-common-white)'
|
||||
stroke='currentColor'
|
||||
strokeWidth='5'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const UncheckedIcon = () => {
|
||||
return (
|
||||
<svg width='1em' height='1em' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path d='M12 20a8 8 0 1 1 0-16 8 8 0 0 1 0 16Z' stroke='var(--mui-palette-text-secondary)' strokeWidth='2' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const radio: Theme['components'] = {
|
||||
MuiRadio: {
|
||||
defaultProps: {
|
||||
icon: <UncheckedIcon />,
|
||||
checkedIcon: <IconChecked />
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme, ownerState }) => ({
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
padding: theme.spacing(1),
|
||||
'& svg': {
|
||||
fontSize: '1.25rem'
|
||||
}
|
||||
}
|
||||
: {
|
||||
padding: theme.spacing(1.5),
|
||||
'& svg': {
|
||||
fontSize: '1.5rem'
|
||||
}
|
||||
}),
|
||||
'&.Mui-checked:not(.Mui-disabled) svg': {
|
||||
filter: 'drop-shadow(var(--mui-customShadows-xs))'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
'&:not(.Mui-checked)': {
|
||||
color: 'var(--mui-palette-text-secondary)'
|
||||
},
|
||||
'&.Mui-checked.MuiRadio-colorPrimary': {
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiRadio-colorSecondary': {
|
||||
color: 'var(--mui-palette-secondary-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiRadio-colorError': {
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiRadio-colorWarning': {
|
||||
color: 'var(--mui-palette-warning-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiRadio-colorInfo': {
|
||||
color: 'var(--mui-palette-info-main)'
|
||||
},
|
||||
'&.Mui-checked.MuiRadio-colorSuccess': {
|
||||
color: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default radio
|
||||
@@ -0,0 +1,35 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const rating: Theme['components'] = {
|
||||
MuiRating: {
|
||||
defaultProps: {
|
||||
emptyIcon: <i className='ri-star-line' />,
|
||||
icon: <i className='ri-star-fill' />
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
gap: '2px',
|
||||
color: 'var(--mui-palette-warning-main)',
|
||||
'& i, & svg': {
|
||||
flexShrink: 0
|
||||
},
|
||||
'& .MuiRating-decimal > label:first-of-type, & .MuiRating-decimal > span:first-of-type': {
|
||||
zIndex: 1
|
||||
}
|
||||
},
|
||||
sizeSmall: {
|
||||
'& .MuiRating-icon i, & .MuiRating-icon svg': {
|
||||
fontSize: '1.25rem'
|
||||
}
|
||||
},
|
||||
sizeLarge: {
|
||||
'& .MuiRating-icon i, & .MuiRating-icon svg': {
|
||||
fontSize: '1.75rem'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default rating
|
||||
@@ -0,0 +1,61 @@
|
||||
// React Imports
|
||||
import React from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const SelectIcon = () => {
|
||||
return <i className='ri-arrow-down-s-line' />
|
||||
}
|
||||
|
||||
const iconStyles = (theme: Theme) => ({
|
||||
userSelect: 'none',
|
||||
display: 'inline-block',
|
||||
fill: 'currentColor',
|
||||
flexShrink: 0,
|
||||
transition: theme.transitions.create('fill', {
|
||||
duration: theme.transitions.duration.shorter
|
||||
}),
|
||||
fontSize: '1.25rem',
|
||||
position: 'absolute',
|
||||
right: '1rem',
|
||||
top: 'calc(50% - 0.5em)',
|
||||
pointerEvents: 'none'
|
||||
})
|
||||
|
||||
const select: Theme['components'] = {
|
||||
MuiSelect: {
|
||||
defaultProps: {
|
||||
IconComponent: SelectIcon
|
||||
},
|
||||
styleOverrides: {
|
||||
select: ({ theme, ownerState }) => ({
|
||||
...(ownerState.variant === 'outlined' && {
|
||||
minHeight: '1.5em'
|
||||
}),
|
||||
'&[aria-expanded="true"] ~ i, &[aria-expanded="true"] ~ svg': {
|
||||
transform: 'rotate(180deg)'
|
||||
},
|
||||
'& ~ i, & ~ svg': iconStyles(theme as Theme),
|
||||
'&.MuiInputBase-inputSizeSmall': {
|
||||
'& ~ i, & ~ svg': {
|
||||
height: '1.375rem',
|
||||
width: '1.375rem'
|
||||
}
|
||||
},
|
||||
'&:not(aria-label="Without label") ~ .MuiOutlinedInput-notchedOutline > legend > span': {
|
||||
paddingInline: '5px'
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiNativeSelect: {
|
||||
styleOverrides: {
|
||||
select: ({ theme }) => ({
|
||||
'& + i, & + svg': iconStyles(theme as Theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default select
|
||||
@@ -0,0 +1,100 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const slider: Theme['components'] = {
|
||||
MuiSlider: {
|
||||
styleOverrides: {
|
||||
root: ({ ownerState }) => ({
|
||||
boxSizing: 'border-box',
|
||||
...(ownerState.orientation === 'horizontal'
|
||||
? ownerState.size !== 'small'
|
||||
? { height: 6 }
|
||||
: { height: 4 }
|
||||
: ownerState.size !== 'small'
|
||||
? { width: 6 }
|
||||
: { width: 4 }),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.45,
|
||||
color: `var(--mui-palette-${ownerState.color}-main)`
|
||||
}
|
||||
}),
|
||||
thumb: ({ ownerState }) => ({
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
height: 14,
|
||||
width: 14,
|
||||
border: '2px solid currentColor',
|
||||
'&:hover, &.Mui-focusVisible': {
|
||||
boxShadow: `0 0 0 7px var(--mui-palette-${ownerState.color}-lightOpacity)`
|
||||
},
|
||||
'&.Mui-active.Mui-focusVisible': {
|
||||
boxShadow: `0 0 0 10px var(--mui-palette-${ownerState.color}-lightOpacity)`
|
||||
}
|
||||
}
|
||||
: {
|
||||
height: 22,
|
||||
width: 22,
|
||||
border: '4px solid currentColor'
|
||||
}),
|
||||
backgroundColor: 'var(--mui-palette-common-white)',
|
||||
...(!ownerState.disabled && {
|
||||
boxShadow: 'var(--mui-customShadows-sm)'
|
||||
}),
|
||||
'&:before': {
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'&:after': {
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
height: 28,
|
||||
width: 28
|
||||
}
|
||||
: {
|
||||
height: 38,
|
||||
width: 38
|
||||
})
|
||||
},
|
||||
'&:hover, &.Mui-focusVisible': {
|
||||
boxShadow: `0 0 0 8px var(--mui-palette-${ownerState.color}-lightOpacity)`
|
||||
},
|
||||
'&.Mui-active.Mui-focusVisible': {
|
||||
boxShadow: `0 0 0 13px var(--mui-palette-${ownerState.color}-lightOpacity)`
|
||||
}
|
||||
}),
|
||||
rail: ({ ownerState }) => ({
|
||||
opacity: 1,
|
||||
color: `var(--mui-palette-${ownerState.color}-lightOpacity)`,
|
||||
...(ownerState.track === 'inverted' && {
|
||||
backgroundColor: `var(--mui-palette-${ownerState.color}-main)`
|
||||
})
|
||||
}),
|
||||
valueLabel: ({ theme, ownerState }) => ({
|
||||
...(ownerState.size === 'small'
|
||||
? {
|
||||
...theme.typography.caption,
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-sm)',
|
||||
padding: theme.spacing(1, 2)
|
||||
}
|
||||
: {
|
||||
...theme.typography.body2,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
borderRadius: 'var(--mui-shape-borderRadius)',
|
||||
padding: theme.spacing(1, 2.5)
|
||||
}),
|
||||
color: 'var(--mui-palette-customColors-tooltipText)',
|
||||
backgroundColor: 'var(--mui-palette-Tooltip-bg)',
|
||||
'&:before': {
|
||||
display: 'none'
|
||||
}
|
||||
}),
|
||||
track: ({ theme, ownerState }) => ({
|
||||
...(ownerState.track === 'inverted' && {
|
||||
backgroundColor: `color-mix(in srgb, ${theme.palette[ownerState.color || 'primary'].main} 16%, var(--mui-palette-background-paper))`,
|
||||
borderColor: `color-mix(in srgb, ${theme.palette[ownerState.color || 'primary'].main} 16%, var(--mui-palette-background-paper))`
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default slider
|
||||
@@ -0,0 +1,27 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { Skin } from '@core/types'
|
||||
|
||||
const snackbar = (skin: Skin): Theme['components'] => ({
|
||||
MuiSnackbarContent: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
padding: theme.spacing(0, 4),
|
||||
...(skin !== 'bordered'
|
||||
? {
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
}
|
||||
: {
|
||||
boxShadow: 'none'
|
||||
}),
|
||||
'& .MuiSnackbarContent-message': {
|
||||
paddingBlock: theme.spacing(3)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default snackbar
|
||||
@@ -0,0 +1,69 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const switchOverrides: Theme['components'] = {
|
||||
MuiSwitch: {
|
||||
defaultProps: {
|
||||
disableRipple: true
|
||||
},
|
||||
styleOverrides: {
|
||||
root: ({ theme, ownerState }) => ({
|
||||
'&:has(.Mui-disabled)': {
|
||||
opacity: 0.45
|
||||
},
|
||||
...(ownerState.size !== 'small'
|
||||
? {
|
||||
width: 46,
|
||||
height: 36,
|
||||
padding: theme.spacing(2.25, 2)
|
||||
}
|
||||
: {
|
||||
width: 42,
|
||||
height: 30,
|
||||
padding: theme.spacing(1.75, 2),
|
||||
'& .MuiSwitch-thumb': {
|
||||
width: 12,
|
||||
height: 12
|
||||
},
|
||||
'& .MuiSwitch-switchBase': {
|
||||
padding: 7,
|
||||
left: 3,
|
||||
'&.Mui-checked': {
|
||||
left: -3
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
switchBase: {
|
||||
top: 2,
|
||||
left: 1,
|
||||
'&.Mui-checked': {
|
||||
left: -7,
|
||||
color: 'var(--mui-palette-common-white)',
|
||||
'& + .MuiSwitch-track': {
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
'&.Mui-disabled + .MuiSwitch-track': {
|
||||
opacity: 1
|
||||
},
|
||||
'&:hover:not(:has(span.MuiTouchRipple-root))': {
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
},
|
||||
thumb: {
|
||||
width: 14,
|
||||
height: 14,
|
||||
boxShadow: 'var(--mui-customShadows-xs)'
|
||||
},
|
||||
track: {
|
||||
opacity: 1,
|
||||
borderRadius: 10,
|
||||
backgroundColor: 'var(--mui-palette-action-focus)',
|
||||
boxShadow: `0 0 4px rgb(0 0 0 / 0.16) inset`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default switchOverrides
|
||||
@@ -0,0 +1,42 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const tablePagination: Theme['components'] = {
|
||||
MuiTablePagination: {
|
||||
styleOverrides: {
|
||||
toolbar: ({ theme }) => ({
|
||||
paddingInlineEnd: `${theme.spacing(3)} !important`
|
||||
}),
|
||||
select: ({ theme }) => ({
|
||||
...theme.typography.body1,
|
||||
paddingInlineStart: 0,
|
||||
'& ~ i, & ~ svg': {
|
||||
fontSize: 20,
|
||||
right: '2px !important',
|
||||
color: 'var(--mui-palette-action-active)'
|
||||
}
|
||||
}),
|
||||
selectLabel: ({ theme }) => ({
|
||||
...theme.typography.body1,
|
||||
color: 'var(--mui-palette-text-secondary)'
|
||||
}),
|
||||
input: ({ theme }) => ({
|
||||
marginInlineEnd: theme.spacing(6)
|
||||
}),
|
||||
displayedRows: ({ theme }) => ({
|
||||
...theme.typography.body1
|
||||
}),
|
||||
actions: ({ theme }) => ({
|
||||
marginInlineStart: theme.spacing(6),
|
||||
'& .Mui-disabled': {
|
||||
color: 'var(--mui-palette-action-active)'
|
||||
},
|
||||
'& .MuiIconButton-root:last-of-type': {
|
||||
marginInlineStart: theme.spacing(2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default tablePagination
|
||||
@@ -0,0 +1,92 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const tabs: Theme['components'] = {
|
||||
MuiTabs: {
|
||||
styleOverrides: {
|
||||
root: ({ theme, ownerState }) => ({
|
||||
minBlockSize: 38,
|
||||
...(ownerState.orientation === 'horizontal'
|
||||
? {
|
||||
borderBlockEnd: '1px solid var(--mui-palette-divider)'
|
||||
}
|
||||
: {
|
||||
borderInlineEnd: '1px solid var(--mui-palette-divider)'
|
||||
}),
|
||||
'& .MuiTab-root:hover': {
|
||||
...(ownerState.orientation === 'horizontal'
|
||||
? {
|
||||
paddingBlockEnd: theme.spacing(1.5),
|
||||
...(ownerState.textColor === 'secondary'
|
||||
? {
|
||||
color: 'var(--mui-palette-secondary-main)',
|
||||
borderBlockEnd: '2px solid var(--mui-palette-secondary-lightOpacity)'
|
||||
}
|
||||
: {
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
borderBlockEnd: '2px solid var(--mui-palette-primary-lightOpacity)'
|
||||
})
|
||||
}
|
||||
: {
|
||||
paddingInlineEnd: theme.spacing(5),
|
||||
...(ownerState.textColor === 'secondary'
|
||||
? {
|
||||
color: 'var(--mui-palette-secondary-main)',
|
||||
borderInlineEnd: '2px solid var(--mui-palette-secondary-mainOpacity)'
|
||||
}
|
||||
: {
|
||||
color: 'var(--mui-palette-primary-main)',
|
||||
borderInlineEnd: '2px solid var(--mui-palette-primary-mainOpacity)'
|
||||
})
|
||||
}),
|
||||
'& .MuiTabScrollButton-root': {
|
||||
borderRadius: theme.shape.borderRadius
|
||||
}
|
||||
},
|
||||
'& ~ .MuiTabPanel-root': {
|
||||
...(ownerState.orientation === 'horizontal'
|
||||
? {
|
||||
paddingBlockStart: theme.spacing(5)
|
||||
}
|
||||
: {
|
||||
paddingInlineStart: theme.spacing(5)
|
||||
})
|
||||
}
|
||||
}),
|
||||
vertical: {
|
||||
minWidth: 131,
|
||||
'& .MuiTab-root': {
|
||||
minWidth: 130
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: ({ theme, ownerState }) => ({
|
||||
lineHeight: 1.4667,
|
||||
padding: theme.spacing(2, 5.5),
|
||||
minBlockSize: 38,
|
||||
color: 'var(--mui-palette-text-primary)',
|
||||
'& > .MuiTab-iconWrapper': {
|
||||
fontSize: '1.125rem',
|
||||
...(ownerState.iconPosition === 'start' && {
|
||||
marginInlineEnd: theme.spacing(1.5)
|
||||
}),
|
||||
...(ownerState.iconPosition === 'end' && {
|
||||
marginInlineStart: theme.spacing(1.5)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiTabPanel: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default tabs
|
||||
@@ -0,0 +1,161 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const timeline: Theme['components'] = {
|
||||
MuiTimeline: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
padding: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTimelineDot: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
margin: theme.spacing(3, 0),
|
||||
boxShadow: 'none',
|
||||
'&:has(> i), &:has(> svg)': {
|
||||
padding: 6
|
||||
},
|
||||
'& > svg, & > i': {
|
||||
fontSize: '1.25rem'
|
||||
},
|
||||
'&:has(svg)': {
|
||||
width: 32,
|
||||
height: 32,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
})
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { variant: 'outlined' },
|
||||
style: {
|
||||
padding: 5,
|
||||
'& + .MuiTimelineConnector-root': {
|
||||
backgroundColor: 'transparent',
|
||||
borderInlineStart: '1px dashed var(--mui-palette-divider)'
|
||||
},
|
||||
'&:has(+ .MuiTimelineConnector-root)': {
|
||||
marginBlock: '0.625rem'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', color: 'grey' },
|
||||
style: {
|
||||
boxShadow: '0 0 0 3px rgb(var(--mui-mainColorChannels-light) / 0.04)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', color: 'primary' },
|
||||
style: {
|
||||
boxShadow: '0 0 0 3px var(--mui-palette-primary-lightOpacity)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', color: 'secondary' },
|
||||
style: {
|
||||
boxShadow: '0 0 0 3px var(--mui-palette-secondary-lightOpacity)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', color: 'error' },
|
||||
style: {
|
||||
boxShadow: '0 0 0 3px var(--mui-palette-error-lightOpacity)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', color: 'warning' },
|
||||
style: {
|
||||
boxShadow: '0 0 0 3px var(--mui-palette-warning-lightOpacity)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', color: 'info' },
|
||||
style: {
|
||||
boxShadow: '0 0 0 3px var(--mui-palette-info-lightOpacity)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'filled', color: 'success' },
|
||||
style: {
|
||||
boxShadow: '0 0 0 3px var(--mui-palette-success-lightOpacity)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal' },
|
||||
style: {
|
||||
border: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'grey' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-action-selected)',
|
||||
color: 'var(--mui-palette-text-primary)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'primary' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-primary-lightOpacity)',
|
||||
color: 'var(--mui-palette-primary-main)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'secondary' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-secondary-lightOpacity)',
|
||||
color: 'var(--mui-palette-secondary-main)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'error' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-error-lightOpacity)',
|
||||
color: 'var(--mui-palette-error-main)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'warning' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-warning-lightOpacity)',
|
||||
color: 'var(--mui-palette-warning-main)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'info' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-info-lightOpacity)',
|
||||
color: 'var(--mui-palette-info-main)'
|
||||
}
|
||||
},
|
||||
{
|
||||
props: { variant: 'tonal', color: 'success' },
|
||||
style: {
|
||||
backgroundColor: 'var(--mui-palette-success-lightOpacity)',
|
||||
color: 'var(--mui-palette-success-main)'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
MuiTimelineConnector: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
width: 1,
|
||||
backgroundColor: 'var(--mui-palette-divider)'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTimelineContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
paddingBottom: '1rem'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default timeline
|
||||
@@ -0,0 +1,34 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const toggleButton: Theme['components'] = {
|
||||
MuiToggleButtonGroup: {
|
||||
styleOverrides: {
|
||||
root: ({ ownerState }) => ({
|
||||
...(ownerState.size === 'small' && {
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-sm)'
|
||||
}),
|
||||
...(ownerState.size === 'large' && {
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-lg)'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
MuiToggleButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:not(.Mui-selected):not(.Mui-disabled)': {
|
||||
color: 'var(--mui-palette-text-secondary)'
|
||||
}
|
||||
},
|
||||
sizeSmall: {
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-sm)'
|
||||
},
|
||||
sizeLarge: {
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-lg)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default toggleButton
|
||||
@@ -0,0 +1,32 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const tooltip: Theme['components'] = {
|
||||
MuiTooltip: {
|
||||
styleOverrides: {
|
||||
popper: {
|
||||
'&[data-popper-placement*="bottom"] .MuiTooltip-tooltip': {
|
||||
marginTop: '6px !important'
|
||||
},
|
||||
'&[data-popper-placement*="top"] .MuiTooltip-tooltip': {
|
||||
marginBottom: '6px !important'
|
||||
},
|
||||
'&[data-popper-placement*="left"] .MuiTooltip-tooltip': {
|
||||
marginRight: '6px !important'
|
||||
},
|
||||
'&[data-popper-placement*="right"] .MuiTooltip-tooltip': {
|
||||
marginLeft: '6px !important'
|
||||
}
|
||||
},
|
||||
tooltip: ({ theme }) => ({
|
||||
borderRadius: 'var(--mui-shape-customBorderRadius-sm)',
|
||||
fontSize: theme.typography.subtitle2.fontSize,
|
||||
lineHeight: 1.539,
|
||||
color: 'var(--mui-palette-customColors-tooltipText)',
|
||||
paddingInline: theme.spacing(3)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default tooltip
|
||||
@@ -0,0 +1,68 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const typography: Theme['components'] = {
|
||||
MuiTypography: {
|
||||
styleOverrides: {
|
||||
gutterBottom: ({ theme }) => ({
|
||||
marginBottom: theme.spacing(2)
|
||||
})
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { variant: 'h1' },
|
||||
style: { color: 'var(--mui-palette-text-primary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'h2' },
|
||||
style: { color: 'var(--mui-palette-text-primary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'h3' },
|
||||
style: { color: 'var(--mui-palette-text-primary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'h4' },
|
||||
style: { color: 'var(--mui-palette-text-primary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'h5' },
|
||||
style: { color: 'var(--mui-palette-text-primary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'h6' },
|
||||
style: { color: 'var(--mui-palette-text-primary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'subtitle1' },
|
||||
style: { color: 'rgb(var(--mui-palette-text-primaryChannel) / 0.55)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'subtitle2' },
|
||||
style: { color: 'rgb(var(--mui-palette-text-primaryChannel) / 0.55)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'body1' },
|
||||
style: { color: 'var(--mui-palette-text-secondary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'body2' },
|
||||
style: { color: 'var(--mui-palette-text-secondary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'button' },
|
||||
style: { color: 'var(--mui-palette-text-primary)' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'caption' },
|
||||
style: { color: 'var(--mui-palette-text-disabled)', display: 'inline-block' }
|
||||
},
|
||||
{
|
||||
props: { variant: 'overline' },
|
||||
style: { color: 'var(--mui-palette-text-primary)', display: 'inline-block' }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default typography
|
||||
@@ -0,0 +1,39 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Type Imports
|
||||
import type { SystemMode } from '@core/types'
|
||||
|
||||
const shadows = (mode: SystemMode): Theme['shadows'] => {
|
||||
const color = `var(--mui-mainColorChannels-${mode}Shadow)`
|
||||
|
||||
return [
|
||||
'none',
|
||||
`0px 2px 1px -1px rgb(${color} / 0.2),0px 1px 1px 0px rgb(${color} / 0.14),0px 1px 3px 0px rgb(${color} / 0.12)`,
|
||||
`0px 3px 1px -2px rgb(${color} / 0.2),0px 2px 2px 0px rgb(${color} / 0.14),0px 1px 5px 0px rgb(${color} / 0.12)`,
|
||||
`0px 3px 3px -2px rgb(${color} / 0.2),0px 3px 4px 0px rgb(${color} / 0.14),0px 1px 8px 0px rgb(${color} / 0.12)`,
|
||||
`0px 2px 4px -1px rgb(${color} / 0.2),0px 4px 5px 0px rgb(${color} / 0.14),0px 1px 10px 0px rgb(${color} / 0.12)`,
|
||||
`0px 3px 5px -1px rgb(${color} / 0.2),0px 5px 8px 0px rgb(${color} / 0.14),0px 1px 14px 0px rgb(${color} / 0.12)`,
|
||||
`0px 3px 5px -1px rgb(${color} / 0.2),0px 6px 10px 0px rgb(${color} / 0.14),0px 1px 18px 0px rgb(${color} / 0.12)`,
|
||||
`0px 4px 5px -2px rgb(${color} / 0.2),0px 7px 10px 1px rgb(${color} / 0.14),0px 2px 16px 1px rgb(${color} / 0.12)`,
|
||||
`0px 5px 5px -3px rgb(${color} / 0.2),0px 8px 10px 1px rgb(${color} / 0.14),0px 3px 14px 2px rgb(${color} / 0.12)`,
|
||||
`0px 5px 6px -3px rgb(${color} / 0.2),0px 9px 12px 1px rgb(${color} / 0.14),0px 3px 16px 2px rgb(${color} / 0.12)`,
|
||||
`0px 6px 6px -3px rgb(${color} / 0.2),0px 10px 14px 1px rgb(${color} / 0.14),0px 4px 18px 3px rgb(${color} / 0.12)`,
|
||||
`0px 6px 7px -4px rgb(${color} / 0.2),0px 11px 15px 1px rgb(${color} / 0.14),0px 4px 20px 3px rgb(${color} / 0.12)`,
|
||||
`0px 7px 8px -4px rgb(${color} / 0.2),0px 12px 17px 2px rgb(${color} / 0.14),0px 5px 22px 4px rgb(${color} / 0.12)`,
|
||||
`0px 7px 8px -4px rgb(${color} / 0.2),0px 13px 19px 2px rgb(${color} / 0.14),0px 5px 24px 4px rgb(${color} / 0.12)`,
|
||||
`0px 7px 9px -4px rgb(${color} / 0.2),0px 14px 21px 2px rgb(${color} / 0.14),0px 5px 26px 4px rgb(${color} / 0.12)`,
|
||||
`0px 8px 9px -5px rgb(${color} / 0.2),0px 15px 22px 2px rgb(${color} / 0.14),0px 6px 28px 5px rgb(${color} / 0.12)`,
|
||||
`0px 8px 10px -5px rgb(${color} / 0.2),0px 16px 24px 2px rgb(${color} / 0.14),0px 6px 30px 5px rgb(${color} / 0.12)`,
|
||||
`0px 8px 11px -5px rgb(${color} / 0.2),0px 17px 26px 2px rgb(${color} / 0.14),0px 6px 32px 5px rgb(${color} / 0.12)`,
|
||||
`0px 9px 11px -5px rgb(${color} / 0.2),0px 18px 28px 2px rgb(${color} / 0.14),0px 7px 34px 6px rgb(${color} / 0.12)`,
|
||||
`0px 9px 12px -6px rgb(${color} / 0.2),0px 19px 29px 2px rgb(${color} / 0.14),0px 7px 36px 6px rgb(${color} / 0.12)`,
|
||||
`0px 10px 13px -6px rgb(${color} / 0.2),0px 20px 31px 3px rgb(${color} / 0.14),0px 8px 38px 7px rgb(${color} / 0.12)`,
|
||||
`0px 10px 13px -6px rgb(${color} / 0.2),0px 21px 33px 3px rgb(${color} / 0.14),0px 8px 40px 7px rgb(${color} / 0.12)`,
|
||||
`0px 10px 14px -6px rgb(${color} / 0.2),0px 22px 35px 3px rgb(${color} / 0.14),0px 8px 42px 7px rgb(${color} / 0.12)`,
|
||||
`0px 11px 14px -7px rgb(${color} / 0.2),0px 23px 36px 3px rgb(${color} / 0.14),0px 9px 44px 8px rgb(${color} / 0.12)`,
|
||||
`0px 11px 15px -7px rgb(${color} / 0.2),0px 24px 38px 3px rgb(${color} / 0.14),0px 9px 46px 8px rgb(${color} / 0.12)`
|
||||
]
|
||||
}
|
||||
|
||||
export default shadows
|
||||
@@ -0,0 +1,5 @@
|
||||
const spacing = {
|
||||
spacing: (factor: number) => `${0.25 * factor}rem`
|
||||
}
|
||||
|
||||
export default spacing
|
||||
@@ -0,0 +1,88 @@
|
||||
// MUI Imports
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
const typography = (fontFamily: string): Theme['typography'] =>
|
||||
({
|
||||
fontFamily:
|
||||
typeof fontFamily === 'undefined' || fontFamily === ''
|
||||
? [
|
||||
'Inter',
|
||||
'sans-serif',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"'
|
||||
].join(',')
|
||||
: fontFamily,
|
||||
fontSize: 13.125,
|
||||
h1: {
|
||||
fontSize: '2.875rem',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.478261
|
||||
},
|
||||
h2: {
|
||||
fontSize: '2.375rem',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.47368421
|
||||
},
|
||||
h3: {
|
||||
fontSize: '1.75rem',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.5
|
||||
},
|
||||
h4: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.58334
|
||||
},
|
||||
h5: {
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.5556
|
||||
},
|
||||
h6: {
|
||||
fontSize: '0.9375rem',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.46667
|
||||
},
|
||||
subtitle1: {
|
||||
fontSize: '0.9375rem',
|
||||
lineHeight: 1.46667
|
||||
},
|
||||
subtitle2: {
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.53846154
|
||||
},
|
||||
body1: {
|
||||
fontSize: '0.9375rem',
|
||||
lineHeight: 1.46667
|
||||
},
|
||||
body2: {
|
||||
fontSize: '0.8125rem',
|
||||
lineHeight: 1.53846154
|
||||
},
|
||||
button: {
|
||||
fontSize: '0.9375rem',
|
||||
lineHeight: 1.46667,
|
||||
textTransform: 'none'
|
||||
},
|
||||
caption: {
|
||||
fontSize: '0.8125rem',
|
||||
lineHeight: 1.38462,
|
||||
letterSpacing: '0.4px'
|
||||
},
|
||||
overline: {
|
||||
fontSize: '0.75rem',
|
||||
lineHeight: 1.16667,
|
||||
letterSpacing: '0.8px'
|
||||
}
|
||||
}) as Theme['typography']
|
||||
|
||||
export default typography
|
||||
@@ -0,0 +1,16 @@
|
||||
// React Imports
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export type Skin = 'default' | 'bordered'
|
||||
|
||||
export type Mode = 'light' | 'dark'
|
||||
|
||||
export type SystemMode = 'light' | 'dark'
|
||||
|
||||
export type Direction = 'ltr' | 'rtl'
|
||||
|
||||
export type ChildrenType = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export type ThemeColor = 'primary' | 'secondary' | 'error' | 'warning' | 'info' | 'success'
|
||||
@@ -0,0 +1,18 @@
|
||||
export type SystemConfig = {
|
||||
id: string;
|
||||
sessionCleanupIntervalMinutes: number;
|
||||
invoiceGenerationDayOfMonth: number;
|
||||
tokenExpirationMinutes: number;
|
||||
tokenCleanupIntervalMinutes: number;
|
||||
profilePictureUpdateIntervalDays: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type UpdateSystemConfigDto = {
|
||||
sessionCleanupIntervalMinutes?: number;
|
||||
invoiceGenerationDayOfMonth?: number;
|
||||
tokenExpirationMinutes?: number;
|
||||
tokenCleanupIntervalMinutes?: number;
|
||||
profilePictureUpdateIntervalDays?: number;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'server-only'
|
||||
|
||||
// Next Imports
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
// Type Imports
|
||||
import type { Settings } from '@core/contexts/settingsContext'
|
||||
import type { SystemMode } from '@core/types'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
export const getSettingsFromCookie = (): Settings => {
|
||||
const cookieStore = cookies()
|
||||
|
||||
const cookieName = themeConfig.settingsCookieName
|
||||
|
||||
return JSON.parse(cookieStore.get(cookieName)?.value || '{}')
|
||||
}
|
||||
|
||||
export const getMode = () => {
|
||||
const settingsCookie = getSettingsFromCookie()
|
||||
|
||||
// Get mode from cookie or fallback to theme config
|
||||
const _mode = settingsCookie.mode || themeConfig.mode
|
||||
|
||||
return _mode
|
||||
}
|
||||
|
||||
export const getSystemMode = (): SystemMode => {
|
||||
const mode = getMode()
|
||||
|
||||
return mode
|
||||
}
|
||||
|
||||
export const getServerMode = () => {
|
||||
const mode = getMode()
|
||||
|
||||
return mode
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
'use client'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '@core/types'
|
||||
|
||||
// Util Imports
|
||||
import { blankLayoutClasses } from './utils/layoutClasses'
|
||||
|
||||
const BlankLayout = ({ children }: ChildrenType) => {
|
||||
return <div className={classnames(blankLayoutClasses.root, 'is-full bs-full')}>{children}</div>
|
||||
}
|
||||
|
||||
export default BlankLayout
|
||||
@@ -0,0 +1,11 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import type { ReactElement } from 'react'
|
||||
|
||||
const LayoutWrapper = ({ verticalLayout }: { verticalLayout: ReactElement }) => {
|
||||
// Return the layout based on the layout context
|
||||
return <div className='flex flex-col flex-auto'>{verticalLayout}</div>
|
||||
}
|
||||
|
||||
export default LayoutWrapper
|
||||
@@ -0,0 +1,39 @@
|
||||
// React Imports
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '@core/types'
|
||||
|
||||
// Component Imports
|
||||
import LayoutContent from './components/vertical/LayoutContent'
|
||||
|
||||
// Util Imports
|
||||
import { verticalLayoutClasses } from './utils/layoutClasses'
|
||||
|
||||
type VerticalLayoutProps = ChildrenType & {
|
||||
navigation?: ReactNode
|
||||
navbar?: ReactNode
|
||||
footer?: ReactNode
|
||||
}
|
||||
|
||||
const VerticalLayout = (props: VerticalLayoutProps) => {
|
||||
// Props
|
||||
const { navbar, footer, navigation, children } = props
|
||||
|
||||
return (
|
||||
<div className={classnames(verticalLayoutClasses.root, 'flex flex-auto')}>
|
||||
{navigation || null}
|
||||
<div className={classnames(verticalLayoutClasses.contentWrapper, 'flex flex-col min-is-0 is-full')}>
|
||||
{navbar || null}
|
||||
{/* Content */}
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
{footer || null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VerticalLayout
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '@core/types'
|
||||
|
||||
// Util Imports
|
||||
import { verticalLayoutClasses } from '@layouts/utils/layoutClasses'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledFooter from '@layouts/styles/vertical/StyledFooter'
|
||||
|
||||
type Props = ChildrenType & {
|
||||
overrideStyles?: CSSObject
|
||||
}
|
||||
|
||||
const Footer = (props: Props) => {
|
||||
// Props
|
||||
const { children, overrideStyles } = props
|
||||
|
||||
return (
|
||||
<StyledFooter
|
||||
overrideStyles={overrideStyles}
|
||||
className={classnames(
|
||||
verticalLayoutClasses.footer,
|
||||
verticalLayoutClasses.footerContentCompact,
|
||||
verticalLayoutClasses.footerStatic,
|
||||
verticalLayoutClasses.footerDetached,
|
||||
'is-full'
|
||||
)}
|
||||
>
|
||||
<div className={verticalLayoutClasses.footerContentWrapper}>{children}</div>
|
||||
</StyledFooter>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
@@ -0,0 +1,26 @@
|
||||
'use client'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '@core/types'
|
||||
|
||||
// Util Imports
|
||||
import { verticalLayoutClasses } from '@layouts/utils/layoutClasses'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledMain from '@layouts/styles/shared/StyledMain'
|
||||
|
||||
const LayoutContent = ({ children }: ChildrenType) => {
|
||||
return (
|
||||
<StyledMain
|
||||
isContentCompact={true}
|
||||
className={classnames(verticalLayoutClasses.content, verticalLayoutClasses.contentCompact, 'flex-auto is-full')}
|
||||
>
|
||||
{children}
|
||||
</StyledMain>
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutContent
|
||||
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '@core/types'
|
||||
|
||||
// Util Imports
|
||||
import { verticalLayoutClasses } from '@layouts/utils/layoutClasses'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledHeader from '@layouts/styles/vertical/StyledHeader'
|
||||
|
||||
type Props = ChildrenType & {
|
||||
overrideStyles?: CSSObject
|
||||
}
|
||||
|
||||
const Navbar = (props: Props) => {
|
||||
// Props
|
||||
const { children, overrideStyles } = props
|
||||
|
||||
return (
|
||||
<StyledHeader
|
||||
overrideStyles={overrideStyles}
|
||||
className={classnames(
|
||||
verticalLayoutClasses.header,
|
||||
verticalLayoutClasses.headerContentCompact,
|
||||
verticalLayoutClasses.headerStatic,
|
||||
verticalLayoutClasses.headerDetached
|
||||
)}
|
||||
>
|
||||
<div className={classnames(verticalLayoutClasses.navbar, 'flex bs-full')}>{children}</div>
|
||||
</StyledHeader>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
@@ -0,0 +1,21 @@
|
||||
// Third-party Imports
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
type StyledMainProps = {
|
||||
isContentCompact: boolean
|
||||
}
|
||||
|
||||
const StyledMain = styled.main<StyledMainProps>`
|
||||
padding: ${themeConfig.layoutPadding}px;
|
||||
${({ isContentCompact }) =>
|
||||
isContentCompact &&
|
||||
`
|
||||
margin-inline: auto;
|
||||
max-inline-size: ${themeConfig.compactContentWidth}px;
|
||||
`}
|
||||
`
|
||||
|
||||
export default StyledMain
|
||||
@@ -0,0 +1,27 @@
|
||||
// Third-party Imports
|
||||
import styled from '@emotion/styled'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
// Util Imports
|
||||
import { verticalLayoutClasses } from '@layouts/utils/layoutClasses'
|
||||
|
||||
type StyledFooterProps = {
|
||||
overrideStyles?: CSSObject
|
||||
}
|
||||
|
||||
const StyledFooter = styled.footer<StyledFooterProps>`
|
||||
margin-inline: auto;
|
||||
max-inline-size: ${themeConfig.compactContentWidth}px;
|
||||
|
||||
& .${verticalLayoutClasses.footerContentWrapper} {
|
||||
padding-block: 15px;
|
||||
padding-inline: ${themeConfig.layoutPadding}px;
|
||||
}
|
||||
|
||||
${({ overrideStyles }) => overrideStyles}
|
||||
`
|
||||
|
||||
export default StyledFooter
|
||||
@@ -0,0 +1,35 @@
|
||||
// Third-party Imports
|
||||
import styled from '@emotion/styled'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Config Imports
|
||||
import themeConfig from '@configs/themeConfig'
|
||||
|
||||
// Util Imports
|
||||
import { verticalLayoutClasses } from '@layouts/utils/layoutClasses'
|
||||
|
||||
type StyledHeaderProps = {
|
||||
overrideStyles?: CSSObject
|
||||
}
|
||||
|
||||
const StyledHeader = styled.header<StyledHeaderProps>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: 100%;
|
||||
flex-shrink: 0;
|
||||
min-block-size: var(--header-height);
|
||||
|
||||
.${verticalLayoutClasses.navbar} {
|
||||
position: relative;
|
||||
padding-block: 10px;
|
||||
padding-inline: ${themeConfig.layoutPadding}px;
|
||||
inline-size: 100%;
|
||||
margin-inline: auto;
|
||||
max-inline-size: ${themeConfig.compactContentWidth}px;
|
||||
}
|
||||
|
||||
${({ overrideStyles }) => overrideStyles}
|
||||
`
|
||||
|
||||
export default StyledHeader
|
||||
@@ -0,0 +1,24 @@
|
||||
// Classes for vertical layout
|
||||
export const verticalLayoutClasses = {
|
||||
root: 'ts-vertical-layout',
|
||||
contentWrapper: 'ts-vertical-layout-content-wrapper',
|
||||
header: 'ts-vertical-layout-header',
|
||||
headerStatic: 'ts-vertical-layout-header-static',
|
||||
headerDetached: 'ts-vertical-layout-header-detached',
|
||||
headerContentCompact: 'ts-vertical-layout-header-content-compact',
|
||||
navbar: 'ts-vertical-layout-navbar',
|
||||
navbarContent: 'ts-vertical-layout-navbar-content',
|
||||
content: 'ts-vertical-layout-content',
|
||||
contentCompact: 'ts-vertical-layout-content-compact',
|
||||
footer: 'ts-vertical-layout-footer',
|
||||
footerStatic: 'ts-vertical-layout-footer-static',
|
||||
footerDetached: 'ts-vertical-layout-footer-detached',
|
||||
footerContentWrapper: 'ts-vertical-layout-footer-content-wrapper',
|
||||
footerContent: 'ts-vertical-layout-footer-content',
|
||||
footerContentCompact: 'ts-vertical-layout-footer-content-compact'
|
||||
}
|
||||
|
||||
// Classes for blank layout
|
||||
export const blankLayoutClasses = {
|
||||
root: 'ts-blank-layout'
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { forwardRef } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import type { LinkProps } from 'next/link'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '../types'
|
||||
|
||||
type RouterLinkProps = LinkProps &
|
||||
Partial<ChildrenType> & {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const RouterLink = forwardRef((props: RouterLinkProps, ref: any) => {
|
||||
// Props
|
||||
const { href, className, ...other } = props
|
||||
|
||||
return (
|
||||
<Link ref={ref} href={href.toString() || '/'} className={className} {...other}>
|
||||
{props.children}
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,193 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { createContext, forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { ForwardRefRenderFunction, MenuHTMLAttributes, MutableRefObject, ReactElement, ReactNode } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type {
|
||||
ChildrenType,
|
||||
MenuItemStyles,
|
||||
RootStylesType,
|
||||
RenderExpandIconParams,
|
||||
RenderExpandedMenuItemIcon
|
||||
} from '../../types'
|
||||
|
||||
// Util Imports
|
||||
import { menuClasses } from '../../utils/menuClasses'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledVerticalMenu from '../../styles/vertical/StyledVerticalMenu'
|
||||
|
||||
// Style Imports
|
||||
import styles from '../../styles/styles.module.css'
|
||||
|
||||
// Default Config Imports
|
||||
import { verticalSubMenuToggleDuration } from '../../defaultConfigs'
|
||||
|
||||
export type MenuSectionStyles = {
|
||||
root?: CSSObject
|
||||
label?: CSSObject
|
||||
prefix?: CSSObject
|
||||
suffix?: CSSObject
|
||||
icon?: CSSObject
|
||||
}
|
||||
|
||||
export type OpenSubmenu = {
|
||||
level: number
|
||||
label: ReactNode
|
||||
active: boolean
|
||||
id: string
|
||||
}
|
||||
|
||||
export type VerticalMenuContextProps = {
|
||||
transitionDuration?: number
|
||||
menuSectionStyles?: MenuSectionStyles
|
||||
menuItemStyles?: MenuItemStyles
|
||||
subMenuOpenBehavior?: 'accordion' | 'collapse'
|
||||
renderExpandIcon?: (params: RenderExpandIconParams) => ReactElement
|
||||
renderExpandedMenuItemIcon?: RenderExpandedMenuItemIcon
|
||||
textTruncate?: boolean
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
openSubmenu?: OpenSubmenu[]
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
openSubmenusRef?: MutableRefObject<OpenSubmenu[]>
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
toggleOpenSubmenu?: (...submenus: { level: number; label: ReactNode; active?: boolean; id: string }[]) => void
|
||||
}
|
||||
|
||||
export type MenuProps = VerticalMenuContextProps &
|
||||
RootStylesType &
|
||||
Partial<ChildrenType> &
|
||||
MenuHTMLAttributes<HTMLMenuElement>
|
||||
|
||||
export const VerticalMenuContext = createContext({} as VerticalMenuContextProps)
|
||||
|
||||
const Menu: ForwardRefRenderFunction<HTMLMenuElement, MenuProps> = (props, ref) => {
|
||||
// Props
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
rootStyles,
|
||||
menuItemStyles,
|
||||
renderExpandIcon,
|
||||
renderExpandedMenuItemIcon,
|
||||
menuSectionStyles,
|
||||
subMenuOpenBehavior = 'accordion', // accordion, collapse
|
||||
transitionDuration = verticalSubMenuToggleDuration,
|
||||
textTruncate = true,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
// States
|
||||
const [openSubmenu, setOpenSubmenu] = useState<OpenSubmenu[]>([])
|
||||
|
||||
// Refs
|
||||
const openSubmenusRef = useRef<OpenSubmenu[]>([])
|
||||
|
||||
// Hooks
|
||||
const pathname = usePathname()
|
||||
|
||||
const toggleOpenSubmenu = useCallback(
|
||||
(...submenus: { level: number; label: ReactNode; active?: boolean; id: string }[]): void => {
|
||||
if (!submenus.length) return
|
||||
|
||||
const openSubmenuCopy = [...openSubmenu]
|
||||
|
||||
submenus.forEach(({ level, label, active = false, id }) => {
|
||||
const submenuIndex = openSubmenuCopy.findIndex(submenu => submenu.id === id)
|
||||
const submenuExists = submenuIndex >= 0
|
||||
const isAccordion = subMenuOpenBehavior === 'accordion'
|
||||
|
||||
const inactiveSubmenuIndex = openSubmenuCopy.findIndex(submenu => !submenu.active && submenu.level === 0)
|
||||
|
||||
// Delete submenu if it exists
|
||||
if (submenuExists) {
|
||||
openSubmenuCopy.splice(submenuIndex, 1)
|
||||
}
|
||||
|
||||
if (isAccordion) {
|
||||
// Add submenu if it doesn't exist
|
||||
if (!submenuExists) {
|
||||
if (inactiveSubmenuIndex >= 0 && !active && level === 0) {
|
||||
openSubmenuCopy.splice(inactiveSubmenuIndex, 1, { level, label, active, id })
|
||||
} else {
|
||||
openSubmenuCopy.push({ level, label, active, id })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Add submenu if it doesn't exist
|
||||
if (!submenuExists) {
|
||||
openSubmenuCopy.push({ level, label, active, id })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setOpenSubmenu(openSubmenuCopy)
|
||||
},
|
||||
[openSubmenu, subMenuOpenBehavior]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setOpenSubmenu([...openSubmenusRef.current])
|
||||
openSubmenusRef.current = []
|
||||
}, [pathname])
|
||||
|
||||
const providerValue = useMemo(
|
||||
() => ({
|
||||
transitionDuration,
|
||||
menuItemStyles,
|
||||
menuSectionStyles,
|
||||
renderExpandIcon,
|
||||
renderExpandedMenuItemIcon,
|
||||
openSubmenu,
|
||||
openSubmenusRef,
|
||||
toggleOpenSubmenu,
|
||||
subMenuOpenBehavior,
|
||||
textTruncate
|
||||
}),
|
||||
[
|
||||
transitionDuration,
|
||||
menuItemStyles,
|
||||
menuSectionStyles,
|
||||
renderExpandIcon,
|
||||
renderExpandedMenuItemIcon,
|
||||
openSubmenu,
|
||||
openSubmenusRef,
|
||||
toggleOpenSubmenu,
|
||||
subMenuOpenBehavior,
|
||||
textTruncate
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<VerticalMenuContext.Provider value={providerValue}>
|
||||
<StyledVerticalMenu
|
||||
ref={ref}
|
||||
className={classnames(menuClasses.root, className)}
|
||||
rootStyles={rootStyles}
|
||||
{...rest}
|
||||
>
|
||||
<ul className={styles.ul}>{children}</ul>
|
||||
</StyledVerticalMenu>
|
||||
</VerticalMenuContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(Menu)
|
||||
@@ -0,0 +1,76 @@
|
||||
// React Imports
|
||||
import { forwardRef } from 'react'
|
||||
import type { ForwardRefRenderFunction } from 'react'
|
||||
|
||||
// Third-party Imports
|
||||
import { css } from '@emotion/react'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType, MenuButtonProps } from '../../types'
|
||||
|
||||
// Component Imports
|
||||
import { RouterLink } from '../RouterLink'
|
||||
|
||||
// Util Imports
|
||||
import { menuClasses } from '../../utils/menuClasses'
|
||||
|
||||
type MenuButtonStylesProps = Partial<ChildrenType> & {
|
||||
level: number
|
||||
active?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const menuButtonStyles = (props: MenuButtonStylesProps) => {
|
||||
// Props
|
||||
const { level, disabled, children } = props
|
||||
|
||||
return css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
minBlockSize: '30px',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
boxSizing: 'border-box',
|
||||
cursor: 'pointer',
|
||||
paddingInlineEnd: '20px',
|
||||
paddingInlineStart: `${level === 0 ? 20 : (level + 1) * 20}px`,
|
||||
|
||||
'&:hover, &[aria-expanded="true"]': {
|
||||
backgroundColor: '#f3f3f3'
|
||||
},
|
||||
|
||||
'&:focus-visible': {
|
||||
outline: 'none',
|
||||
backgroundColor: '#f3f3f3'
|
||||
},
|
||||
|
||||
...(disabled && {
|
||||
pointerEvents: 'none',
|
||||
cursor: 'default',
|
||||
color: '#adadad'
|
||||
}),
|
||||
|
||||
// All the active styles are applied to the button including menu items or submenu
|
||||
[`&.${menuClasses.active}`]: {
|
||||
...(!children && { color: 'white' }),
|
||||
backgroundColor: children ? '#f3f3f3' : '#765feb'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const MenuButton: ForwardRefRenderFunction<HTMLAnchorElement, MenuButtonProps> = (
|
||||
{ className, children, ...rest },
|
||||
ref
|
||||
) => {
|
||||
return rest.href ? (
|
||||
<RouterLink ref={ref} className={className} href={rest.href} {...rest}>
|
||||
{children}
|
||||
</RouterLink>
|
||||
) : (
|
||||
<a ref={ref} className={className} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(MenuButton)
|
||||
@@ -0,0 +1,186 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { forwardRef, useEffect, useState } from 'react'
|
||||
import type { AnchorHTMLAttributes, ForwardRefRenderFunction, ReactElement, ReactNode } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import { useUpdateEffect } from 'react-use'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType, MenuItemElement, MenuItemExactMatchUrlProps, RootStylesType } from '../../types'
|
||||
|
||||
// Component Imports
|
||||
import MenuButton from './MenuButton'
|
||||
|
||||
// Hook Imports
|
||||
import useVerticalNav from '../../hooks/useVerticalNav'
|
||||
import useVerticalMenu from '../../hooks/useVerticalMenu'
|
||||
|
||||
// Util Imports
|
||||
import { renderMenuIcon } from '../../utils/menuUtils'
|
||||
import { menuClasses } from '../../utils/menuClasses'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledMenuLabel from '../../styles/StyledMenuLabel'
|
||||
import StyledMenuPrefix from '../../styles/StyledMenuPrefix'
|
||||
import StyledMenuSuffix from '../../styles/StyledMenuSuffix'
|
||||
import StyledVerticalMenuItem from '../../styles/vertical/StyledVerticalMenuItem'
|
||||
|
||||
export type MenuItemProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'prefix'> &
|
||||
RootStylesType &
|
||||
Partial<ChildrenType> &
|
||||
MenuItemExactMatchUrlProps & {
|
||||
icon?: ReactElement
|
||||
prefix?: ReactNode
|
||||
suffix?: ReactNode
|
||||
disabled?: boolean
|
||||
target?: string
|
||||
rel?: string
|
||||
onActiveChange?: (active: boolean) => void
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
level?: number
|
||||
}
|
||||
|
||||
const MenuItem: ForwardRefRenderFunction<HTMLLIElement, MenuItemProps> = (props, ref) => {
|
||||
// Props
|
||||
const {
|
||||
children,
|
||||
icon,
|
||||
className,
|
||||
prefix,
|
||||
suffix,
|
||||
level = 0,
|
||||
disabled = false,
|
||||
exactMatch = true,
|
||||
activeUrl,
|
||||
onActiveChange,
|
||||
rootStyles,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
// States
|
||||
const [active, setActive] = useState(false)
|
||||
|
||||
// Hooks
|
||||
const pathname = usePathname()
|
||||
const { menuItemStyles, renderExpandedMenuItemIcon, textTruncate } = useVerticalMenu()
|
||||
|
||||
const { toggleVerticalNav, isToggled, isBreakpointReached } = useVerticalNav()
|
||||
|
||||
// Get the styles for the specified element.
|
||||
const getMenuItemStyles = (element: MenuItemElement): CSSObject | undefined => {
|
||||
// If the menuItemStyles prop is provided, get the styles for the specified element.
|
||||
if (menuItemStyles) {
|
||||
// Define the parameters that are passed to the style functions.
|
||||
const params = { level, disabled, active, isSubmenu: false }
|
||||
|
||||
// Get the style function for the specified element.
|
||||
const styleFunction = menuItemStyles[element]
|
||||
|
||||
if (styleFunction) {
|
||||
// If the style function is a function, call it and return the result.
|
||||
// Otherwise, return the style function itself.
|
||||
return typeof styleFunction === 'function' ? styleFunction(params) : styleFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the click event.
|
||||
const handleClick = () => {
|
||||
if (isToggled) {
|
||||
toggleVerticalNav()
|
||||
}
|
||||
}
|
||||
|
||||
// Change active state when the url changes
|
||||
useEffect(() => {
|
||||
const href = rest.href
|
||||
|
||||
if (href) {
|
||||
// Check if the current url matches any of the children urls
|
||||
if (exactMatch ? pathname === href : activeUrl && pathname.includes(activeUrl)) {
|
||||
setActive(true)
|
||||
} else {
|
||||
setActive(false)
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname])
|
||||
|
||||
// Call the onActiveChange callback when the active state changes.
|
||||
useUpdateEffect(() => {
|
||||
onActiveChange?.(active)
|
||||
}, [active])
|
||||
|
||||
return (
|
||||
<StyledVerticalMenuItem
|
||||
ref={ref}
|
||||
className={classnames(
|
||||
menuClasses.menuItemRoot,
|
||||
{ [menuClasses.disabled]: disabled },
|
||||
{ [menuClasses.active]: active },
|
||||
className
|
||||
)}
|
||||
level={level}
|
||||
disabled={disabled}
|
||||
buttonStyles={getMenuItemStyles('button')}
|
||||
menuItemStyles={getMenuItemStyles('root')}
|
||||
rootStyles={rootStyles}
|
||||
>
|
||||
<MenuButton
|
||||
className={classnames(menuClasses.button, { [menuClasses.active]: active })}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
{...rest}
|
||||
onClick={e => {
|
||||
handleClick()
|
||||
rest.onClick && rest.onClick(e)
|
||||
}}
|
||||
>
|
||||
{/* Menu Item Icon */}
|
||||
{renderMenuIcon({
|
||||
icon,
|
||||
level,
|
||||
active,
|
||||
disabled,
|
||||
renderExpandedMenuItemIcon,
|
||||
styles: getMenuItemStyles('icon'),
|
||||
isBreakpointReached
|
||||
})}
|
||||
|
||||
{/* Menu Item Prefix */}
|
||||
{prefix && (
|
||||
<StyledMenuPrefix className={menuClasses.prefix} rootStyles={getMenuItemStyles('prefix')}>
|
||||
{prefix}
|
||||
</StyledMenuPrefix>
|
||||
)}
|
||||
|
||||
{/* Menu Item Label */}
|
||||
<StyledMenuLabel
|
||||
className={menuClasses.label}
|
||||
rootStyles={getMenuItemStyles('label')}
|
||||
textTruncate={textTruncate}
|
||||
>
|
||||
{children}
|
||||
</StyledMenuLabel>
|
||||
|
||||
{/* Menu Item Suffix */}
|
||||
{suffix && (
|
||||
<StyledMenuSuffix className={menuClasses.suffix} rootStyles={getMenuItemStyles('suffix')}>
|
||||
{suffix}
|
||||
</StyledMenuSuffix>
|
||||
)}
|
||||
</MenuButton>
|
||||
</StyledVerticalMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(MenuItem)
|
||||
@@ -0,0 +1,121 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { forwardRef } from 'react'
|
||||
import type { ForwardRefRenderFunction, CSSProperties, ReactElement, ReactNode } from 'react'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type { MenuSectionStyles } from './Menu'
|
||||
import type { ChildrenType, RootStylesType } from '../../types'
|
||||
|
||||
// Hook Imports
|
||||
import useVerticalMenu from '../../hooks/useVerticalMenu'
|
||||
|
||||
// Util Imports
|
||||
import { menuClasses } from '../../utils/menuClasses'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledMenuIcon from '../../styles/StyledMenuIcon'
|
||||
import StyledMenuPrefix from '../../styles/StyledMenuPrefix'
|
||||
import StyledMenuSuffix from '../../styles/StyledMenuSuffix'
|
||||
import StyledMenuSectionLabel from '../../styles/StyledMenuSectionLabel'
|
||||
import StyledVerticalMenuSection from '../../styles/vertical/StyledVerticalMenuSection'
|
||||
|
||||
export type MenuSectionProps = Partial<ChildrenType> &
|
||||
RootStylesType & {
|
||||
label: ReactNode
|
||||
icon?: ReactElement
|
||||
prefix?: ReactNode
|
||||
suffix?: ReactNode
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
className?: string
|
||||
}
|
||||
|
||||
type MenuSectionElement = keyof MenuSectionStyles
|
||||
|
||||
const menuSectionWrapperStyles: CSSProperties = {
|
||||
display: 'inline-block',
|
||||
inlineSize: '100%',
|
||||
position: 'relative',
|
||||
listStyle: 'none',
|
||||
padding: 0,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
|
||||
const menuSectionContentStyles: CSSProperties = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
inlineSize: '100%',
|
||||
position: 'relative',
|
||||
paddingBlock: '0.75rem',
|
||||
paddingInline: '1.25rem',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
|
||||
const MenuSection: ForwardRefRenderFunction<HTMLLIElement, MenuSectionProps> = (props, ref) => {
|
||||
// Props
|
||||
const { children, icon, className, prefix, suffix, label, rootStyles, ...rest } = props
|
||||
|
||||
// Hooks
|
||||
const { menuSectionStyles, textTruncate } = useVerticalMenu()
|
||||
|
||||
const getMenuSectionStyles = (element: MenuSectionElement): CSSObject | undefined => {
|
||||
// If the menuSectionStyles prop is provided, get the styles for the element from the prop
|
||||
if (menuSectionStyles) {
|
||||
return menuSectionStyles[element]
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// Menu Section
|
||||
<StyledVerticalMenuSection
|
||||
ref={ref}
|
||||
rootStyles={rootStyles}
|
||||
menuSectionStyles={getMenuSectionStyles('root')}
|
||||
className={classnames(menuClasses.menuSectionRoot, className)}
|
||||
>
|
||||
{/* Menu Section Content Wrapper */}
|
||||
<ul className={menuClasses.menuSectionWrapper} {...rest} style={menuSectionWrapperStyles}>
|
||||
{/* Menu Section Content */}
|
||||
<li className={menuClasses.menuSectionContent} style={menuSectionContentStyles}>
|
||||
{icon && (
|
||||
<StyledMenuIcon className={menuClasses.icon} rootStyles={getMenuSectionStyles('icon')}>
|
||||
{icon}
|
||||
</StyledMenuIcon>
|
||||
)}
|
||||
{prefix && (
|
||||
<StyledMenuPrefix className={menuClasses.prefix} rootStyles={getMenuSectionStyles('prefix')}>
|
||||
{prefix}
|
||||
</StyledMenuPrefix>
|
||||
)}
|
||||
{label && (
|
||||
<StyledMenuSectionLabel
|
||||
className={menuClasses.menuSectionLabel}
|
||||
rootStyles={getMenuSectionStyles('label')}
|
||||
textTruncate={textTruncate}
|
||||
>
|
||||
{label}
|
||||
</StyledMenuSectionLabel>
|
||||
)}
|
||||
{suffix && (
|
||||
<StyledMenuSuffix className={menuClasses.suffix} rootStyles={getMenuSectionStyles('suffix')}>
|
||||
{suffix}
|
||||
</StyledMenuSuffix>
|
||||
)}
|
||||
</li>
|
||||
{/* Render Child */}
|
||||
{children}
|
||||
</ul>
|
||||
</StyledVerticalMenuSection>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef<HTMLLIElement, MenuSectionProps>(MenuSection)
|
||||
@@ -0,0 +1,22 @@
|
||||
// Third-party Imports
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '../../types'
|
||||
|
||||
// Util Imports
|
||||
import { verticalNavClasses } from '../../utils/menuClasses'
|
||||
|
||||
const StyledNavHeader = styled.div`
|
||||
padding: 15px;
|
||||
padding-inline-start: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const NavHeader = ({ children }: ChildrenType) => {
|
||||
return <StyledNavHeader className={verticalNavClasses.header}>{children}</StyledNavHeader>
|
||||
}
|
||||
|
||||
export default NavHeader
|
||||
@@ -0,0 +1,332 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { Children, cloneElement, forwardRef, useEffect, useId, useRef, useState } from 'react'
|
||||
import type {
|
||||
AnchorHTMLAttributes,
|
||||
ForwardRefRenderFunction,
|
||||
KeyboardEvent,
|
||||
MouseEvent,
|
||||
ReactElement,
|
||||
ReactNode
|
||||
} from 'react'
|
||||
|
||||
// Next Imports
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import styled from '@emotion/styled'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type { OpenSubmenu } from './Menu'
|
||||
import type { MenuItemProps } from './MenuItem'
|
||||
import type { ChildrenType, RootStylesType, SubMenuItemElement } from '../../types'
|
||||
|
||||
// Component Imports
|
||||
import SubMenuContent from './SubMenuContent'
|
||||
import MenuButton, { menuButtonStyles } from './MenuButton'
|
||||
|
||||
// Icon Imports
|
||||
import ChevronRight from '../../svg/ChevronRight'
|
||||
|
||||
// Hook Imports
|
||||
import useVerticalNav from '../../hooks/useVerticalNav'
|
||||
import useVerticalMenu from '../../hooks/useVerticalMenu'
|
||||
|
||||
// Util Imports
|
||||
import { menuClasses } from '../../utils/menuClasses'
|
||||
import { confirmUrlInChildren, renderMenuIcon } from '../../utils/menuUtils'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledMenuLabel from '../../styles/StyledMenuLabel'
|
||||
import StyledMenuPrefix from '../../styles/StyledMenuPrefix'
|
||||
import StyledMenuSuffix from '../../styles/StyledMenuSuffix'
|
||||
import StyledVerticalNavExpandIcon, {
|
||||
StyledVerticalNavExpandIconWrapper
|
||||
} from '../../styles/vertical/StyledVerticalNavExpandIcon'
|
||||
|
||||
export type SubMenuProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'prefix'> &
|
||||
RootStylesType &
|
||||
Partial<ChildrenType> & {
|
||||
label: ReactNode
|
||||
icon?: ReactElement
|
||||
prefix?: ReactNode
|
||||
suffix?: ReactNode
|
||||
defaultOpen?: boolean
|
||||
disabled?: boolean
|
||||
contentClassName?: string
|
||||
onOpenChange?: (open: boolean) => void
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
level?: number
|
||||
}
|
||||
|
||||
type StyledSubMenuProps = Pick<SubMenuProps, 'rootStyles' | 'disabled'> & {
|
||||
level: number
|
||||
active?: boolean
|
||||
menuItemStyles?: CSSObject
|
||||
buttonStyles?: CSSObject
|
||||
}
|
||||
|
||||
const StyledSubMenu = styled.li<StyledSubMenuProps>`
|
||||
position: relative;
|
||||
inline-size: 100%;
|
||||
margin-block-start: 4px;
|
||||
|
||||
&.${menuClasses.open} > .${menuClasses.button} {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
${({ menuItemStyles }) => menuItemStyles};
|
||||
${({ rootStyles }) => rootStyles};
|
||||
|
||||
> .${menuClasses.button} {
|
||||
${({ level, disabled, active, children }) =>
|
||||
menuButtonStyles({
|
||||
level,
|
||||
active,
|
||||
disabled,
|
||||
children
|
||||
})};
|
||||
${({ buttonStyles }) => buttonStyles};
|
||||
}
|
||||
`
|
||||
|
||||
const SubMenu: ForwardRefRenderFunction<HTMLLIElement, SubMenuProps> = (props, ref) => {
|
||||
// Props
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
contentClassName,
|
||||
label,
|
||||
icon,
|
||||
title,
|
||||
prefix,
|
||||
suffix,
|
||||
defaultOpen,
|
||||
level = 0,
|
||||
disabled = false,
|
||||
rootStyles,
|
||||
onOpenChange,
|
||||
onClick,
|
||||
onKeyUp,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
// States
|
||||
const [active, setActive] = useState<boolean>(false)
|
||||
|
||||
// Refs
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Hooks
|
||||
const id = useId()
|
||||
const pathname = usePathname()
|
||||
const { isBreakpointReached } = useVerticalNav()
|
||||
|
||||
const {
|
||||
renderExpandIcon,
|
||||
renderExpandedMenuItemIcon,
|
||||
menuItemStyles,
|
||||
openSubmenu,
|
||||
toggleOpenSubmenu,
|
||||
transitionDuration,
|
||||
openSubmenusRef,
|
||||
textTruncate
|
||||
} = useVerticalMenu()
|
||||
|
||||
// Vars
|
||||
// Filter out falsy values from children
|
||||
const childNodes = Children.toArray(children).filter(Boolean) as [ReactElement<SubMenuProps | MenuItemProps>]
|
||||
|
||||
const isSubMenuOpen = openSubmenu?.some((item: OpenSubmenu) => item.id === id) ?? false
|
||||
|
||||
const handleSlideToggle = (): void => {
|
||||
toggleOpenSubmenu?.({ level, label, active, id })
|
||||
onOpenChange?.(!isSubMenuOpen)
|
||||
if (openSubmenusRef?.current && openSubmenusRef?.current.length > 0) openSubmenusRef.current = []
|
||||
}
|
||||
|
||||
const handleOnClick = (event: MouseEvent<HTMLAnchorElement, globalThis.MouseEvent>) => {
|
||||
onClick?.(event)
|
||||
handleSlideToggle()
|
||||
}
|
||||
|
||||
const handleOnKeyUp = (event: KeyboardEvent<HTMLAnchorElement>) => {
|
||||
onKeyUp?.(event)
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
handleSlideToggle()
|
||||
}
|
||||
}
|
||||
|
||||
const getSubMenuItemStyles = (element: SubMenuItemElement): CSSObject | undefined => {
|
||||
// If the menuItemStyles prop is provided, get the styles for the specified element.
|
||||
if (menuItemStyles) {
|
||||
// Define the parameters that are passed to the style functions.
|
||||
const params = {
|
||||
level,
|
||||
disabled,
|
||||
active,
|
||||
isSubmenu: true,
|
||||
open: isSubMenuOpen
|
||||
}
|
||||
|
||||
// Get the style function for the specified element.
|
||||
const styleFunction = menuItemStyles[element]
|
||||
|
||||
if (styleFunction) {
|
||||
// If the style function is a function, call it and return the result.
|
||||
// Otherwise, return the style function itself.
|
||||
return typeof styleFunction === 'function' ? styleFunction(params) : styleFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (confirmUrlInChildren(children, pathname)) {
|
||||
openSubmenusRef?.current.push({ level, label, active: true, id })
|
||||
} else {
|
||||
if (defaultOpen) {
|
||||
openSubmenusRef?.current.push({ level, label, active: false, id })
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
// Change active state when the url changes
|
||||
useEffect(() => {
|
||||
// Check if the current url matches any of the children urls
|
||||
if (confirmUrlInChildren(children, pathname)) {
|
||||
setActive(true)
|
||||
|
||||
if (openSubmenusRef?.current.findIndex(submenu => submenu.id === id) === -1) {
|
||||
openSubmenusRef?.current.push({ level, label, active: true, id })
|
||||
}
|
||||
} else {
|
||||
setActive(false)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname])
|
||||
|
||||
/* useEffect(() => {
|
||||
console.log(openSubmenu)
|
||||
}, [openSubmenu]) */
|
||||
|
||||
const submenuContent = (
|
||||
<SubMenuContent
|
||||
ref={contentRef}
|
||||
transitionDuration={transitionDuration}
|
||||
open={isSubMenuOpen}
|
||||
level={level}
|
||||
className={classnames(menuClasses.subMenuContent, contentClassName)}
|
||||
rootStyles={{
|
||||
...getSubMenuItemStyles('subMenuContent')
|
||||
}}
|
||||
>
|
||||
{childNodes.map(node =>
|
||||
cloneElement(node, {
|
||||
level: level + 1
|
||||
})
|
||||
)}
|
||||
</SubMenuContent>
|
||||
)
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
/* Sub Menu */
|
||||
<StyledSubMenu
|
||||
ref={ref}
|
||||
className={classnames(
|
||||
menuClasses.subMenuRoot,
|
||||
{ [menuClasses.active]: active },
|
||||
{ [menuClasses.disabled]: disabled },
|
||||
{ [menuClasses.open]: isSubMenuOpen },
|
||||
className
|
||||
)}
|
||||
menuItemStyles={getSubMenuItemStyles('root')}
|
||||
level={level}
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
buttonStyles={getSubMenuItemStyles('button')}
|
||||
rootStyles={rootStyles}
|
||||
>
|
||||
{/* Menu Item */}
|
||||
<MenuButton
|
||||
ref={null}
|
||||
onClick={handleOnClick}
|
||||
onKeyUp={handleOnKeyUp}
|
||||
title={title}
|
||||
className={classnames(menuClasses.button, { [menuClasses.active]: active })}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
{...rest}
|
||||
>
|
||||
{/* Sub Menu Icon */}
|
||||
{renderMenuIcon({
|
||||
icon,
|
||||
level,
|
||||
active,
|
||||
disabled,
|
||||
renderExpandedMenuItemIcon,
|
||||
styles: getSubMenuItemStyles('icon'),
|
||||
isBreakpointReached
|
||||
})}
|
||||
|
||||
{/* Sub Menu Prefix */}
|
||||
{prefix && (
|
||||
<StyledMenuPrefix className={menuClasses.prefix} rootStyles={getSubMenuItemStyles('prefix')}>
|
||||
{prefix}
|
||||
</StyledMenuPrefix>
|
||||
)}
|
||||
|
||||
{/* Sub Menu Label */}
|
||||
<StyledMenuLabel
|
||||
className={menuClasses.label}
|
||||
rootStyles={getSubMenuItemStyles('label')}
|
||||
textTruncate={textTruncate}
|
||||
>
|
||||
{label}
|
||||
</StyledMenuLabel>
|
||||
|
||||
{/* Sub Menu Suffix */}
|
||||
{suffix && (
|
||||
<StyledMenuSuffix className={menuClasses.suffix} rootStyles={getSubMenuItemStyles('suffix')}>
|
||||
{suffix}
|
||||
</StyledMenuSuffix>
|
||||
)}
|
||||
|
||||
{/* Sub Menu Toggle Icon Wrapper */}
|
||||
{
|
||||
<StyledVerticalNavExpandIconWrapper
|
||||
className={menuClasses.subMenuExpandIcon}
|
||||
rootStyles={getSubMenuItemStyles('subMenuExpandIcon')}
|
||||
>
|
||||
{renderExpandIcon ? (
|
||||
renderExpandIcon({
|
||||
level,
|
||||
disabled,
|
||||
active,
|
||||
open: isSubMenuOpen
|
||||
})
|
||||
) : (
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
/* Expanded Arrow Icon */
|
||||
<StyledVerticalNavExpandIcon open={isSubMenuOpen} transitionDuration={transitionDuration}>
|
||||
<ChevronRight fontSize='1rem' />
|
||||
</StyledVerticalNavExpandIcon>
|
||||
)}
|
||||
</StyledVerticalNavExpandIconWrapper>
|
||||
}
|
||||
</MenuButton>
|
||||
|
||||
{/* Sub Menu Content */}
|
||||
{submenuContent}
|
||||
</StyledSubMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef<HTMLLIElement, SubMenuProps>(SubMenu)
|
||||
@@ -0,0 +1,84 @@
|
||||
// React Imports
|
||||
import { forwardRef, useEffect, useState } from 'react'
|
||||
import type { ForwardRefRenderFunction, HTMLAttributes, MutableRefObject } from 'react'
|
||||
|
||||
// Type Imports
|
||||
import type { VerticalMenuContextProps } from './Menu'
|
||||
import type { ChildrenType, RootStylesType } from '../../types'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledSubMenuContent from '../../styles/StyledSubMenuContent'
|
||||
|
||||
// Style Imports
|
||||
import styles from '../../styles/styles.module.css'
|
||||
|
||||
export type SubMenuContentProps = HTMLAttributes<HTMLDivElement> &
|
||||
RootStylesType &
|
||||
Partial<ChildrenType> & {
|
||||
open?: boolean
|
||||
transitionDuration?: VerticalMenuContextProps['transitionDuration']
|
||||
level?: number
|
||||
}
|
||||
|
||||
const SubMenuContent: ForwardRefRenderFunction<HTMLDivElement, SubMenuContentProps> = (props, ref) => {
|
||||
// Props
|
||||
const { children, open, level, transitionDuration, ...rest } = props
|
||||
|
||||
// States
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
// Refs
|
||||
const SubMenuContentRef = ref as MutableRefObject<HTMLDivElement>
|
||||
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
if (open) {
|
||||
const target = SubMenuContentRef?.current
|
||||
|
||||
if (target) {
|
||||
target.style.display = 'block'
|
||||
target.style.overflow = 'hidden'
|
||||
target.style.blockSize = 'auto'
|
||||
const height = target.offsetHeight
|
||||
|
||||
target.style.blockSize = '0px'
|
||||
target.offsetHeight
|
||||
|
||||
target.style.blockSize = `${height}px`
|
||||
|
||||
setTimeout(() => {
|
||||
target.style.overflow = 'auto'
|
||||
target.style.blockSize = 'auto'
|
||||
}, transitionDuration)
|
||||
}
|
||||
} else {
|
||||
const target = SubMenuContentRef?.current
|
||||
|
||||
if (target) {
|
||||
target.style.overflow = 'hidden'
|
||||
target.style.blockSize = `${target.offsetHeight}px`
|
||||
target.offsetHeight
|
||||
target.style.blockSize = '0px'
|
||||
|
||||
setTimeout(() => {
|
||||
target.style.overflow = 'auto'
|
||||
target.style.display = 'none'
|
||||
}, transitionDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open, mounted, SubMenuContentRef])
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<StyledSubMenuContent ref={ref} level={level} open={open} transitionDuration={transitionDuration} {...rest}>
|
||||
<ul className={styles.ul}>{children}</ul>
|
||||
</StyledSubMenuContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(SubMenuContent)
|
||||
@@ -0,0 +1,138 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useEffect } from 'react'
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import type { CSSObject } from '@emotion/styled'
|
||||
|
||||
// Type Imports
|
||||
import type { BreakpointType } from '../../types'
|
||||
|
||||
// Context Imports
|
||||
import type { VerticalNavState } from '../../contexts/verticalNavContext'
|
||||
|
||||
// Hook Imports
|
||||
import useMediaQuery from '../../hooks/useMediaQuery'
|
||||
import useVerticalNav from '../../hooks/useVerticalNav'
|
||||
|
||||
// Util Imports
|
||||
import { verticalNavClasses } from '../../utils/menuClasses'
|
||||
|
||||
// Styled Component Imports
|
||||
import StyledBackdrop from '../../styles/StyledBackdrop'
|
||||
import StyledVerticalNav from '../../styles/vertical/StyledVerticalNav'
|
||||
import StyledVerticalNavContainer from '../../styles/vertical/StyledVerticalNavContainer'
|
||||
import StyledVerticalNavBgColorContainer from '../../styles/vertical/StyledVerticalNavBgColorContainer'
|
||||
|
||||
// Default Config Imports
|
||||
import { defaultBreakpoints, verticalNavToggleDuration } from '../../defaultConfigs'
|
||||
|
||||
export type VerticalNavProps = HTMLAttributes<HTMLHtmlElement> & {
|
||||
width?: VerticalNavState['width']
|
||||
breakpoint?: BreakpointType
|
||||
customBreakpoint?: string
|
||||
breakpoints?: Partial<typeof defaultBreakpoints>
|
||||
transitionDuration?: VerticalNavState['transitionDuration']
|
||||
backdropColor?: string
|
||||
customStyles?: CSSObject
|
||||
}
|
||||
|
||||
const VerticalNav = (props: VerticalNavProps) => {
|
||||
// Props
|
||||
const {
|
||||
width = 260,
|
||||
breakpoint = 'lg',
|
||||
customBreakpoint,
|
||||
breakpoints,
|
||||
transitionDuration = verticalNavToggleDuration,
|
||||
backdropColor,
|
||||
className,
|
||||
customStyles,
|
||||
children,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
// Vars
|
||||
const mergedBreakpoints = { ...defaultBreakpoints, ...breakpoints }
|
||||
|
||||
// Hooks
|
||||
const {
|
||||
updateVerticalNavState,
|
||||
width: widthContext,
|
||||
isBreakpointReached: isBreakpointReachedContext,
|
||||
isToggled: isToggledContext,
|
||||
transitionDuration: transitionDurationContext
|
||||
} = useVerticalNav()
|
||||
|
||||
// Find the breakpoint from which screen size responsive behavior should enable and if its reached or not
|
||||
const breakpointReached = useMediaQuery(customBreakpoint ?? (breakpoint ? mergedBreakpoints[breakpoint] : breakpoint))
|
||||
|
||||
// UseEffect, update verticalNav state to set initial values and update values on change
|
||||
useEffect(() => {
|
||||
updateVerticalNavState({
|
||||
width,
|
||||
transitionDuration,
|
||||
isBreakpointReached: breakpointReached
|
||||
})
|
||||
|
||||
if (!breakpointReached) {
|
||||
updateVerticalNavState({ isToggled: false })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [width, breakpointReached, updateVerticalNavState])
|
||||
|
||||
// Handle Backdrop(Content Overlay) Click
|
||||
const handleBackdropClick = () => {
|
||||
// Close the verticalNav
|
||||
updateVerticalNavState({ isToggled: false })
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledVerticalNav
|
||||
width={width}
|
||||
isBreakpointReached={isBreakpointReachedContext}
|
||||
customStyles={customStyles}
|
||||
transitionDuration={transitionDurationContext}
|
||||
className={classnames(
|
||||
verticalNavClasses.root,
|
||||
{
|
||||
[verticalNavClasses.toggled]: isToggledContext,
|
||||
[verticalNavClasses.breakpointReached]: isBreakpointReachedContext
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<StyledVerticalNavContainer
|
||||
width={widthContext}
|
||||
className={verticalNavClasses.container}
|
||||
transitionDuration={transitionDurationContext}
|
||||
>
|
||||
{/* VerticalNav Container to apply styling like background */}
|
||||
<StyledVerticalNavBgColorContainer className={verticalNavClasses.bgColorContainer}>
|
||||
{children}
|
||||
</StyledVerticalNavBgColorContainer>
|
||||
</StyledVerticalNavContainer>
|
||||
|
||||
{/* When verticalNav is toggled on smaller screen, show/hide verticalNav backdrop */}
|
||||
{isToggledContext && breakpointReached && (
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
/* VerticalNav Backdrop */
|
||||
<StyledBackdrop
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label='backdrop'
|
||||
onClick={handleBackdropClick}
|
||||
onKeyPress={handleBackdropClick}
|
||||
className={verticalNavClasses.backdrop}
|
||||
backdropColor={backdropColor}
|
||||
/>
|
||||
)}
|
||||
</StyledVerticalNav>
|
||||
)
|
||||
}
|
||||
|
||||
export default VerticalNav
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { createContext, useCallback, useMemo, useState } from 'react'
|
||||
|
||||
// Type Imports
|
||||
import type { ChildrenType } from '../types'
|
||||
|
||||
export type VerticalNavState = {
|
||||
width?: number
|
||||
isToggled?: boolean
|
||||
isBreakpointReached?: boolean
|
||||
transitionDuration?: number
|
||||
}
|
||||
|
||||
export type VerticalNavContextProps = VerticalNavState & {
|
||||
updateVerticalNavState: (values: VerticalNavState) => void
|
||||
toggleVerticalNav: (value?: VerticalNavState['isToggled']) => void
|
||||
}
|
||||
|
||||
const VerticalNavContext = createContext({} as VerticalNavContextProps)
|
||||
|
||||
export const VerticalNavProvider = ({ children }: ChildrenType) => {
|
||||
// States
|
||||
const [verticalNavState, setVerticalNavState] = useState<VerticalNavState>()
|
||||
|
||||
// Hooks
|
||||
const updateVerticalNavState = useCallback((values: Partial<VerticalNavState>) => {
|
||||
setVerticalNavState(prevState => ({
|
||||
...prevState,
|
||||
...values
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const toggleVerticalNav = useCallback((value?: boolean) => {
|
||||
setVerticalNavState(prevState => ({
|
||||
...prevState,
|
||||
isToggled: value !== undefined ? Boolean(value) : !Boolean(prevState?.isToggled)
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const verticalNavProviderValue = useMemo(
|
||||
() => ({
|
||||
...verticalNavState,
|
||||
updateVerticalNavState,
|
||||
toggleVerticalNav
|
||||
}),
|
||||
[verticalNavState, updateVerticalNavState, toggleVerticalNav]
|
||||
)
|
||||
|
||||
return <VerticalNavContext.Provider value={verticalNavProviderValue}>{children}</VerticalNavContext.Provider>
|
||||
}
|
||||
|
||||
export default VerticalNavContext
|
||||
@@ -0,0 +1,15 @@
|
||||
// Type Imports
|
||||
import type { BreakpointType } from './types'
|
||||
|
||||
export const defaultBreakpoints: Record<BreakpointType, string> = {
|
||||
xs: '480px',
|
||||
sm: '600px',
|
||||
md: '900px',
|
||||
lg: '1200px',
|
||||
xl: '1536px',
|
||||
xxl: '1920px',
|
||||
always: 'always'
|
||||
}
|
||||
|
||||
export const verticalNavToggleDuration = 300
|
||||
export const verticalSubMenuToggleDuration = 300
|
||||
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const useMediaQuery = (breakpoint?: string): boolean => {
|
||||
// States
|
||||
const [matches, setMatches] = useState(breakpoint === 'always')
|
||||
|
||||
useEffect(() => {
|
||||
if (breakpoint && breakpoint !== 'always') {
|
||||
const media = window.matchMedia(`(max-width: ${breakpoint})`)
|
||||
|
||||
if (media.matches !== matches) {
|
||||
setMatches(media.matches)
|
||||
}
|
||||
|
||||
const listener = () => setMatches(media.matches)
|
||||
|
||||
window.addEventListener('resize', listener)
|
||||
|
||||
return () => window.removeEventListener('resize', listener)
|
||||
}
|
||||
}, [matches, breakpoint])
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
export default useMediaQuery
|
||||
@@ -0,0 +1,21 @@
|
||||
// React Imports
|
||||
import { useContext } from 'react'
|
||||
|
||||
// Type Imports
|
||||
import type { VerticalMenuContextProps } from '../components/vertical-menu/Menu'
|
||||
|
||||
// Context Imports
|
||||
import { VerticalMenuContext } from '../components/vertical-menu/Menu'
|
||||
|
||||
const useVerticalMenu = (): VerticalMenuContextProps => {
|
||||
// Hooks
|
||||
const context = useContext(VerticalMenuContext)
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('Menu Component is required!')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export default useVerticalMenu
|
||||
@@ -0,0 +1,18 @@
|
||||
// React Imports
|
||||
import { useContext } from 'react'
|
||||
|
||||
// Context Imports
|
||||
import VerticalNavContext from '../contexts/verticalNavContext'
|
||||
|
||||
const useVerticalNav = () => {
|
||||
// Hooks
|
||||
const context = useContext(VerticalNavContext)
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('VerticalNav Component is required!')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export default useVerticalNav
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user