mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
Optimize icon picker for mobile with responsive sizing (#12275)
* Increase emoji picker cell size on mobile Mobile uses a 40px button with a 32px emoji glyph (vs. 32px / 24px on desktop), so roughly 8 emojis fit across a typical phone screen for easier touch targeting. https://claude.ai/code/session_017Rrv75Rc6eZ7eb2iNpZxpu * tweaks --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,15 +6,21 @@ import { IconType } from "@shared/types";
|
||||
import { IconLibrary } from "@shared/utils/IconLibrary";
|
||||
import { Emoji } from "~/components/Emoji";
|
||||
import Text from "~/components/Text";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import { TRANSLATED_CATEGORIES } from "../utils";
|
||||
import Grid from "./Grid";
|
||||
import { IconButton } from "./IconButton";
|
||||
import { CustomEmoji } from "@shared/components/CustomEmoji";
|
||||
|
||||
/**
|
||||
* icon/emoji size is 24px; and we add 4px padding on all sides,
|
||||
* Desktop: 24px icon/emoji + 4px padding on all sides = 32px button.
|
||||
* Mobile: 32px icon/emoji + 4px padding on all sides = 40px button, so
|
||||
* roughly 8 emojis fit across a typical phone screen.
|
||||
*/
|
||||
const BUTTON_SIZE = 32;
|
||||
const BUTTON_SIZE_DESKTOP = 32;
|
||||
const BUTTON_SIZE_MOBILE = 40;
|
||||
const ICON_SIZE_DESKTOP = 24;
|
||||
const ICON_SIZE_MOBILE = 32;
|
||||
|
||||
type OutlineNode = {
|
||||
type: IconType.SVG;
|
||||
@@ -53,8 +59,11 @@ const GridTemplate = (
|
||||
{ width, height, data, empty, onIconSelect }: Props,
|
||||
ref: React.Ref<HTMLDivElement>
|
||||
) => {
|
||||
const isMobile = useMobile();
|
||||
const buttonSize = isMobile ? BUTTON_SIZE_MOBILE : BUTTON_SIZE_DESKTOP;
|
||||
const iconSize = isMobile ? ICON_SIZE_MOBILE : ICON_SIZE_DESKTOP;
|
||||
// 24px padding for the Grid Container
|
||||
const itemsPerRow = Math.floor((width - 24) / BUTTON_SIZE);
|
||||
const itemsPerRow = Math.max(1, Math.floor((width - 24) / buttonSize));
|
||||
|
||||
const gridItems = compact(
|
||||
data.flatMap((node) => {
|
||||
@@ -84,7 +93,11 @@ const GridTemplate = (
|
||||
onClick={() => onIconSelect({ id: item.name, value: item.name })}
|
||||
style={{ "--delay": `${item.delay}ms` } as React.CSSProperties}
|
||||
>
|
||||
<Icon as={IconLibrary.getComponent(item.name)} color={item.color}>
|
||||
<Icon
|
||||
as={IconLibrary.getComponent(item.name)}
|
||||
color={item.color}
|
||||
size={iconSize}
|
||||
>
|
||||
{item.initial}
|
||||
</Icon>
|
||||
</IconButton>
|
||||
@@ -96,7 +109,11 @@ const GridTemplate = (
|
||||
key={item.id}
|
||||
onClick={() => onIconSelect({ id: item.id, value: item.value })}
|
||||
>
|
||||
<Emoji width={24} height={24}>
|
||||
<Emoji
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
size={isMobile ? iconSize : undefined}
|
||||
>
|
||||
{item.type === IconType.Custom ? (
|
||||
<CustomEmoji value={item.value} title={item.name} />
|
||||
) : (
|
||||
@@ -119,7 +136,7 @@ const GridTemplate = (
|
||||
height={height}
|
||||
data={gridItems}
|
||||
columns={itemsPerRow}
|
||||
itemWidth={BUTTON_SIZE}
|
||||
itemWidth={buttonSize}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
import { s, hover } from "@shared/styles";
|
||||
import { breakpoints, s, hover } from "@shared/styles";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
|
||||
export const IconButton = styled(NudeButton)<{ delay?: number }>`
|
||||
@@ -10,4 +10,9 @@ export const IconButton = styled(NudeButton)<{ delay?: number }>`
|
||||
&: ${hover} {
|
||||
background: ${s("listItemHoverBackground")};
|
||||
}
|
||||
|
||||
@media (max-width: ${breakpoints.tablet - 1}px) {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -79,7 +79,9 @@ const IconPicker = ({
|
||||
|
||||
const [activeTab, setActiveTab] = React.useState<TabName>(defaultTab);
|
||||
|
||||
const popoverWidth = isMobile ? windowWidth : POPOVER_WIDTH;
|
||||
// The Drawer's inner content has 6px padding on each side; subtract it
|
||||
// so the panel doesn't overflow horizontally and itemsPerRow is correct.
|
||||
const popoverWidth = isMobile ? windowWidth - 12 : POPOVER_WIDTH;
|
||||
|
||||
const handleTabChange = React.useCallback((value: string) => {
|
||||
setActiveTab(value as TabName);
|
||||
|
||||
@@ -297,7 +297,7 @@ const StyledIconPicker = styled(IconPicker)`
|
||||
const Title = styled(ContentEditable)<TitleProps>`
|
||||
position: relative;
|
||||
line-height: ${lineHeight};
|
||||
margin-top: ${(props: TitleProps) => (props.$containsIcon ? "10vh" : "3vh")};
|
||||
margin-top: 8vh;
|
||||
margin-bottom: 0.5em;
|
||||
font-size: ${fontSize};
|
||||
font-weight: 600;
|
||||
|
||||
Reference in New Issue
Block a user