Skip to content

Commit

Permalink
Merge pull request #938 from traPtitech/fix/use-valibot
Browse files Browse the repository at this point in the history
[dashboard] form周りの改善(valibotの使用, contextの利用)
  • Loading branch information
eyemono-moe authored Aug 24, 2024
2 parents 0bbe3f9 + c328c07 commit 7b7de61
Show file tree
Hide file tree
Showing 61 changed files with 6,622 additions and 4,951 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/dashboard-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ jobs:
- run: corepack enable
- run: yarn install --immutable
- run: yarn typecheck
test:
name: Test
runs-on: ubuntu-latest
needs: [packages]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: corepack enable
- run: yarn install --immutable
- run: yarn test
build:
name: Build
runs-on: ubuntu-latest
Expand Down
11 changes: 8 additions & 3 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"fix": "yarn fmt:apply && yarn lint:apply",
"typecheck": "tsc --noEmit",
"ci": "biome ci src",
"analyze": "vite build --mode analyze"
"analyze": "vite build --mode analyze",
"test": "vitest"
},
"license": "MIT",
"devDependencies": {
Expand All @@ -24,13 +25,15 @@
"@macaron-css/vite": "1.5.1",
"@tanstack/virtual-core": "3.8.4",
"@types/node": "22.1.0",
"jsdom": "24.1.1",
"rollup-plugin-visualizer": "5.12.0",
"typescript": "5.5.4",
"unplugin-fonts": "1.1.1",
"vite": "5.3.5",
"vite-plugin-compression": "0.5.1",
"vite-plugin-solid": "2.10.2",
"vite-plugin-solid-svg": "0.8.1"
"vite-plugin-solid-svg": "0.8.1",
"vitest": "2.0.3"
},
"dependencies": {
"@bufbuild/protobuf": "1.10.0",
Expand All @@ -52,7 +55,9 @@
"solid-js": "1.8.19",
"solid-tippy": "0.2.1",
"solid-toast": "0.5.0",
"tippy.js": "6.3.7"
"tippy.js": "6.3.7",
"ts-pattern": "5.2.0",
"valibot": "0.36.0"
},
"packageManager": "[email protected]+sha512.ec40d0639bb307441b945d9467139cbb88d14394baac760b52eca038b330d16542d66fef61574271534ace5a200518dabf3b53a85f1f9e4bfa37141b538a9590"
}
90 changes: 73 additions & 17 deletions dashboard/src/components/templates/RadioGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { style } from '@macaron-css/core'
import { styled } from '@macaron-css/solid'
import { For, type JSX, Show, splitProps } from 'solid-js'
import { colorOverlay } from '/@/libs/colorOverlay'
import { colorVars, textVars } from '/@/theme'
import { colorVars, media, textVars } from '/@/theme'
import { RadioIcon } from '../UI/RadioIcon'
import { ToolTip, type TooltipProps } from '../UI/ToolTip'
import { TooltipInfoIcon } from '../UI/TooltipInfoIcon'
Expand All @@ -13,21 +13,41 @@ const OptionsContainer = styled('div', {
base: {
width: '100%',
display: 'flex',
flexWrap: 'wrap',
gap: '16px',
},
variants: {
wrap: {
true: {
flexWrap: 'wrap',
},
false: {
flexWrap: 'nowrap',
'@media': {
[media.mobile]: {
flexDirection: 'column',
},
},
},
},
},
defaultVariants: {
wrap: 'true',
},
})
const fullItemStyle = style({
width: '100%',
minWidth: 'min(200px, 100%)',
})
const itemStyle = style({
const fitItemStyle = style({
width: 'fit-content',
minWidth: 'min(200px, 100%)',
})
const labelStyle = style({
width: '100%',
height: '100%',
padding: '16px',
display: 'grid',
gridTemplateColumns: '1fr 20px',
alignItems: 'center',
justifyItems: 'start',
display: 'flex',
flexDirection: 'column',
gap: '8px',

background: colorVars.semantic.ui.primary,
Expand Down Expand Up @@ -57,10 +77,33 @@ const labelStyle = style({
},
},
})
const ItemTitle = styled('div', {
base: {
display: 'grid',
gridTemplateColumns: '1fr 20px',
alignItems: 'center',
justifyItems: 'start',
gap: '8px',
color: colorVars.semantic.text.black,
...textVars.text.regular,
},
})
const Description = styled('div', {
base: {
color: colorVars.semantic.text.black,
...textVars.caption.regular,
},
})
export const errorTextStyle = style({
width: '100%',
color: colorVars.semantic.accent.error,
...textVars.text.regular,
})

export interface RadioOption<T extends string> {
value: T
label: string
description?: string
}

export interface Props<T extends string> {
Expand All @@ -70,6 +113,8 @@ export interface Props<T extends string> {
options: RadioOption<T>[]
value?: T
setValue?: (value: T) => void
wrap?: boolean
full?: boolean
required?: boolean
disabled?: boolean
readOnly?: boolean
Expand All @@ -85,7 +130,7 @@ export const RadioGroup = <T extends string>(props: Props<T>): JSX.Element => {
const [rootProps, _addedProps, inputProps] = splitProps(
props,
['name', 'value', 'options', 'required', 'disabled', 'readOnly'],
['info', 'tooltip', 'setValue'],
['wrap', 'full', 'info', 'tooltip', 'setValue', 'error', 'label'],
)

return (
Expand All @@ -108,25 +153,36 @@ export const RadioGroup = <T extends string>(props: Props<T>): JSX.Element => {
</TitleContainer>
</Show>
<ToolTip {...props.tooltip}>
<OptionsContainer>
<OptionsContainer wrap={props.wrap}>
<For each={props.options}>
{(option) => (
<KRadioGroup.Item value={option.value} class={itemStyle}>
<KRadioGroup.Item
value={option.value}
classList={{
[fullItemStyle]: props.full,
[fitItemStyle]: !props.full,
}}
>
<KRadioGroup.ItemInput {...inputProps} />
<KRadioGroup.ItemLabel class={labelStyle}>
{option.label}
<KRadioGroup.ItemControl>
<KRadioGroup.ItemIndicator forceMount>
<RadioIcon selected={option.value === props.value} />
</KRadioGroup.ItemIndicator>
</KRadioGroup.ItemControl>
<ItemTitle>
{option.label}
<KRadioGroup.ItemControl>
<KRadioGroup.ItemIndicator forceMount>
<RadioIcon selected={option.value === props.value} />
</KRadioGroup.ItemIndicator>
</KRadioGroup.ItemControl>
</ItemTitle>
<Show when={option.description}>
<Description>{option.description}</Description>
</Show>
</KRadioGroup.ItemLabel>
</KRadioGroup.Item>
)}
</For>
</OptionsContainer>
</ToolTip>
<KRadioGroup.ErrorMessage>{props.error}</KRadioGroup.ErrorMessage>
<KRadioGroup.ErrorMessage class={errorTextStyle}>{props.error}</KRadioGroup.ErrorMessage>
</KRadioGroup.Root>
)
}
54 changes: 35 additions & 19 deletions dashboard/src/components/templates/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Combobox as KComboBox, Select as KSelect } from '@kobalte/core'
import { keyframes, style } from '@macaron-css/core'
import { type JSX, Show, createMemo, splitProps } from 'solid-js'
import { type JSX, Show, createEffect, createMemo, createSignal, splitProps, untrack } from 'solid-js'
import { colorVars, textVars } from '/@/theme'
import { CheckBoxIcon } from '../UI/CheckBoxIcon'
import { MaterialSymbols } from '../UI/MaterialSymbols'
Expand Down Expand Up @@ -174,7 +174,19 @@ export const SingleSelect = <T extends string | number>(props: SingleSelectProps
['placeholder', 'ref', 'onInput', 'onChange', 'onBlur'],
)

const selectedOption = () => props.options.find((o) => o.value === props.value)
// const selectedOption = () => props.options.find((o) => o.value === props.value)
const [selectedOption, setSelectedOption] = createSignal<SelectOption<T>>()

createEffect(() => {
const found = props.options.find((o) => o.value === props.value)
// KobalteのSelect/Comboboxではundefinedを使用できないため、空文字列を指定している
setSelectedOption(
found ?? {
label: '',
value: '' as T,
},
)
})

return (
<KSelect.Root<SelectOption<T>>
Expand All @@ -183,7 +195,10 @@ export const SingleSelect = <T extends string | number>(props: SingleSelectProps
multiple={false}
disallowEmptySelection
value={selectedOption()}
onChange={(v) => props.setValue?.(v.value)}
onChange={(v) => {
props.setValue?.(v.value)
setSelectedOption(v)
}}
optionValue="value"
optionTextValue="label"
validationState={props.error ? 'invalid' : 'valid'}
Expand Down Expand Up @@ -346,38 +361,38 @@ const comboBoxInputStyle = style({

export type ComboBoxProps<T extends string | number> = SelectProps<T> & {
value: T | undefined
setValue?: (v: T) => void
setValue?: (v: T | undefined) => void
}

export const ComboBox = <T extends string | number>(props: SingleSelectProps<T>): JSX.Element => {
export const ComboBox = <T extends string | number>(props: ComboBoxProps<T>): JSX.Element => {
const [rootProps, selectProps] = splitProps(
props,
['name', 'placeholder', 'options', 'required', 'disabled', 'readOnly'],
['placeholder', 'ref', 'onInput', 'onChange', 'onBlur'],
)

const selectedOption = createMemo<SelectOption<T>>(
(prev) => {
const find = props.options.find((o) => o.value === props.value)
if (find) {
props.setValue?.(find.value)
return find
}
props.setValue?.(prev.value)
return prev
},
{ label: props.value?.toString() ?? '', value: props.value ?? ('' as T) },
)
const [selectedOption, setSelectedOption] = createSignal<SelectOption<T>>()

createEffect(() => {
const found = props.options.find((o) => o.value === props.value)
// KobalteのSelect/Comboboxではundefinedを使用できないため、空文字列を指定している
setSelectedOption(
found ?? {
label: '',
value: '' as T,
},
)
})

return (
<KComboBox.Root<SelectOption<T>>
class={containerStyle}
{...rootProps}
multiple={false}
disallowEmptySelection
allowDuplicateSelectionEvents
value={selectedOption()}
onChange={(v) => {
props.setValue?.(v.value)
setSelectedOption(v)
}}
optionValue="value"
optionTextValue="label"
Expand All @@ -389,6 +404,7 @@ export const ComboBox = <T extends string | number>(props: SingleSelectProps<T>)
<KComboBox.ItemLabel>{props.item.textValue}</KComboBox.ItemLabel>
</KComboBox.Item>
)}
{...rootProps}
>
<Show when={props.label}>
<TitleContainer>
Expand Down
Loading

0 comments on commit 7b7de61

Please sign in to comment.