fix(ui): improve mobile entity picker sheets (#13343)
Co-Authored-By: Paperclip CodexRunner <codexrunner@paperclip.local> Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
df984cbc2c
commit
8b84ae5b35
|
|
@ -129,6 +129,8 @@ describe("InlineEntitySelector", () => {
|
|||
|
||||
const searchInput = document.querySelector('input[placeholder="Search responsible..."]') as HTMLInputElement | null;
|
||||
expect(searchInput).not.toBeNull();
|
||||
expect(searchInput?.className).toContain("text-base");
|
||||
expect(document.querySelector("[data-mobile-entity-picker]")).not.toBeNull();
|
||||
expect(document.activeElement).toBe(searchInput);
|
||||
|
||||
act(() => {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ export const InlineEntitySelector = forwardRef<HTMLButtonElement, InlineEntitySe
|
|||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
data-mobile-entity-picker=""
|
||||
align="start"
|
||||
side="bottom"
|
||||
collisionPadding={16}
|
||||
|
|
@ -159,7 +160,7 @@ export const InlineEntitySelector = forwardRef<HTMLButtonElement, InlineEntitySe
|
|||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="w-full border-b border-border bg-transparent px-2 py-1.5 text-sm outline-none placeholder:text-muted-foreground/60"
|
||||
className="w-full border-b border-border bg-transparent px-2 py-1.5 text-base outline-none placeholder:text-muted-foreground/60 md:text-sm"
|
||||
placeholder={searchPlaceholder}
|
||||
value={query}
|
||||
onChange={(event) => {
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ describe("SearchableSelect", () => {
|
|||
|
||||
expect(container.querySelector("[data-option-key='recent:alpha']")).not.toBeNull();
|
||||
expect(container.querySelector("[data-option-key='all:alpha']")).not.toBeNull();
|
||||
expect(container.querySelector("[data-mobile-entity-picker]")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("filters options and returns the selected option object", async () => {
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ export function SearchableSelect<
|
|||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
data-mobile-entity-picker=""
|
||||
align={align}
|
||||
collisionPadding={16}
|
||||
disablePortal={disablePortal}
|
||||
|
|
|
|||
|
|
@ -2999,3 +2999,23 @@ span.paperclip-mention-chip[data-mention-kind="external-object"] {
|
|||
.runner-activity-roll-in { animation: none; }
|
||||
.runner-activity-roll-out { display: none; }
|
||||
}
|
||||
|
||||
/* Keep entity-picker search controls visible when a narrow viewport opens its
|
||||
software keyboard. The fixed sheet also avoids scale motion during mobile
|
||||
viewport resizing. */
|
||||
@media (max-width: 40rem) {
|
||||
[data-mobile-entity-picker] {
|
||||
position: fixed !important;
|
||||
inset: auto calc(var(--spacing) * 4) max(calc(var(--spacing) * 4), env(safe-area-inset-bottom)) !important;
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
max-height: calc(100dvh - calc(var(--spacing) * 8)) !important;
|
||||
transform: none !important;
|
||||
transform-origin: bottom center !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
[data-mobile-entity-picker] [data-slot="command-list"] {
|
||||
max-height: min(var(--sz-300px), calc(100dvh - calc(var(--spacing) * 24))) !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { InlineEntitySelector, type InlineEntityOption } from "@/components/InlineEntitySelector";
|
||||
|
||||
const assignees: InlineEntityOption[] = [
|
||||
{ id: "agent-product", label: "Product Lead", searchText: "planning product" },
|
||||
{ id: "agent-engineer", label: "Frontend Engineer", searchText: "ui implementation" },
|
||||
{ id: "agent-qa", label: "QA Engineer", searchText: "testing review" },
|
||||
];
|
||||
|
||||
const projects: InlineEntityOption[] = [
|
||||
{ id: "project-control-plane", label: "Control Plane" },
|
||||
{ id: "project-mobile", label: "Mobile Experience" },
|
||||
{ id: "project-connectors", label: "Apps and Connectors" },
|
||||
];
|
||||
|
||||
function OpenPicker({ kind, options }: { kind: "Assignee" | "Project"; options: InlineEntityOption[] }) {
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
triggerRef.current?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-end p-4">
|
||||
<InlineEntitySelector
|
||||
ref={triggerRef}
|
||||
value={value}
|
||||
options={options}
|
||||
placeholder={`Choose ${kind.toLowerCase()}`}
|
||||
noneLabel={`No ${kind.toLowerCase()}`}
|
||||
searchPlaceholder={`Search ${kind.toLowerCase()}s...`}
|
||||
emptyMessage={`No matching ${kind.toLowerCase()}.`}
|
||||
onChange={setValue}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: "Components/Entity pickers/Mobile",
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
viewport: { defaultViewport: "mobile1" },
|
||||
},
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const AssigneePicker: Story = {
|
||||
render: () => <OpenPicker kind="Assignee" options={assignees} />,
|
||||
};
|
||||
|
||||
export const ProjectPicker: Story = {
|
||||
render: () => <OpenPicker kind="Project" options={projects} />,
|
||||
};
|
||||
Loading…
Reference in New Issue