Skip to content

Commit

Permalink
feat: score input
Browse files Browse the repository at this point in the history
  • Loading branch information
shiftpsh committed Jul 17, 2024
1 parent c239200 commit 403dec3
Show file tree
Hide file tree
Showing 20 changed files with 1,003 additions and 116 deletions.
6 changes: 6 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>개척단 훈련소</title>

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Oxanium:[email protected]&family=Zen+Antique+Soft&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/fonts/TAEBAEKmilkyway.css" />
<link rel="stylesheet" href="/fonts/KimDaegeon.css" />
</head>
Expand Down
222 changes: 222 additions & 0 deletions src/components/commons/Autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import styled from "@emotion/styled";
import { Button } from "@mui/base/Button";
import { Popper } from "@mui/base/Popper";
import {
useAutocomplete,
UseAutocompleteProps,
} from "@mui/base/useAutocomplete";
import { unstable_useForkRef as useForkRef } from "@mui/utils";
import { IconChevronDown, IconX } from "@tabler/icons-react";
import * as React from "react";
import { forwardRef } from "react";
import { color } from "../../styles/colors";
import { commons } from "../../styles/commons";
import { option } from "../../styles/option";
import { ListBoxWrapper } from "./ListBox";

const StyledAutocompleteRoot = styled.div`
${commons.textField}
display: inline-flex;
align-items: center;
gap: 8px;
`;

const StyledInput = styled.input`
${commons.textFieldBase}
flex: 1 0 0;
min-width: 0;
background-color: transparent;
height: 100%;
border: none;
&:focus,
&:hover {
border: none;
outline: none;
}
`;

const AutocompleteOption = styled.li`
${option.base}
&:hover {
${option.hover}
}
&[aria-selected="true"] {
${option.selected}
}
&.Mui-focused,
&.Mui-focusVisible {
${option.highlighted}
}
&.Mui-focusVisible {
${option.focusVisible}
}
&[aria-selected="true"].Mui-focused,
&[aria-selected="true"].Mui-focusVisible {
${option.highlightedSelected}
}
`;

const StyledPopper = styled.div`
position: relative;
z-index: 1001;
width: 320px;
`;

const IconContainer = styled(Button)`
color: ${color.selectButtonLight};
display: inline-flex;
align-self: center;
align-items: center;
justify-content: center;
&:hover {
cursor: pointer;
}
`;

const StyledNoOptions = styled.li`
list-style: none;
padding: 8px;
cursor: default;
color: ${color.silkBlueSecondaryText};
`;

interface Props<
Value = string,
Multiple extends boolean | undefined = false,
DisableClearable extends boolean | undefined = false,
FreeSolo extends boolean | undefined = false
> extends UseAutocompleteProps<Value, Multiple, DisableClearable, FreeSolo> {
slotProps?: {
root?: React.HTMLAttributes<HTMLDivElement>;
};
}

interface AutocompleteType {
<
Value = string,
Multiple extends boolean | undefined = false,
DisableClearable extends boolean | undefined = false,
FreeSolo extends boolean | undefined = false
>(
props: Props<Value, Multiple, DisableClearable, FreeSolo>,
ref: React.ForwardedRef<HTMLDivElement>
): JSX.Element | null;
propTypes?: unknown;
displayName?: string | undefined;
}

const Autocomplete = forwardRef(
<
Value = string,
Multiple extends boolean | undefined = false,
DisableClearable extends boolean | undefined = false,
FreeSolo extends boolean | undefined = false
>(
props: Props<Value, Multiple, DisableClearable, FreeSolo>,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const {
disableClearable = false,
disabled = false,
readOnly = false,
slotProps = {},
...other
} = props;

const {
getRootProps,
getInputProps,
getPopupIndicatorProps,
getClearProps,
getListboxProps,
getOptionProps,
dirty,
id,
popupOpen,
focused,
anchorEl,
setAnchorEl,
groupedOptions,
} = useAutocomplete({
...props,
componentName: "BaseAutocomplete",
});

const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;

const rootRef = useForkRef(ref, setAnchorEl);

return (
<React.Fragment>
<StyledAutocompleteRoot
{...getRootProps(other)}
{...slotProps.root}
ref={rootRef}
className={focused ? "focused" : undefined}
>
<StyledInput
id={id}
disabled={disabled}
readOnly={readOnly}
{...getInputProps()}
/>
{hasClearIcon && (
<IconContainer {...getClearProps()}>
<IconX />
</IconContainer>
)}
<IconContainer
{...getPopupIndicatorProps()}
className={popupOpen ? "popupOpen" : undefined}
>
<IconChevronDown />
</IconContainer>
</StyledAutocompleteRoot>
{anchorEl ? (
<Popper
open={popupOpen}
anchorEl={anchorEl}
slots={{
root: StyledPopper,
}}
modifiers={[
{ name: "flip", enabled: false },
{ name: "preventOverflow", enabled: true },
]}
>
<ListBoxWrapper {...getListboxProps()}>
{groupedOptions.map((option, index) => {
const isGroup =
typeof option === "object" && option && "group" in option;
if (!isGroup) {
const optionProps = getOptionProps({ option, index });

return (
<AutocompleteOption {...optionProps}>
{props.getOptionLabel?.(option)}
</AutocompleteOption>
);
} else {
return <AutocompleteOption>Test</AutocompleteOption>;
}
})}

{groupedOptions.length === 0 && (
<StyledNoOptions>결과 없음</StyledNoOptions>
)}
</ListBoxWrapper>
</Popper>
) : null}
</React.Fragment>
);
}
) as AutocompleteType;

export default Autocomplete;
67 changes: 67 additions & 0 deletions src/components/commons/ListBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import styled from "@emotion/styled";
import { CssTransition, PopupContext } from "@mui/base";
import { forwardRef, useContext } from "react";
import { color } from "../../styles/colors";

export const ListBoxWrapper = styled.ul`
background-color: ${color.silkBlue};
color: white;
padding: 16px;
margin: 12px 0;
width: 320px;
max-width: calc(100vw - 64px);
border-radius: 4px;
.closed & {
opacity: 0;
transform: scale(0.95, 0.8);
transition: opacity 200ms ease-in, transform 200ms ease-in;
}
.open & {
opacity: 1;
transform: scale(1, 1);
transition: opacity 100ms ease-out,
transform 100ms cubic-bezier(0.43, 0.29, 0.37, 1.48);
}
.placement-top & {
transform-origin: bottom;
}
.placement-bottom & {
transform-origin: top;
}
`;

interface Props {
ownerState?: unknown;
}

const ListBox = forwardRef(
(props: Props, ref: React.ForwardedRef<HTMLUListElement>) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { ownerState, ...other } = props;
const popupContext = useContext(PopupContext);

if (popupContext == null) {
throw new Error(
"The `AnimatedListbox` component cannot be rendered outside a `Popup` component"
);
}

const verticalPlacement = popupContext.placement.split("-")[0];

return (
<CssTransition
className={`placement-${verticalPlacement}`}
enterClassName="open"
exitClassName="closed"
>
<ListBoxWrapper {...other} ref={ref} />
</CssTransition>
);
}
);

export default ListBox;
34 changes: 8 additions & 26 deletions src/components/commons/Option.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,32 @@
import styled from "@emotion/styled";
import { Option as MuiOption, optionClasses } from "@mui/base";
import { color } from "../../styles/colors";
import { option } from "../../styles/option";

const Option = styled(MuiOption)`
list-style: none;
padding: 8px;
cursor: default;
&:last-of-type {
border-bottom: none;
}
${option.base}
&.${optionClasses.selected} {
background-image: linear-gradient(
to right,
${color.goldLight},
${color.silkBlue}
);
color: ${color.silkBlue};
${option.selected}
}
&.${optionClasses.highlighted} {
background-color: ${color.silkBlueLight};
outline: 1px solid ${color.goldLight};
color: white;
${option.highlighted}
}
&:focus-visible {
outline: 1px solid ${color.goldLight};
${option.focusVisible}
}
&.${optionClasses.highlighted}.${optionClasses.selected} {
background-image: linear-gradient(
to right,
${color.goldLight},
${color.silkBlueLight}
);
color: ${color.silkBlue};
${option.highlightedSelected}
}
&.${optionClasses.disabled} {
color: ${color.silkBlueSecondaryText};
${option.disabled}
}
&:hover:not(.${optionClasses.disabled}) {
background-color: ${color.silkBlueLight};
${option.hover}
}
`;

Expand Down
Loading

0 comments on commit 403dec3

Please sign in to comment.