Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore(site): convert more components from Emotion to TailwindCSS#19719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Parkreiner merged 30 commits intomainfrommes/emo-01c
Sep 17, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
30 commits
Select commitHold shift + click to select a range
302dd27
fix: tweak react file again
ParkreinerSep 6, 2025
d7de1b3
wip: update search
ParkreinerSep 6, 2025
3ab9a6c
fix: convert search
ParkreinerSep 6, 2025
2854960
fix: update SearchField
ParkreinerSep 6, 2025
619884b
fix: update spacing
ParkreinerSep 6, 2025
aa5e643
fix: remove unused class
ParkreinerSep 6, 2025
0bf2a86
fix: convert 2 more
ParkreinerSep 6, 2025
c0dd679
fix: conver stats
ParkreinerSep 6, 2025
9b59d61
fix: update Stats
ParkreinerSep 6, 2025
f96516e
fix: update TImelineEntry
ParkreinerSep 6, 2025
07bffaa
fix: update stats text size
ParkreinerSep 8, 2025
f498a9a
fix: start updating WorkspaceBuildPageView
ParkreinerSep 8, 2025
7bef950
fix: update more styles
ParkreinerSep 8, 2025
d7318a3
Merge branch 'main' into mes/emo-01c
ParkreinerSep 8, 2025
59f5511
fix: update stack label
ParkreinerSep 8, 2025
75ec011
fix: update more components
ParkreinerSep 8, 2025
0b4477e
Merge branch 'main' into mes/emo-01c
ParkreinerSep 8, 2025
695df97
Merge branch 'mes/emo-01c' of https://github.com/coder/coder into mes…
ParkreinerSep 8, 2025
42908bd
fix: add CSS stretch support back
ParkreinerSep 8, 2025
a925850
fix: apply Blink fixes
ParkreinerSep 8, 2025
6e11e22
fix: remove style typo
ParkreinerSep 8, 2025
f06e42a
Merge branch 'main' into mes/emo-01c
ParkreinerSep 8, 2025
bea2627
Merge branch 'main' into mes/emo-01c
ParkreinerSep 15, 2025
cf3da37
fix: update prop name
ParkreinerSep 15, 2025
097e42e
Merge branch 'main' into mes/emo-01c
ParkreinerSep 16, 2025
dd645b6
Merge branch 'main' into mes/emo-01c
ParkreinerSep 17, 2025
fff224e
fix: swap refs
ParkreinerSep 17, 2025
f626046
fix: resolve timing issues
ParkreinerSep 17, 2025
66f0e3d
fix: apply suggestions
ParkreinerSep 17, 2025
5bdf7d1
refactor: remove react state
ParkreinerSep 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletionssite/src/@types/react.d.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
declare module"react"{
interfaceCSSProperties{
[key: `--${string}`]:string|number|undefined;
}
}

export{};
Comment on lines +1 to +7
Copy link
MemberAuthor

@ParkreinerParkreinerSep 8, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Felt like this would be useful. It's always been valid React, but it's just never been baked into the type definitions

Used for setting a custom CSS variable that's scoped to a specific element

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

omg thank you

