Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1a67bca
Support negative values in position x and y for image and text layer
SwarnimDoegar Jan 16, 2026
560cdba
Add support for duplicate and rename in L1 sidebar
SwarnimDoegar Jan 19, 2026
645fd1d
Add advanced padding input support
SwarnimDoegar Jan 20, 2026
117c4a7
Remove unwanted console logs
SwarnimDoegar Jan 20, 2026
5a13741
Fix Icon Button in padding input
SwarnimDoegar Jan 21, 2026
7663b96
Add focus support in image overlay
SwarnimDoegar Jan 21, 2026
937b21d
Add tooltip and better UX in padding input
SwarnimDoegar Jan 22, 2026
c36c4dd
Add zoom support with fo face and object in base image and image overlay
SwarnimDoegar Jan 22, 2026
56834fe
Fix zoom input step size and default value
SwarnimDoegar Jan 23, 2026
90c2b1b
Fix padding toggle button style
SwarnimDoegar Jan 23, 2026
85364a6
Remove unused imports in PaddingInput component
SwarnimDoegar Jan 28, 2026
2054687
Add gradient effect support in base and overlay images
SwarnimDoegar Jan 28, 2026
9ce5aa2
Improve renaming mode UX
SwarnimDoegar Jan 28, 2026
f439563
Improve Gradient Picker implementation
SwarnimDoegar Jan 28, 2026
e00059e
Add shadow and grayscale support in image layer
SwarnimDoegar Jan 28, 2026
a878f98
Retain Padding Input Value
SwarnimDoegar Jan 30, 2026
7d21805
Retain Zoom Input Value
SwarnimDoegar Jan 30, 2026
415b18a
Add Distortion support in base and overlay image
SwarnimDoegar Jan 30, 2026
7e3c44f
Refactor padding input
SwarnimDoegar Jan 30, 2026
3fb5b78
Add per corner radius support in base and overlay image
SwarnimDoegar Jan 30, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ const AnchorField: React.FC<AnchorFieldProps> = ({
minWidth="0"
p="0"
isDisabled={!positions.includes(position.value)}
onClick={() => onChange(position.value)}
onClick={() => {
if (value === position.value) {
return onChange("")
}
onChange(position.value)
}}
borderRadius="md"
border={
value === position.value
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import {
Box,
Flex,
HStack,
Icon,
Text,
Input,
InputGroup,
InputLeftElement,
IconButton,
useColorModeValue,
Tooltip,
} from "@chakra-ui/react"
import { set } from "lodash"
import type * as React from "react"
import { useState, useEffect, forwardRef } from "react"
import { RxCornerTopLeft } from "@react-icons/all-files/rx/RxCornerTopLeft"
import { RxCornerTopRight } from "@react-icons/all-files/rx/RxCornerTopRight"
import { RxCornerBottomRight } from "@react-icons/all-files/rx/RxCornerBottomRight"
import { RxCornerBottomLeft } from "@react-icons/all-files/rx/RxCornerBottomLeft"
import { TbBorderCorners } from "@react-icons/all-files/tb/TbBorderCorners"
import { FieldErrors } from "react-hook-form"

type RadiusMode = "uniform" | "individual"

export type RadiusState = {
mode: RadiusMode
radius: RadiusObject | string
}

type RadiusInputFieldProps = {
id?: string
onChange: (value: RadiusState) => void
errors?: FieldErrors<Record<string, unknown>>
name: string,
value?: Partial<RadiusState>
}

export type RadiusObject = {
topLeft: string | "max"
topRight: string | "max"
bottomRight: string | "max"
bottomLeft: string | "max"
}

type RadiusDirection = "topLeft" | "topRight" | "bottomRight" | "bottomLeft"

function getUpdatedRadiusValue(
current: RadiusObject | string,
corner: RadiusDirection | "all",
value: string,
mode: "uniform" | "individual"
): RadiusObject | string {
let inputValue: RadiusObject | number | string
try {
inputValue = JSON.parse(value)
} catch {
inputValue = value
}
if (mode === "uniform") {
if (inputValue === "") {
return ""
} else if (typeof inputValue === "string" || typeof inputValue === "number") {
return inputValue.toString()
} else {
const { topLeft, topRight, bottomRight, bottomLeft } = inputValue
if (topLeft === topRight && topLeft === bottomRight && topLeft === bottomLeft) {
return topLeft
} else {
return "";
}
}
} else {
let commonValue: string = ""
if (typeof inputValue === "string" || typeof inputValue === "number") {
commonValue = inputValue.toString()
}
const updatedRadius = current && typeof current === "object"
? { ...current }
: { topLeft: commonValue, topRight: commonValue, bottomRight: commonValue, bottomLeft: commonValue }
if (corner !== "all") {
set(updatedRadius, corner, inputValue.toString())
}
return updatedRadius
}
}

export const RadiusInputField: React.FC<RadiusInputFieldProps> = ({
id,
onChange,
errors,
name: propertyName,
value
}) => {
const [radiusMode, setRadiusMode] = useState<RadiusMode>(value?.mode ?? "uniform")
const [radiusValue, setRadiusValue] = useState<RadiusObject | string>(value?.radius ?? "")
const errorRed = useColorModeValue("red.500", "red.300")
const activeColor = useColorModeValue("blue.500", "blue.600")
const inactiveColor = useColorModeValue("gray.600", "gray.400")

useEffect(() => {
const formatRadiusValue = (value: RadiusObject | string): string | RadiusObject => {
if (value === "") return ""
if (typeof value === "string") {
return value
} else {
return value;
}
}
const formattedValue = formatRadiusValue(radiusValue)
onChange({ mode: radiusMode, radius: formattedValue })
}, [radiusValue, radiusMode])


return (
<HStack
as="fieldset"
id={id}
role="group"
spacing={2}
alignItems="stretch"
justifyContent="space-between"
>
<Flex direction="row" flex="1" flexWrap="wrap" gap={2}>
{radiusMode === "uniform" ? (
<Box flex="1">
<Input
onChange={(e) => {
const val = e.target.value
setRadiusValue(getUpdatedRadiusValue(
radiusValue,
"all",
val,
radiusMode
))
}}
value={typeof radiusValue === "string" ? radiusValue : ""}
placeholder="Uniform Radius"
isInvalid={!!errors?.[propertyName]?.radius}
fontSize="sm"
/>
<Text fontSize='xs' color={errorRed}>{errors?.[propertyName]?.radius?.message}</Text>
</Box>
) : (
<>
{[
{ name: "topLeft", label: "Top Left", icon: RxCornerTopLeft },
{ name: "topRight", label: "Top Right", icon: RxCornerTopRight },
{ name: "bottomLeft", label: "Bottom Left", icon: RxCornerBottomLeft },
{ name: "bottomRight", label: "Bottom Right", icon: RxCornerBottomRight },
].map(({ name, label, icon }) => (
<Box flex="1 1 calc(50% - 4px)" key={name}>
<InputGroup>
<InputLeftElement pointerEvents="none" fontSize="0.7em">
<Icon as={icon} color="gray.500" />
</InputLeftElement>
<Input
onChange={(e) => {
const val = e.target.value
setRadiusValue(getUpdatedRadiusValue(
radiusValue,
name as RadiusDirection,
val,
radiusMode
))
}}
value={typeof radiusValue === "object" ? radiusValue?.[name as RadiusDirection] ?? "" : ""}
placeholder={label}
isInvalid={!!errors?.[propertyName]?.radius?.[name as RadiusDirection]}
fontSize="sm"
/>
</InputGroup>
<Text fontSize='xs' color={errorRed}>{errors?.[propertyName]?.radius?.[name as RadiusDirection]?.message}</Text>
</Box>
))}
</>
)}
</Flex>
<Tooltip
hasArrow
label={radiusMode === "uniform" ? "Enable individual radius" : "Disable individual radius"}
openDelay={200}
modifiers={[
{
name: 'zIndex',
enabled: true,
phase: 'write',
fn({ state }) {
state.elements.popper.style.zIndex = '2100';
},
},
]}
>
<IconButton
aria-label={radiusMode === "uniform" ? "Switch to individual radius" : "Switch to uniform radius"}
aria-pressed={radiusMode === "individual"}
icon={<TbBorderCorners size={20} />}
padding="0.05em"
onClick={() => {
const newRadiusMode = radiusMode === "uniform" ? "individual" : "uniform"
setRadiusValue(getUpdatedRadiusValue(
radiusValue,
"all",
JSON.stringify(radiusValue),
newRadiusMode
))
setRadiusMode(newRadiusMode)
}}
variant="outline"
color={radiusMode === "individual" ? activeColor : inactiveColor}
/>
</Tooltip>
</HStack>
)
}

export default RadiusInputField
Loading