Skip to content

Commit

Permalink
Cloud image constrain proportions (#614)
Browse files Browse the repository at this point in the history
  • Loading branch information
jossmac authored Aug 31, 2023
1 parent e12c4f1 commit bcb3b8e
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 11 deletions.
6 changes: 6 additions & 0 deletions .changeset/popular-moles-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystatic/core': patch
'@keystar/ui': patch
---

Support constrained proportions on cloud image block and field. Refactor `Tooltip` styles to allow consumer overrides via style props.
8 changes: 4 additions & 4 deletions design-system/pkg/src/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ export const Tooltip: ForwardRefExoticComponent<
backgroundColor: tokenSchema.color.background.inverse,
color: tokenSchema.color.foreground.inverse,
borderRadius: tokenSchema.size.radius.small,
maxWidth: tokenSchema.size.alias.singleLineWidth,
minHeight: tokenSchema.size.element.small,
paddingBlock: tokenSchema.size.space.regular,
paddingInline: tokenSchema.size.space.regular,
opacity: 0,
pointerEvents: 'none',
transition: transition(['opacity', 'transform']),
Expand Down Expand Up @@ -163,10 +167,6 @@ export const Tooltip: ForwardRefExoticComponent<
boxSizing: 'border-box',
display: 'flex',
gap: tokenSchema.size.space.small,
maxInlineSize: tokenSchema.size.alias.singleLineWidth,
minBlockSize: tokenSchema.size.element.small,
paddingBlock: tokenSchema.size.space.regular,
paddingInline: tokenSchema.size.space.regular,
})}
>
<SlotProvider slots={slots}>
Expand Down
50 changes: 47 additions & 3 deletions packages/keystatic/src/component-blocks/cloud-image-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import {
Button,
ButtonGroup,
ClearButton,
ToggleButton,
} from '@keystar/ui/button';
import { Dialog, DialogContainer, DialogTrigger } from '@keystar/ui/dialog';
import { Icon } from '@keystar/ui/icon';
import { imageIcon } from '@keystar/ui/icon/icons/imageIcon';
import { link2Icon } from '@keystar/ui/icon/icons/link2Icon';
import { link2OffIcon } from '@keystar/ui/icon/icons/link2OffIcon';
import { pencilIcon } from '@keystar/ui/icon/icons/pencilIcon';
import { trash2Icon } from '@keystar/ui/icon/icons/trash2Icon';
import { undo2Icon } from '@keystar/ui/icon/icons/undo2Icon';
Expand Down Expand Up @@ -90,6 +93,7 @@ function ImageDialog(props: {
const { image, onCancel, onChange, onClose } = props;
const [state, setState] = useState<CloudImageProps>(image ?? emptyImageData);
const [status, setStatus] = useState<ImageStatus>(image ? 'good' : '');
const [constrainProportions, setConstrainProportions] = useState(true);
const [dimensions, setDimensions] = useState<ImageDimensions>(emptyImageData);
const formId = useId();
const imageLibraryURL = useImageLibraryURL();
Expand Down Expand Up @@ -205,14 +209,49 @@ function ImageDialog(props: {
width="scale.1600"
formatOptions={{ maximumFractionDigits: 0 }}
value={state.width}
onChange={width => setState(state => ({ ...state, width }))}
onChange={width => {
if (constrainProportions) {
setState(state => ({
...state,
width,
height: Math.round(width / getAspectRatio(state)),
}));
} else {
setState(state => ({ ...state, width }));
}
}}
/>
<TooltipTrigger>
<ToggleButton
isSelected={constrainProportions}
aria-label="Constrain proportions"
prominence="low"
onPress={() => {
setConstrainProportions(state => !state);
}}
>
<Icon
src={constrainProportions ? link2Icon : link2OffIcon}
/>
</ToggleButton>
<Tooltip>Constrain proportions</Tooltip>
</TooltipTrigger>
<NumberField
label="Height"
width="scale.1600"
formatOptions={{ maximumFractionDigits: 0 }}
value={state.height}
onChange={height => setState(state => ({ ...state, height }))}
onChange={height => {
if (constrainProportions) {
setState(state => ({
...state,
height,
width: Math.round(height * getAspectRatio(state)),
}));
} else {
setState(state => ({ ...state, height }));
}
}}
/>
<TooltipTrigger>
<ActionButton
Expand All @@ -228,7 +267,7 @@ function ImageDialog(props: {
>
<Icon src={undo2Icon} />
</ActionButton>
<Tooltip>{revertLabel}</Tooltip>
<Tooltip maxWidth="100%">{revertLabel}</Tooltip>
</TooltipTrigger>
</HStack>
</>
Expand Down Expand Up @@ -445,3 +484,8 @@ export function useImageLibraryURL() {
const { project, team } = getSplitCloudProject(config);
return `https://keystatic.cloud/teams/${team}/project/${project}/images`;
}

function getAspectRatio(state: CloudImageProps) {
if (!state.width || !state.height) return 1;
return state.width / state.height;
}
49 changes: 45 additions & 4 deletions packages/keystatic/src/form/fields/cloudImage/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import { useEffect, useId, useState } from 'react';

import { ActionButton, ClearButton } from '@keystar/ui/button';
import { ActionButton, ClearButton, ToggleButton } from '@keystar/ui/button';
import { ObjectField, PreviewProps } from '@keystatic/core';
import { Icon } from '@keystar/ui/icon';
import { link2Icon } from '@keystar/ui/icon/icons/link2Icon';
import { link2OffIcon } from '@keystar/ui/icon/icons/link2OffIcon';
import { undo2Icon } from '@keystar/ui/icon/icons/undo2Icon';
import { Box, Flex, HStack, VStack } from '@keystar/ui/layout';
import { TextLink } from '@keystar/ui/link';
Expand Down Expand Up @@ -58,6 +60,7 @@ function ImageField(props: {
const [status, setStatus] = useState<ImageStatus>(image.src ? 'good' : '');
const imageLibraryURL = useImageLibraryURL();
const dimensions = useImageDimensions(image.src);
const [constrainProportions, setConstrainProportions] = useState(true);
const revertLabel = `Revert to original (${dimensions.width} × ${dimensions.height})`;
const dimensionsMatchOriginal =
dimensions.width === image.width && dimensions.height === image.height;
Expand Down Expand Up @@ -179,14 +182,47 @@ function ImageField(props: {
width="scale.1600"
formatOptions={{ maximumFractionDigits: 0 }}
value={image.width}
onChange={width => props.onChange({ ...image, width })}
onChange={width => {
if (constrainProportions) {
props.onChange({
...image,
width,
height: Math.round(width / getAspectRatio(image)),
});
} else {
props.onChange({ ...image, width });
}
}}
/>
<TooltipTrigger>
<ToggleButton
isSelected={constrainProportions}
aria-label="Constrain proportions"
prominence="low"
onPress={() => {
setConstrainProportions(state => !state);
}}
>
<Icon src={constrainProportions ? link2Icon : link2OffIcon} />
</ToggleButton>
<Tooltip>Constrain proportions</Tooltip>
</TooltipTrigger>
<NumberField
label="Height"
width="scale.1600"
formatOptions={{ maximumFractionDigits: 0 }}
value={image.height}
onChange={height => props.onChange({ ...image, height })}
onChange={height => {
if (constrainProportions) {
props.onChange({
...image,
height,
width: Math.round(height * getAspectRatio(image)),
});
} else {
props.onChange({ ...image, height });
}
}}
/>
<TooltipTrigger>
<ActionButton
Expand All @@ -206,7 +242,7 @@ function ImageField(props: {
>
<Icon src={undo2Icon} />
</ActionButton>
<Tooltip>{revertLabel}</Tooltip>
<Tooltip maxWidth="100%">{revertLabel}</Tooltip>
</TooltipTrigger>
</HStack>
</>
Expand Down Expand Up @@ -263,3 +299,8 @@ export function CloudImageFieldInput(
</Flex>
);
}

function getAspectRatio(state: CloudImageProps) {
if (!state.width || !state.height) return 1;
return state.width / state.height;
}

3 comments on commit bcb3b8e

@vercel
Copy link

@vercel vercel bot commented on bcb3b8e Aug 31, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

keystar-ui – ./design-system/docs

keystar-ui-thinkmill-labs.vercel.app
voussoir.vercel.app
keystar-ui.vercel.app
keystar-ui-git-main-thinkmill-labs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on bcb3b8e Aug 31, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

keystatic – ./dev-projects/next-app

keystatic.vercel.app
keystatic-git-main-thinkmill-labs.vercel.app
keystatic-thinkmill-labs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on bcb3b8e Aug 31, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.