4 changes: 2 additions & 2 deletionssite/src/components/Filter/SelectFilter.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,8 +52,8 @@ export const SelectFilter: FC<SelectFilterProps> = ({
<SelectMenuTrigger>
<SelectMenuButton
startIcon={selectedOption?.startIcon}
css={{ flexBasis: width, flexGrow: 1 }}
className="shrink-0"
className="shrink-0 grow"
style={{ flexBasis: width }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

yeah, this always should've been astyle. good catch!

aria-label={label}
>
{selectedOption?.label ?? placeholder}
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -506,7 +506,7 @@ export const MultiSelectCombobox = forwardRef<
<Badge
key={option.value}
className={cn(
"data-[disabled]:bg-content-disabled data-[disabled]:text-surface-tertiarydata-[disabled]:hover:bg-content-disabled",
"data-[disabled]:bg-content-disabled data-[disabled]:text-surface-tertiary data-[disabled]:hover:bg-content-disabled",
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We had a missing space before, so two of the classes would just never trigger

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I love Tailwind

"data-[fixed]:bg-content-disabled data-[fixed]:text-surface-tertiary data-[fixed]:hover:bg-surface-secondary",
badgeClassName,
)}
Expand Down
40 changes: 30 additions & 10 deletionssite/src/components/Search/Search.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,46 @@
importtype{Meta,StoryObj}from"@storybook/react-vite";
import{Search,SearchInput}from"./Search";
import{Search,SearchEmpty,SearchInput}from"./Search";

constmeta:Meta<typeofSearchInput>={
title:"components/Search",
component:SearchInput,
decorators:[
(Story)=>(
<Search>
<Story/>
</Search>
),
Comment on lines -8 to -12
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This was making it so that every time you tried to mountSearchInput, you would always getSearch wrapped around it, which made theSearchEmpty story a pain to write

],
};

exportdefaultmeta;
typeStory=StoryObj<typeofSearchInput>;

exportconstExample:Story={};
exportconstExample:Story={
render:(props)=>(
<Search>
<SearchInput{...props}/>
</Search>
),
};

exportconstWithPlaceholder:Story={
exportconstWithCustomPlaceholder:Story={
args:{
label:"uwu",
placeholder:"uwu",
},
render:(props)=>(
<Search>
<SearchInput{...props}/>
</Search>
),
};

exportconstWithSearchEmpty:Story={
args:{
label:"I crave the certainty of steel",
placeholder:"Alas, I am empty",
},
render:(props)=>(
<divclassName="flex flex-col gap-2">
<Search>
<SearchInput{...props}/>
</Search>

<SearchEmpty/>
</div>
),
};
96 changes: 28 additions & 68 deletionssite/src/components/Search/Search.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
import type { Interpolation, Theme } from "@emotion/react";
// biome-ignore lint/style/noRestrictedImports: use it to have the component prop
import Box, { type BoxProps } from "@mui/material/Box";
import visuallyHidden from "@mui/utils/visuallyHidden";
import { SearchIcon } from "lucide-react";
import type { FC, HTMLAttributes, InputHTMLAttributes, Ref } from "react";
import { cn } from "utils/cn";

interface SearchProps extendsOmit<BoxProps, "ref"> {
$$ref?: Ref<unknown>;
interface SearchProps extendsHTMLAttributes<HTMLDivElement> {
ref?: Ref<HTMLDivElement>;
}

/**
Expand All@@ -18,100 +15,63 @@ interface SearchProps extends Omit<BoxProps, "ref"> {
* </Search>
* ```
*/
export const Search: FC<SearchProps> = ({ children, $$ref, ...boxProps }) => {
export const Search: FC<SearchProps> = ({
children,
ref,
className,
...props
}) => {
return (
<Box ref={$$ref} {...boxProps} css={SearchStyles.container}>
<SearchIcon className="size-icon-xs" css={SearchStyles.icon} />
<div
ref={ref}
{...props}
className={cn(
"flex items-center h-10 pl-4 border-0 border-b border-solid border-border",
className,
)}
>
<SearchIcon className="size-icon-xs text-sm text-content-secondary" />
{children}
</Box>
</div>
);
};

const SearchStyles = {
container: (theme) => ({
display: "flex",
alignItems: "center",
paddingLeft: 16,
height: 40,
borderBottom: `1px solid ${theme.palette.divider}`,
}),

icon: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
}),
} satisfies Record<string, Interpolation<Theme>>;

type SearchInputProps = InputHTMLAttributes<HTMLInputElement> & {
label?: string;
$$ref?: Ref<HTMLInputElement>;
ref?: Ref<HTMLInputElement>;
};

export const SearchInput: FC<SearchInputProps> = ({
label,
$$ref,
ref,
id,
...inputProps
}) => {
return (
<>
<labelcss={{ ...visuallyHidden }}htmlFor={inputProps.id}>
<labelclassName="sr-only"htmlFor={id}>
{label}
</label>
<input
ref={$$ref}
tabIndex={-1}
ref={ref}
id={id}
Copy link
MemberAuthor

@ParkreinerParkreinerSep 8, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We were adding an ID to a label, but never associated it with the input

tabIndex={0}
type="text"
placeholder="Search..."
css={SearchInputStyles.input}
className="text-inherit h-full border-0 bg-transparent grow basis-0 outline-none pl-4 placeholder:text-content-secondary"
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't know if this CSS is 100% best practices, but we have a bunch offlex: 1 in the codebase, which is equivalent todisplay: flex; flex-grow: 1; flex-basis: 0

I think that most of the time, we just wanteddisplay: flex in the CSS, but I didn't have time to go through the ramifications of changing all the styles

{...inputProps}
/>
</>
);
};

const SearchInputStyles = {
input: (theme) => ({
color: "inherit",
height: "100%",
border: 0,
background: "none",
flex: 1,
marginLeft: 16,
outline: 0,
"&::placeholder": {
color: theme.palette.text.secondary,
},
}),
} satisfies Record<string, Interpolation<Theme>>;

export const SearchEmpty: FC<HTMLAttributes<HTMLDivElement>> = ({
children = "Not found",
...props
}) => {
return (
<divcss={SearchEmptyStyles.empty} {...props}>
<divclassName="text-sm text-content-secondary text-center py-2" {...props}>
{children}
</div>
);
};

const SearchEmptyStyles = {
empty: (theme) => ({
fontSize: 13,
color: theme.palette.text.secondary,
textAlign: "center",
paddingTop: 8,
paddingBottom: 8,
}),
} satisfies Record<string, Interpolation<Theme>>;

/**
* Reusable styles for consumers of the base components
*/
export const searchStyles = {
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This was only ever used forWorkspaceButton, so I just moved the styles there

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

very nice. this should've been avariant prop or something if we actually cared about reuse. I like going with the simplest solution tho.

content: {
width: 320,
padding: 0,
borderRadius: 4,
},
} satisfies Record<string, Interpolation<Theme>>;
36 changes: 17 additions & 19 deletionssite/src/components/SearchField/SearchField.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,49 @@
import { useTheme } from "@emotion/react";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import TextField, { type TextFieldProps } from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
importvisuallyHiddenfrom "@mui/utils/visuallyHidden";
import{ useEffectEvent }from "hooks/hookPolyfills";
import { SearchIcon, XIcon } from "lucide-react";
import { type FC,useEffect, useRef } from "react";
import { type FC,useLayoutEffect, useRef } from "react";

export type SearchFieldProps = Omit<TextFieldProps, "onChange"> & {
onChange: (query: string) => void;
autoFocus?: boolean;
};

export const SearchField: FC<SearchFieldProps> = ({
value = "",
InputProps,
onChange,
value = "",
autoFocus = false,
InputProps,
...textFieldProps
}) => {
const theme = useTheme();
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
// MUI's autoFocus behavior is wonky. If you set autoFocus=true, the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

it seems to just pass it down to the HTML. but the code is hard to follow, so it might be doing something else. if it causes issues tho I guess it's all the same. 🤷‍♀️

// component will keep getting focus on every single render, even if there
// are other input elements on screen. We want this to be one-time logic
const inputRef = useRef<HTMLInputElement | null>(null);
const focusOnMount = useEffectEvent((): void => {
if (autoFocus) {
inputRef.current?.focus();
}
});
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This wasn't hard to fix, but it was a nasty bug, and I think the only reason we haven't had a problem is because we never had any components withautoFocus set totrue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

was the bug before just that this is missing the deps list?

Suggested change
});
},[]);

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yeah, this was running on each render because there was no list

useLayoutEffect(() => {
focusOnMount();
}, [focusOnMount]);

return (
<TextField
// Specifying `minWidth` so that the text box can't shrink so much
inputRef={inputRef}
// Specifying min width so that the text box can't shrink so much
// that it becomes un-clickable as we add more filter controls
css={{ minWidth: "280px" }}
className="min-w-[280px]"
size="small"
value={value}
onChange={(e) => onChange(e.target.value)}
inputRef={inputRef}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon
className="size-icon-xs"
css={{
color: theme.palette.text.secondary,
}}
/>
<SearchIcon className="size-icon-xs text-content-secondary" />
</InputAdornment>
),
endAdornment: value !== "" && (
Expand All@@ -58,7 +56,7 @@ export const SearchField: FC<SearchFieldProps> = ({
}}
>
<XIcon className="size-icon-xs" />
<spancss={{ ...visuallyHidden }}>Clear search</span>
<spanclassName="sr-only">Clear search</span>
</IconButton>
</Tooltip>
</InputAdornment>
Expand Down
34 changes: 12 additions & 22 deletionssite/src/components/SelectMenu/SelectMenu.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,6 @@ import {
} from "react";
import { cn } from "utils/cn";

const SIDE_PADDING = 16;

export const SelectMenu = Popover;

export const SelectMenuTrigger = PopoverTrigger;
Expand All@@ -37,13 +35,20 @@ type SelectMenuButtonProps = ButtonProps & {
export const SelectMenuButton = forwardRef<
HTMLButtonElement,
SelectMenuButtonProps
>((props, ref) => {
const { startIcon, ...restProps } = props;
>(({ className, startIcon, children, ...props }, ref) => {
return (
<Button variant="outline" size="lg" ref={ref} {...restProps}>
<Button
variant="outline"
size="lg"
ref={ref}
// Shrink padding right slightly to account for visual weight of
// the chevron
className={cn("flex flex-row gap-2 pr-1.5", className)}
{...props}
>
{startIcon}
<span className="text-left block overflow-hidden text-ellipsis flex-grow">
{props.children}
{children}
</span>
<ChevronDownIcon />
</Button>
Expand All@@ -55,22 +60,7 @@ export const SelectMenuSearch: FC<SearchFieldProps> = (props) => {
<SearchField
fullWidth
size="medium"
css={(theme) => ({
borderBottom: `1px solid ${theme.palette.divider}`,
"& input": {
fontSize: 14,
},
"& fieldset": {
border: 0,
borderRadius: 0,
},
"& .MuiInputBase-root": {
padding: `12px ${SIDE_PADDING}px`,
},
"& .MuiInputAdornment-positionStart": {
marginRight: SIDE_PADDING,
},
})}
className="border border-solid border-border [&_input]:text-sm [&_fieldset]:border-0 [&_fieldset]:rounded-none [&_.MuiInputBase-root]:px-4 [&_.MuiInputBase-root]:py-3"
{...props}
inputProps={{ autoFocus: true, ...props.inputProps }}
/>
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp