forked from mirrors/thatmattlove-hyperglass
Improve form styles
This commit is contained in:
parent
55a9918fd0
commit
7c1a5bf1c3
8 changed files with 162 additions and 126 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
"""Validate branding configuration variables."""
|
"""Validate branding configuration variables."""
|
||||||
|
|
||||||
# Standard Library
|
# Standard Library
|
||||||
from typing import Union, Optional, Sequence
|
import typing as t
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Third Party
|
# Third Party
|
||||||
|
|
@ -33,13 +33,14 @@ ColorMode = constr(regex=r"light|dark")
|
||||||
DOHProvider = constr(regex="|".join(DNS_OVER_HTTPS.keys()))
|
DOHProvider = constr(regex="|".join(DNS_OVER_HTTPS.keys()))
|
||||||
Title = constr(max_length=32)
|
Title = constr(max_length=32)
|
||||||
Side = constr(regex=r"left|right")
|
Side = constr(regex=r"left|right")
|
||||||
|
LocationDisplayMode = t.Literal["auto", "dropdown", "gallery"]
|
||||||
|
|
||||||
|
|
||||||
class Analytics(HyperglassModel):
|
class Analytics(HyperglassModel):
|
||||||
"""Validation model for Google Analytics."""
|
"""Validation model for Google Analytics."""
|
||||||
|
|
||||||
enable: StrictBool = False
|
enable: StrictBool = False
|
||||||
id: Optional[StrictStr]
|
id: t.Optional[StrictStr]
|
||||||
|
|
||||||
@validator("id")
|
@validator("id")
|
||||||
def validate_id(cls, value, values):
|
def validate_id(cls, value, values):
|
||||||
|
|
@ -102,7 +103,7 @@ class Greeting(HyperglassModel):
|
||||||
"""Validation model for greeting modal."""
|
"""Validation model for greeting modal."""
|
||||||
|
|
||||||
enable: StrictBool = False
|
enable: StrictBool = False
|
||||||
file: Optional[FilePath]
|
file: t.Optional[FilePath]
|
||||||
title: StrictStr = "Welcome"
|
title: StrictStr = "Welcome"
|
||||||
button: StrictStr = "Continue"
|
button: StrictStr = "Continue"
|
||||||
required: StrictBool = False
|
required: StrictBool = False
|
||||||
|
|
@ -121,8 +122,8 @@ class Logo(HyperglassModel):
|
||||||
light: FilePath = DEFAULT_IMAGES / "hyperglass-light.svg"
|
light: FilePath = DEFAULT_IMAGES / "hyperglass-light.svg"
|
||||||
dark: FilePath = DEFAULT_IMAGES / "hyperglass-dark.svg"
|
dark: FilePath = DEFAULT_IMAGES / "hyperglass-dark.svg"
|
||||||
favicon: FilePath = DEFAULT_IMAGES / "hyperglass-icon.svg"
|
favicon: FilePath = DEFAULT_IMAGES / "hyperglass-icon.svg"
|
||||||
width: Optional[Union[StrictInt, Percentage]] = "100%"
|
width: t.Optional[t.Union[StrictInt, Percentage]] = "100%"
|
||||||
height: Optional[Union[StrictInt, Percentage]]
|
height: t.Optional[t.Union[StrictInt, Percentage]]
|
||||||
|
|
||||||
|
|
||||||
class LogoPublic(Logo):
|
class LogoPublic(Logo):
|
||||||
|
|
@ -188,12 +189,12 @@ class ThemeColors(HyperglassModel):
|
||||||
cyan: Color = "#118ab2"
|
cyan: Color = "#118ab2"
|
||||||
pink: Color = "#f2607d"
|
pink: Color = "#f2607d"
|
||||||
purple: Color = "#8d30b5"
|
purple: Color = "#8d30b5"
|
||||||
primary: Optional[Color]
|
primary: t.Optional[Color]
|
||||||
secondary: Optional[Color]
|
secondary: t.Optional[Color]
|
||||||
success: Optional[Color]
|
success: t.Optional[Color]
|
||||||
warning: Optional[Color]
|
warning: t.Optional[Color]
|
||||||
error: Optional[Color]
|
error: t.Optional[Color]
|
||||||
danger: Optional[Color]
|
danger: t.Optional[Color]
|
||||||
|
|
||||||
@validator(*FUNC_COLOR_MAP.keys(), pre=True, always=True)
|
@validator(*FUNC_COLOR_MAP.keys(), pre=True, always=True)
|
||||||
def validate_colors(cls, value, values, field):
|
def validate_colors(cls, value, values, field):
|
||||||
|
|
@ -226,7 +227,7 @@ class Theme(HyperglassModel):
|
||||||
"""Validation model for theme variables."""
|
"""Validation model for theme variables."""
|
||||||
|
|
||||||
colors: ThemeColors = ThemeColors()
|
colors: ThemeColors = ThemeColors()
|
||||||
default_color_mode: Optional[ColorMode]
|
default_color_mode: t.Optional[ColorMode]
|
||||||
fonts: ThemeFonts = ThemeFonts()
|
fonts: ThemeFonts = ThemeFonts()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -256,10 +257,10 @@ class Web(HyperglassModel):
|
||||||
|
|
||||||
credit: Credit = Credit()
|
credit: Credit = Credit()
|
||||||
dns_provider: DnsOverHttps = DnsOverHttps()
|
dns_provider: DnsOverHttps = DnsOverHttps()
|
||||||
links: Sequence[Link] = [
|
links: t.Sequence[Link] = [
|
||||||
Link(title="PeeringDB", url="https://www.peeringdb.com/asn/{primary_asn}")
|
Link(title="PeeringDB", url="https://www.peeringdb.com/asn/{primary_asn}")
|
||||||
]
|
]
|
||||||
menus: Sequence[Menu] = [
|
menus: t.Sequence[Menu] = [
|
||||||
Menu(title="Terms", content=DEFAULT_TERMS),
|
Menu(title="Terms", content=DEFAULT_TERMS),
|
||||||
Menu(title="Help", content=DEFAULT_HELP),
|
Menu(title="Help", content=DEFAULT_HELP),
|
||||||
]
|
]
|
||||||
|
|
@ -268,6 +269,7 @@ class Web(HyperglassModel):
|
||||||
opengraph: OpenGraph = OpenGraph()
|
opengraph: OpenGraph = OpenGraph()
|
||||||
text: Text = Text()
|
text: Text = Text()
|
||||||
theme: Theme = Theme()
|
theme: Theme = Theme()
|
||||||
|
location_display_mode: LocationDisplayMode = "auto"
|
||||||
|
|
||||||
|
|
||||||
class WebPublic(Web):
|
class WebPublic(Web):
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,14 @@ export const FormField: React.FC<TField> = (props: TField) => {
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<FormLabel
|
<FormLabel
|
||||||
pl={1}
|
|
||||||
pr={0}
|
pr={0}
|
||||||
|
mb={{ lg: 4 }}
|
||||||
htmlFor={name}
|
htmlFor={name}
|
||||||
display="flex"
|
display="flex"
|
||||||
opacity={opacity}
|
opacity={opacity}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
|
fontWeight="bold"
|
||||||
color={error !== null ? errorColor : labelColor}
|
color={error !== null ? errorColor : labelColor}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
|
|
|
||||||
99
hyperglass/ui/components/form/location-card.tsx
Normal file
99
hyperglass/ui/components/form/location-card.tsx
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { Flex, Box, Avatar, chakra } from '@chakra-ui/react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useColorValue } from '~/context';
|
||||||
|
import { useOpposingColor } from '~/hooks';
|
||||||
|
|
||||||
|
import type { LocationOption } from './queryLocation';
|
||||||
|
import type { LocationCardProps } from './types';
|
||||||
|
|
||||||
|
const MotionBox = motion(Box);
|
||||||
|
|
||||||
|
export const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
|
const { option, onChange, defaultChecked, hasError } = props;
|
||||||
|
const { label } = option;
|
||||||
|
const [isChecked, setChecked] = useState(defaultChecked);
|
||||||
|
|
||||||
|
function handleChange(value: LocationOption) {
|
||||||
|
if (isChecked) {
|
||||||
|
setChecked(false);
|
||||||
|
onChange('remove', value);
|
||||||
|
} else {
|
||||||
|
setChecked(true);
|
||||||
|
onChange('add', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bg = useColorValue('white', 'blackSolid.800');
|
||||||
|
const imageBorder = useColorValue('gray.600', 'whiteAlpha.800');
|
||||||
|
const fg = useOpposingColor(bg);
|
||||||
|
const checkedBorder = useColorValue('blue.400', 'blue.300');
|
||||||
|
const errorBorder = useColorValue('red.500', 'red.300');
|
||||||
|
|
||||||
|
const borderColor = useMemo(
|
||||||
|
() =>
|
||||||
|
hasError && isChecked
|
||||||
|
? // Highlight red when there are no overlapping query types for the locations selected.
|
||||||
|
errorBorder
|
||||||
|
: isChecked && !hasError
|
||||||
|
? // Highlight blue when any location is selected and there is no error.
|
||||||
|
checkedBorder
|
||||||
|
: // Otherwise, no border.
|
||||||
|
'transparent',
|
||||||
|
|
||||||
|
[hasError, isChecked, checkedBorder, errorBorder],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<MotionBox
|
||||||
|
py={4}
|
||||||
|
px={6}
|
||||||
|
bg={bg}
|
||||||
|
w="100%"
|
||||||
|
minW="xs"
|
||||||
|
maxW="sm"
|
||||||
|
mx="auto"
|
||||||
|
shadow="sm"
|
||||||
|
key={label}
|
||||||
|
rounded="lg"
|
||||||
|
cursor="pointer"
|
||||||
|
borderWidth="1px"
|
||||||
|
borderStyle="solid"
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
borderColor={borderColor}
|
||||||
|
onClick={(e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleChange(option);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex justifyContent="space-between" alignItems="center">
|
||||||
|
<chakra.h2
|
||||||
|
color={fg}
|
||||||
|
fontWeight="bold"
|
||||||
|
mt={{ base: 2, md: 0 }}
|
||||||
|
fontSize={{ base: 'lg', md: 'xl' }}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</chakra.h2>
|
||||||
|
<Avatar
|
||||||
|
color={fg}
|
||||||
|
fit="cover"
|
||||||
|
alt={label}
|
||||||
|
name={label}
|
||||||
|
boxSize={12}
|
||||||
|
rounded="full"
|
||||||
|
borderWidth={1}
|
||||||
|
bg="whiteAlpha.300"
|
||||||
|
borderStyle="solid"
|
||||||
|
borderColor={imageBorder}
|
||||||
|
src={(option.data?.avatar as string) ?? undefined}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{option?.data?.description && (
|
||||||
|
<chakra.p mt={2} color={fg} opacity={0.6} fontSize="sm">
|
||||||
|
{option.data.description as string}
|
||||||
|
</chakra.p>
|
||||||
|
)}
|
||||||
|
</MotionBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Wrap, VStack, Flex, Box, Avatar, chakra } from '@chakra-ui/react';
|
import { Wrap, VStack, Flex, chakra } from '@chakra-ui/react';
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { Select } from '~/components';
|
import { Select } from '~/components';
|
||||||
import { useConfig, useColorValue } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { useOpposingColor, useFormState } from '~/hooks';
|
import { useFormState } from '~/hooks';
|
||||||
import { isMultiValue, isSingleValue } from '~/components/select';
|
import { isMultiValue, isSingleValue } from '~/components/select';
|
||||||
|
import { LocationCard } from './location-card';
|
||||||
|
|
||||||
import type { DeviceGroup, SingleOption, OptionGroup, FormData } from '~/types';
|
import type { DeviceGroup, SingleOption, OptionGroup, FormData } from '~/types';
|
||||||
import type { SelectOnChange } from '~/components/select';
|
import type { SelectOnChange } from '~/components/select';
|
||||||
import type { TQuerySelectField, LocationCardProps } from './types';
|
import type { TQuerySelectField } from './types';
|
||||||
|
|
||||||
/** Location option type alias for future extensions. */
|
/** Location option type alias for future extensions. */
|
||||||
type LocationOption = SingleOption;
|
export type LocationOption = SingleOption;
|
||||||
|
|
||||||
function buildOptions(devices: DeviceGroup[]): OptionGroup<LocationOption>[] {
|
function buildOptions(devices: DeviceGroup[]): OptionGroup<LocationOption>[] {
|
||||||
return devices
|
return devices
|
||||||
|
|
@ -37,101 +37,13 @@ function buildOptions(devices: DeviceGroup[]): OptionGroup<LocationOption>[] {
|
||||||
.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
|
.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
const MotionBox = motion(Box);
|
|
||||||
|
|
||||||
const LocationCard = (props: LocationCardProps): JSX.Element => {
|
|
||||||
const { option, onChange, defaultChecked, hasError } = props;
|
|
||||||
const { label } = option;
|
|
||||||
const [isChecked, setChecked] = useState(defaultChecked);
|
|
||||||
|
|
||||||
function handleChange(value: LocationOption) {
|
|
||||||
if (isChecked) {
|
|
||||||
setChecked(false);
|
|
||||||
onChange('remove', value);
|
|
||||||
} else {
|
|
||||||
setChecked(true);
|
|
||||||
onChange('add', value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bg = useColorValue('whiteAlpha.300', 'blackSolid.700');
|
|
||||||
const imageBorder = useColorValue('gray.600', 'whiteAlpha.800');
|
|
||||||
const fg = useOpposingColor(bg);
|
|
||||||
const checkedBorder = useColorValue('blue.400', 'blue.300');
|
|
||||||
const errorBorder = useColorValue('red.500', 'red.300');
|
|
||||||
|
|
||||||
const borderColor = useMemo(
|
|
||||||
() =>
|
|
||||||
hasError && isChecked
|
|
||||||
? // Highlight red when there are no overlapping query types for the locations selected.
|
|
||||||
errorBorder
|
|
||||||
: isChecked && !hasError
|
|
||||||
? // Highlight blue when any location is selected and there is no error.
|
|
||||||
checkedBorder
|
|
||||||
: // Otherwise, no border.
|
|
||||||
'transparent',
|
|
||||||
|
|
||||||
[hasError, isChecked, checkedBorder, errorBorder],
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<MotionBox
|
|
||||||
py={4}
|
|
||||||
px={6}
|
|
||||||
bg={bg}
|
|
||||||
w="100%"
|
|
||||||
minW="xs"
|
|
||||||
maxW="sm"
|
|
||||||
mx="auto"
|
|
||||||
shadow="lg"
|
|
||||||
key={label}
|
|
||||||
rounded="lg"
|
|
||||||
cursor="pointer"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderStyle="solid"
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
borderColor={borderColor}
|
|
||||||
onClick={(e: React.MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleChange(option);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex justifyContent="space-between" alignItems="center">
|
|
||||||
<chakra.h2
|
|
||||||
color={fg}
|
|
||||||
fontWeight="bold"
|
|
||||||
mt={{ base: 2, md: 0 }}
|
|
||||||
fontSize={{ base: 'lg', md: 'xl' }}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</chakra.h2>
|
|
||||||
<Avatar
|
|
||||||
color={fg}
|
|
||||||
fit="cover"
|
|
||||||
alt={label}
|
|
||||||
name={label}
|
|
||||||
boxSize={12}
|
|
||||||
rounded="full"
|
|
||||||
borderWidth={1}
|
|
||||||
bg="whiteAlpha.300"
|
|
||||||
borderStyle="solid"
|
|
||||||
borderColor={imageBorder}
|
|
||||||
src={(option.data?.avatar as string) ?? undefined}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{option?.data?.description && (
|
|
||||||
<chakra.p mt={2} color={fg} opacity={0.6} fontSize="sm">
|
|
||||||
{option.data.description as string}
|
|
||||||
</chakra.p>
|
|
||||||
)}
|
|
||||||
</MotionBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const QueryLocation = (props: TQuerySelectField): JSX.Element => {
|
export const QueryLocation = (props: TQuerySelectField): JSX.Element => {
|
||||||
const { onChange, label } = props;
|
const { onChange, label } = props;
|
||||||
|
|
||||||
const { devices } = useConfig();
|
const {
|
||||||
|
devices,
|
||||||
|
web: { locationDisplayMode },
|
||||||
|
} = useConfig();
|
||||||
const {
|
const {
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext<FormData>();
|
} = useFormContext<FormData>();
|
||||||
|
|
@ -139,12 +51,18 @@ export const QueryLocation = (props: TQuerySelectField): JSX.Element => {
|
||||||
const setSelection = useFormState(s => s.setSelection);
|
const setSelection = useFormState(s => s.setSelection);
|
||||||
const { form, filtered } = useFormState(({ form, filtered }) => ({ form, filtered }));
|
const { form, filtered } = useFormState(({ form, filtered }) => ({ form, filtered }));
|
||||||
const options = useMemo(() => buildOptions(devices), [devices]);
|
const options = useMemo(() => buildOptions(devices), [devices]);
|
||||||
|
|
||||||
const element = useMemo(() => {
|
const element = useMemo(() => {
|
||||||
|
if (locationDisplayMode === 'dropdown') {
|
||||||
|
return 'select';
|
||||||
|
} else if (locationDisplayMode === 'gallery') {
|
||||||
|
return 'cards';
|
||||||
|
}
|
||||||
const groups = options.length;
|
const groups = options.length;
|
||||||
const maxOptionsPerGroup = Math.max(...options.map(opt => opt.options.length));
|
const maxOptionsPerGroup = Math.max(...options.map(opt => opt.options.length));
|
||||||
const showCards = groups < 5 && maxOptionsPerGroup < 6;
|
const showCards = groups < 5 && maxOptionsPerGroup < 6;
|
||||||
return showCards ? 'cards' : 'select';
|
return showCards ? 'cards' : 'select';
|
||||||
}, [options]);
|
}, [options, locationDisplayMode]);
|
||||||
|
|
||||||
const noOverlap = useMemo(
|
const noOverlap = useMemo(
|
||||||
() => form.queryLocation.length > 1 && filtered.types.length === 0,
|
() => form.queryLocation.length > 1 && filtered.types.length === 0,
|
||||||
|
|
@ -221,8 +139,8 @@ export const QueryLocation = (props: TQuerySelectField): JSX.Element => {
|
||||||
options={options}
|
options={options}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
name="queryLocation"
|
name="queryLocation"
|
||||||
onChange={handleSelectChange}
|
|
||||||
closeMenuOnSelect={false}
|
closeMenuOnSelect={false}
|
||||||
|
onChange={handleSelectChange}
|
||||||
value={selections.queryLocation}
|
value={selections.queryLocation}
|
||||||
isError={typeof errors.queryLocation !== 'undefined'}
|
isError={typeof errors.queryLocation !== 'undefined'}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ const Option = (props: OptionProps<OptionWithDescription, false>) => {
|
||||||
export const QueryTarget = (props: TQueryTarget): JSX.Element => {
|
export const QueryTarget = (props: TQueryTarget): JSX.Element => {
|
||||||
const { name, register, onChange, placeholder } = props;
|
const { name, register, onChange, placeholder } = props;
|
||||||
|
|
||||||
const bg = useColorValue('white', 'whiteAlpha.100');
|
const bg = useColorValue('white', 'blackSolid.800');
|
||||||
const color = useColorValue('gray.400', 'whiteAlpha.800');
|
const color = useColorValue('gray.400', 'whiteAlpha.800');
|
||||||
const border = useColorValue('gray.100', 'whiteAlpha.50');
|
const border = useColorValue('gray.100', 'whiteAlpha.50');
|
||||||
const placeholderColor = useColorValue('gray.600', 'whiteAlpha.700');
|
const placeholderColor = useColorValue('gray.600', 'whiteAlpha.700');
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ export const LookingGlass = (): JSX.Element => {
|
||||||
</FormField>
|
</FormField>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
<SlideFade offsetY={100} in={filtered.types.length > 0} unmountOnExit>
|
<SlideFade offsetX={-100} in={filtered.types.length > 0} unmountOnExit>
|
||||||
<FormField
|
<FormField
|
||||||
name="queryType"
|
name="queryType"
|
||||||
label={web.text.queryType}
|
label={web.text.queryType}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export const useControlStyle = <Opt extends SingleOption, IsMulti extends boolea
|
||||||
props: RSStyleCallbackProps,
|
props: RSStyleCallbackProps,
|
||||||
): RSStyleFunction<'control', Opt, IsMulti> => {
|
): RSStyleFunction<'control', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const { isError } = useSelectContext();
|
const { isError } = useSelectContext();
|
||||||
|
|
||||||
const minHeight = useToken('space', 12);
|
const minHeight = useToken('space', 12);
|
||||||
|
|
@ -22,9 +23,10 @@ export const useControlStyle = <Opt extends SingleOption, IsMulti extends boolea
|
||||||
const color = useColorToken('colors', 'black', 'whiteAlpha.800');
|
const color = useColorToken('colors', 'black', 'whiteAlpha.800');
|
||||||
const focusBorder = useColorToken('colors', 'blue.500', 'blue.300');
|
const focusBorder = useColorToken('colors', 'blue.500', 'blue.300');
|
||||||
const invalidBorder = useColorToken('colors', 'red.500', 'red.300');
|
const invalidBorder = useColorToken('colors', 'red.500', 'red.300');
|
||||||
|
// const borderColor = useColorToken('colors', 'gray.200', 'whiteAlpha.300');
|
||||||
const borderColor = useColorToken('colors', 'gray.100', 'whiteAlpha.50');
|
const borderColor = useColorToken('colors', 'gray.100', 'whiteAlpha.50');
|
||||||
const borderHover = useColorToken('colors', 'gray.300', 'whiteAlpha.400');
|
const borderHover = useColorToken('colors', 'gray.300', 'whiteAlpha.400');
|
||||||
const backgroundColor = useColorToken('colors', 'white', 'whiteAlpha.100');
|
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.800');
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(base, state) => {
|
(base, state) => {
|
||||||
|
|
@ -56,9 +58,12 @@ export const useMenuStyle = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||||
props: RSStyleCallbackProps,
|
props: RSStyleCallbackProps,
|
||||||
): RSStyleFunction<'menu', Opt, IsMulti> => {
|
): RSStyleFunction<'menu', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const { isOpen } = useSelectContext();
|
const { isOpen } = useSelectContext();
|
||||||
|
|
||||||
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700');
|
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700');
|
||||||
const styles = { backgroundColor, zIndex: 1500 };
|
const styles = { backgroundColor, zIndex: 1500 };
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -66,13 +71,14 @@ export const useMenuListStyle = <Opt extends SingleOption, IsMulti extends boole
|
||||||
props: RSStyleCallbackProps,
|
props: RSStyleCallbackProps,
|
||||||
): RSStyleFunction<'menuList', Opt, IsMulti> => {
|
): RSStyleFunction<'menuList', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const { isOpen } = useSelectContext();
|
const { isOpen } = useSelectContext();
|
||||||
|
|
||||||
const borderRadius = useToken('radii', 'md');
|
const borderRadius = useToken('radii', 'md');
|
||||||
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700');
|
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700');
|
||||||
const scrollbarTrack = useColorToken('colors', 'blackAlpha.50', 'whiteAlpha.50');
|
const scrollbarTrack = useColorToken('colors', 'blackAlpha.50', 'whiteAlpha.50');
|
||||||
const scrollbarThumb = useColorToken('colors', 'blackAlpha.300', 'whiteAlpha.300');
|
const scrollbarThumb = useColorToken('colors', 'blackAlpha.300', 'whiteAlpha.300');
|
||||||
const scrollbarThumbHover = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400');
|
const scrollbarThumbHover = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400');
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
borderRadius,
|
borderRadius,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
|
@ -82,6 +88,7 @@ export const useMenuListStyle = <Opt extends SingleOption, IsMulti extends boole
|
||||||
'&::-webkit-scrollbar-thumb:hover': { backgroundColor: scrollbarThumbHover },
|
'&::-webkit-scrollbar-thumb:hover': { backgroundColor: scrollbarThumbHover },
|
||||||
'-ms-overflow-style': { display: 'none' },
|
'-ms-overflow-style': { display: 'none' },
|
||||||
};
|
};
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -89,6 +96,7 @@ export const useOptionStyle = <Opt extends SingleOption, IsMulti extends boolean
|
||||||
props: RSStyleCallbackProps,
|
props: RSStyleCallbackProps,
|
||||||
): RSStyleFunction<'option', Opt, IsMulti> => {
|
): RSStyleFunction<'option', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const { isOpen } = useSelectContext();
|
const { isOpen } = useSelectContext();
|
||||||
|
|
||||||
const fontSize = useToken('fontSizes', 'lg');
|
const fontSize = useToken('fontSizes', 'lg');
|
||||||
|
|
@ -136,8 +144,10 @@ export const useIndicatorSeparatorStyle = <Opt extends SingleOption, IsMulti ext
|
||||||
props: RSStyleCallbackProps,
|
props: RSStyleCallbackProps,
|
||||||
): RSStyleFunction<'indicatorSeparator', Opt, IsMulti> => {
|
): RSStyleFunction<'indicatorSeparator', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
const backgroundColor = useColorToken('colors', 'whiteAlpha.700', 'gray.600');
|
const backgroundColor = useColorToken('colors', 'gray.200', 'whiteAlpha.300');
|
||||||
|
// const backgroundColor = useColorToken('colors', 'gray.200', 'gray.600');
|
||||||
const styles = { backgroundColor };
|
const styles = { backgroundColor };
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -145,8 +155,10 @@ export const usePlaceholderStyle = <Opt extends SingleOption, IsMulti extends bo
|
||||||
props: RSStyleCallbackProps,
|
props: RSStyleCallbackProps,
|
||||||
): RSStyleFunction<'placeholder', Opt, IsMulti> => {
|
): RSStyleFunction<'placeholder', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const color = useColorToken('colors', 'gray.600', 'whiteAlpha.700');
|
const color = useColorToken('colors', 'gray.600', 'whiteAlpha.700');
|
||||||
const fontSize = useToken('fontSizes', 'lg');
|
const fontSize = useToken('fontSizes', 'lg');
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, { color, fontSize }), [colorMode]);
|
return useCallback(base => mergeWith({}, base, { color, fontSize }), [colorMode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -157,8 +169,8 @@ export const useSingleValueStyle = <Opt extends SingleOption, IsMulti extends bo
|
||||||
|
|
||||||
const color = useColorValue('black', 'whiteAlpha.800');
|
const color = useColorValue('black', 'whiteAlpha.800');
|
||||||
const fontSize = useToken('fontSizes', 'lg');
|
const fontSize = useToken('fontSizes', 'lg');
|
||||||
|
|
||||||
const styles = { color, fontSize };
|
const styles = { color, fontSize };
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [color, colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [color, colorMode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -169,8 +181,9 @@ export const useMultiValueStyle = <Opt extends SingleOption, IsMulti extends boo
|
||||||
|
|
||||||
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
||||||
const color = useOpposingColor(backgroundColor);
|
const color = useOpposingColor(backgroundColor);
|
||||||
|
const borderRadius = useToken('radii', 'md');
|
||||||
|
const styles = { backgroundColor, color, borderRadius };
|
||||||
|
|
||||||
const styles = { backgroundColor, color };
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [backgroundColor, colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [backgroundColor, colorMode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -181,8 +194,8 @@ export const useMultiValueLabelStyle = <Opt extends SingleOption, IsMulti extend
|
||||||
|
|
||||||
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
||||||
const color = useOpposingColor(backgroundColor);
|
const color = useOpposingColor(backgroundColor);
|
||||||
|
|
||||||
const styles = { color };
|
const styles = { color };
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -193,16 +206,17 @@ export const useMultiValueRemoveStyle = <Opt extends SingleOption, IsMulti exten
|
||||||
|
|
||||||
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
||||||
const color = useOpposingColor(backgroundColor);
|
const color = useOpposingColor(backgroundColor);
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
color,
|
color,
|
||||||
'&:hover': { backgroundColor: 'inherit', color, opacity: 0.7 },
|
'&:hover': { backgroundColor: 'transparent', color, opacity: 0.8 },
|
||||||
};
|
};
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRSTheme = (): RSThemeFunction => {
|
export const useRSTheme = (): RSThemeFunction => {
|
||||||
const borderRadius = useToken('radii', 'md');
|
const borderRadius = useToken('radii', 'md');
|
||||||
|
|
||||||
return useCallback((t: ReactSelect.Theme): ReactSelect.Theme => ({ ...t, borderRadius }), []);
|
return useCallback((t: ReactSelect.Theme): ReactSelect.Theme => ({ ...t, borderRadius }), []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -215,5 +229,6 @@ export const useMenuPortal = <Opt extends SingleOption, IsMulti extends boolean>
|
||||||
const styles = {
|
const styles = {
|
||||||
zIndex: isMobile ? 1500 : 1,
|
zIndex: isMobile ? 1500 : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
return useCallback(base => merge(base, styles), [isMobile]);
|
return useCallback(base => merge(base, styles), [isMobile]);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ interface _Web {
|
||||||
terms: { enable: boolean; title: string };
|
terms: { enable: boolean; title: string };
|
||||||
text: _Text;
|
text: _Text;
|
||||||
theme: _ThemeConfig;
|
theme: _ThemeConfig;
|
||||||
|
location_display_mode: 'auto' | 'gallery' | 'dropdown';
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DirectiveBase = {
|
type _DirectiveBase = {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue