Files
react-test/docs/data/joy/components/autocomplete/GitHubLabel.tsx
how2ice 005cf56baf
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
init project
2025-12-12 14:26:25 +09:00

343 lines
9.8 KiB
TypeScript

import * as React from 'react';
import { Popper } from '@mui/base/Popper';
import { ClickAwayListener } from '@mui/base/ClickAwayListener';
import Autocomplete from '@mui/joy/Autocomplete';
import AutocompleteListbox from '@mui/joy/AutocompleteListbox';
import AutocompleteOption from '@mui/joy/AutocompleteOption';
import Box from '@mui/joy/Box';
import Link from '@mui/joy/Link';
import List from '@mui/joy/List';
import ListItem from '@mui/joy/ListItem';
import Typography from '@mui/joy/Typography';
import Sheet from '@mui/joy/Sheet';
import SettingsIcon from '@mui/icons-material/Settings';
import CloseIcon from '@mui/icons-material/Close';
import DoneIcon from '@mui/icons-material/Done';
import colors from '@mui/joy/colors';
const Listbox = React.forwardRef<HTMLUListElement, any>((props, ref) => (
<AutocompleteListbox
ref={ref}
{...props}
variant="plain"
size="sm"
sx={{
'--List-padding': '0px',
'--List-radius': '0px',
'--ListItem-paddingX': '8px',
'--ListItem-paddingY': '8px',
minWidth: '100%',
}}
/>
));
export default function GitHubLabel() {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [value, setValue] = React.useState<LabelType[]>([labels[1], labels[11]]);
const [pendingValue, setPendingValue] = React.useState<LabelType[]>([]);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setPendingValue(value);
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setValue(pendingValue);
if (anchorEl) {
anchorEl.focus();
}
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? 'github-label' : undefined;
return (
<React.Fragment>
<Box sx={{ width: 221 }}>
<Link
color="neutral"
component="button"
underline="none"
level="body-xs"
aria-describedby={id}
onClick={handleClick}
sx={{
display: 'flex',
justifyContent: 'space-between',
width: '100%',
fontWeight: 'lg',
color: 'text.secondary',
py: 1,
'&:hover': {
color: 'primary.plainColor',
},
}}
>
<span>Labels</span>
<SettingsIcon />
</Link>
<List
size="sm"
sx={{
'--List-gap': '3px',
'--ListItem-minHeight': '20px',
'--ListItem-paddingX': '4px',
'--ListItem-paddingY': '0.15em',
'--ListItem-radius': '2px',
fontSize: '13px',
}}
>
{value.map((label) => (
<ListItem
key={label.name}
sx={{ fontWeight: 600, backgroundColor: label.color, color: '#fff' }}
>
{label.name}
</ListItem>
))}
</List>
</Box>
<Popper id={id} open={open} anchorEl={anchorEl} placement="bottom-start">
<ClickAwayListener onClickAway={handleClose}>
<Sheet
variant="outlined"
sx={(theme) => ({
width: 300,
boxShadow: 'md',
borderRadius: '6px',
overflow: 'hidden',
'--joy-palette-neutral-plainBg': '#fff',
'--joy-palette-background-surface': '#fff',
[theme.getColorSchemeSelector('dark')]: {
'--joy-palette-neutral-plainBg': '#000',
'--joy-palette-background-surface': '#000',
},
})}
>
<Typography
sx={{
fontSize: 'sm',
fontWeight: 600,
padding: '8px 10px',
borderBottom: '1px solid',
borderColor: 'divider',
}}
>
Apply labels to this pull request
</Typography>
<Autocomplete
open
autoFocus
multiple
size="sm"
placeholder="Filter labels"
slots={{ listbox: Listbox }}
onClose={(event, reason) => {
if (reason === 'escape') {
handleClose();
}
}}
value={pendingValue}
onChange={(event, newValue, reason) => {
if (
event.type === 'keydown' &&
((event as React.KeyboardEvent).key === 'Backspace' ||
(event as React.KeyboardEvent).key === 'Delete') &&
reason === 'removeOption'
) {
return;
}
setPendingValue(newValue);
}}
disableClearable
disableCloseOnSelect
forcePopupIcon={false}
renderTags={() => null}
noOptionsText="No labels"
renderOption={(props, option, { selected }) => (
<AutocompleteOption
{...props}
color="neutral"
sx={(theme) => ({
alignItems: 'flex-start',
border: 'none',
borderBottom: '1px solid',
borderColor: 'divider',
'--joy-palette-neutral-plainHoverBg': 'rgba(0, 0, 0, 0.03)',
'--joy-palette-neutral-plainActiveBg': 'rgba(0, 0, 0, 0.03)',
[theme.getColorSchemeSelector('dark')]: {
'--joy-palette-neutral-plainHoverBg': colors.grey[800],
'--joy-palette-neutral-plainActiveBg': colors.grey[800],
},
'&[aria-selected="true"]': {
fontWeight: 'normal',
},
'&:first-of-type': {
borderTop: '1px solid',
borderColor: 'divider',
},
})}
>
<DoneIcon
sx={[
selected
? { visibility: 'visible' }
: { visibility: 'hidden' },
]}
/>
<Box
component="span"
sx={{
width: 14,
height: 14,
flexShrink: 0,
borderRadius: '3px',
mr: 1,
ml: '5px',
mt: '4px',
backgroundColor: option.color,
}}
/>
<Box sx={{ flexGrow: 1 }}>
<Typography level="title-sm">{option.name}</Typography>
<Typography level="body-xs">{option.description}</Typography>
</Box>
<CloseIcon
sx={[
selected
? {
visibility: 'visible',
}
: {
visibility: 'hidden',
},
]}
/>
</AutocompleteOption>
)}
options={[...labels].sort((a, b) => {
// Display the selected labels first.
let ai = value.indexOf(a);
ai = ai === -1 ? value.length + labels.indexOf(a) : ai;
let bi = value.indexOf(b);
bi = bi === -1 ? value.length + labels.indexOf(b) : bi;
return ai - bi;
})}
getOptionLabel={(option) => option.name}
sx={{
p: '4px 2px',
borderTop: '1px solid',
borderBottom: '1px solid',
borderColor: 'divider',
'--Input-radius': '4px',
m: '0.75rem 0.5rem',
}}
/>
</Sheet>
</ClickAwayListener>
</Popper>
</React.Fragment>
);
}
interface LabelType {
name: string;
color: string;
description?: string;
}
// From https://github.com/abdonrd/github-labels
const labels = [
{
name: 'good first issue',
color: '#7057ff',
description: 'Good for newcomers',
},
{
name: 'help wanted',
color: '#008672',
description: 'Extra attention is needed',
},
{
name: 'priority: critical',
color: '#b60205',
description: '',
},
{
name: 'priority: high',
color: '#d93f0b',
description: '',
},
{
name: 'priority: low',
color: '#0e8a16',
description: '',
},
{
name: 'priority: medium',
color: '#fbca04',
description: '',
},
{
name: "status: can't reproduce",
color: '#fec1c1',
description: '',
},
{
name: 'status: confirmed',
color: '#215cea',
description: '',
},
{
name: 'status: duplicate',
color: '#cfd3d7',
description: 'This issue or pull request already exists',
},
{
name: 'status: needs information',
color: '#fef2c0',
description: '',
},
{
name: 'status: wont do/fix',
color: '#eeeeee',
description: 'This will not be worked on',
},
{
name: 'type: bug',
color: '#d73a4a',
description: "Something isn't working",
},
{
name: 'type: discussion',
color: '#d4c5f9',
description: '',
},
{
name: 'type: documentation',
color: '#006b75',
description: '',
},
{
name: 'type: enhancement',
color: '#84b6eb',
description: '',
},
{
name: 'type: epic',
color: '#3e4b9e',
description: 'A theme of work that contain sub-tasks',
},
{
name: 'type: feature request',
color: '#fbca04',
description: 'New feature or request',
},
{
name: 'type: question',
color: '#d876e3',
description: 'Further information is requested',
},
];