fix(frontend): switch grouped forward view to full list (#161)

* fix(frontend): left-align announcement logo in homepage banner

* fix(frontend): keep multiselect panels floating and preserve summaries

* fix(frontend): rebalance config card header spacing

* fix(frontend): normalize formatting and fix multiselect modal behavior

* fix(frontend): polish batch actions and sharing page guidance

* fix(frontend): switch grouped forward view to full list

* fix(backend): switch diagnosis internet target to bing

* style(frontend): beautify forward group view list display

* style: remove 'x' suffix from traffic ratio input and center config alert

* fix(ui): expand exit node select upwards in modal

* style: fix vertical alignment of logo and title in announcement banner
This commit is contained in:
sagit
2026-02-20 18:31:39 +08:00
committed by GitHub
parent 45bfd35a20
commit d216567c02
10 changed files with 429 additions and 412 deletions
@@ -1,5 +1,4 @@
import * as React from "react";
import { CalendarIcon } from "lucide-react";
import { FieldContainer, type FieldMetaProps } from "./shared";
@@ -88,7 +87,6 @@ export function DatePicker({
value,
}: DatePickerProps) {
const id = React.useId();
const nativePickerRef = React.useRef<HTMLInputElement | null>(null);
const formattedValue = React.useMemo(() => formatDateValue(value), [value]);
const [textValue, setTextValue] = React.useState(formattedValue);
@@ -160,30 +158,6 @@ export function DatePicker({
setTextValue(formattedValue);
};
const openNativePicker = () => {
if (isDisabled) {
return;
}
const picker = nativePickerRef.current;
if (!picker) {
return;
}
const pickerWithShow = picker as HTMLInputElement & {
showPicker?: () => void;
};
if (typeof pickerWithShow.showPicker === "function") {
pickerWithShow.showPicker();
return;
}
picker.focus();
picker.click();
};
return (
<FieldContainer
description={description}
@@ -193,71 +167,40 @@ export function DatePicker({
isRequired={isRequired}
label={label}
>
{shouldUseTextInput ? (
<div className="flex items-center gap-2">
<Input
aria-invalid={isInvalid}
className={cn("flex-1", className)}
disabled={isDisabled}
id={id}
inputMode="numeric"
placeholder="YYYY-MM-DD"
required={isRequired}
type="text"
value={textValue}
onBlur={commitTextInput}
onChange={(event) => {
const nextValue = event.target.value;
<Input
aria-invalid={isInvalid}
className={cn(className)}
disabled={isDisabled}
id={id}
inputMode={shouldUseTextInput ? "numeric" : undefined}
placeholder={shouldUseTextInput ? "YYYY-MM-DD" : undefined}
required={isRequired}
type={shouldUseTextInput ? "text" : "date"}
value={shouldUseTextInput ? textValue : formattedValue}
onChange={(event) => {
const nextValue = event.target.value;
setTextValue(nextValue);
notifyTextDateChange(nextValue);
}}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
commitTextInput();
if (shouldUseTextInput) {
setTextValue(nextValue);
notifyTextDateChange(nextValue);
return;
}
notifyNativeDateChange(nextValue);
}}
onBlur={shouldUseTextInput ? commitTextInput : undefined}
onKeyDown={
shouldUseTextInput
? (event) => {
if (event.key === "Enter") {
event.preventDefault();
commitTextInput();
}
}
}}
/>
<button
aria-label="打开日历选择器"
className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-input bg-background text-default-600 shadow-sm transition-colors hover:bg-default-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
disabled={isDisabled}
type="button"
onClick={openNativePicker}
>
<CalendarIcon className="h-4 w-4" />
</button>
<input
ref={nativePickerRef}
aria-hidden="true"
className="sr-only"
disabled={isDisabled}
tabIndex={-1}
type="date"
value={formattedValue}
onChange={(event) => {
const nextValue = event.target.value;
setTextValue(nextValue);
notifyNativeDateChange(nextValue);
}}
/>
</div>
) : (
<Input
aria-invalid={isInvalid}
className={cn(className)}
disabled={isDisabled}
id={id}
required={isRequired}
type="date"
value={formattedValue}
onChange={(event) => {
notifyNativeDateChange(event.target.value);
}}
/>
)}
: undefined
}
/>
</FieldContainer>
);
}
@@ -37,6 +37,7 @@ export interface SelectProps<T = unknown> extends FieldMetaProps {
selectionMode?: SelectionMode;
size?: "sm" | "md" | "lg";
variant?: string;
dropdownPlacement?: "bottom" | "top";
}
export interface SelectItemProps {
@@ -163,6 +164,7 @@ export function Select<T>({
selectedKeys,
selectionMode = "single",
size,
dropdownPlacement = "bottom",
}: SelectProps<T>) {
const generatedId = React.useId();
const options = React.useMemo(
@@ -284,9 +286,16 @@ export function Select<T>({
return null;
}
const placementClasses = dropdownPlacement === "top"
? "bottom-full mb-1"
: "top-full mt-1";
return (
<div
className="absolute left-0 top-full z-50 mt-1 max-h-56 w-full space-y-1 overflow-y-auto rounded-md border border-divider bg-background p-2 shadow-md"
className={cn(
"absolute left-0 z-50 w-full space-y-1 overflow-y-auto rounded-md border border-divider bg-background p-2 shadow-md max-h-56",
placementClasses
)}
id={`${generatedId}-listbox`}
ref={listboxRef}
role="listbox"
@@ -4,11 +4,15 @@ import { cn } from "@/lib/utils";
interface TableClassNames {
th?: string;
td?: string;
tr?: string;
wrapper?: string;
}
interface TableStyleContextValue {
thClassName?: string;
tdClassName?: string;
trClassName?: string;
}
const TableStyleContext = React.createContext<TableStyleContextValue>({});
@@ -24,7 +28,13 @@ export function Table({
...props
}: TableProps) {
return (
<TableStyleContext.Provider value={{ thClassName: classNames?.th }}>
<TableStyleContext.Provider
value={{
thClassName: classNames?.th,
tdClassName: classNames?.td,
trClassName: classNames?.tr,
}}
>
<div
className={cn("w-full overflow-auto rounded-md", classNames?.wrapper)}
>
@@ -151,11 +161,23 @@ export function TableColumn({
}
export function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
const { trClassName } = React.useContext(TableStyleContext);
return (
<tr className={cn("border-b last:border-b-0", className)} {...props} />
<tr
className={cn("border-b last:border-b-0", trClassName, className)}
{...props}
/>
);
}
export function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return <td className={cn("px-3 py-2 align-middle", className)} {...props} />;
const { tdClassName } = React.useContext(TableStyleContext);
return (
<td
className={cn("px-3 py-2 align-middle", tdClassName, className)}
{...props}
/>
);
}