Skip to content

Commit 7db4069

Browse files
authored
Tailwind border-radius support (#6657)
**Problem:** Make border-radius controls work in tailwind projects. **Fix:** - Add the border-radius shorthand/longhand props to the StyleInfo interface - Add overflow prop to the StyleInfo interface (necessary because when we set the border-radius we set the element to overflow: hidden) - Update InlineStylePlugin and TailwindStylePlugin to support the new props in StyleInfo - Refactor the set-border-radius strategy and the border-radius control handle control to read element styles through a StyleInfoReader instance - Add a new property patcher in style-plugins@patchers to take care of patching removed border-radius props - Add a new EditorState substate so we can get a StyleInfoReader in a useEditorState hook without using the full store - Remove the condition in tailwind-compilation that `ElementsToRerenderGLOBAL.current` has to be `'rerender-all-elements'` for the tailwind classes to be regenerated. For some reason this blocked the class generation after a border-radius interaction. We are still protected against tailwind class generation during the interaction (we check `isInteractionActive`) - Add tests with a tailwind project to the set-border-radius strategy test suite - removeTailwindClasses guarantees that subsequent tests do not have the tailwind css on **Todo:** - [x] Check why the ``ElementsToRerenderGLOBAL.current` check caused problems. **Manual Tests:** I hereby swear that: - [x] I opened a hydrogen project and it loaded - [x] I could navigate to various routes in Play mode
1 parent a4cf331 commit 7db4069

12 files changed

+514
-84
lines changed

editor/src/components/canvas/canvas-strategies/strategies/set-border-radius-strategy.spec.browser2.tsx

+336
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@ import { fromString } from '../../../../core/shared/element-path'
22
import type { CanvasVector, Size, WindowPoint } from '../../../../core/shared/math-utils'
33
import { canvasVector, size, windowPoint } from '../../../../core/shared/math-utils'
44
import { assertNever } from '../../../../core/shared/utils'
5+
import { TailwindConfigPath } from '../../../../core/tailwind/tailwind-config'
6+
import { createModifiedProject } from '../../../../sample-projects/sample-project-utils.test-utils'
57
import type { Modifiers } from '../../../../utils/modifiers'
68
import { cmdModifier, emptyModifiers } from '../../../../utils/modifiers'
9+
import {
10+
selectComponentsForTest,
11+
setFeatureForBrowserTestsUseInDescribeBlockOnly,
12+
wait,
13+
} from '../../../../utils/utils.test-utils'
714
import { selectComponents, setFocusedElement } from '../../../editor/actions/action-creators'
15+
import { StoryboardFilePath } from '../../../editor/store/editor-state'
816
import type { BorderRadiusCorner } from '../../border-radius-control-utils'
917
import { BorderRadiusCorners } from '../../border-radius-control-utils'
1018
import type { EdgePosition } from '../../canvas-types'
@@ -23,6 +31,7 @@ import {
2331
renderTestEditorWithCode,
2432
makeTestProjectCodeWithSnippet,
2533
getPrintedUiJsCode,
34+
renderTestEditorWithModel,
2635
} from '../../ui-jsx.test-utils'
2736

2837
describe('set border radius strategy', () => {
@@ -424,6 +433,333 @@ describe('set border radius strategy', () => {
424433
})
425434
})
426435
})
436+
437+
describe('Tailwind', () => {
438+
setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true)
439+
440+
const TailwindProject = (classes: string) =>
441+
createModifiedProject({
442+
[StoryboardFilePath]: `
443+
import React from 'react'
444+
import { Scene, Storyboard } from 'utopia-api'
445+
export var storyboard = (
446+
<Storyboard data-uid='sb'>
447+
<Scene
448+
id='scene'
449+
commentId='scene'
450+
data-uid='scene'
451+
style={{
452+
width: 700,
453+
height: 759,
454+
position: 'absolute',
455+
left: 212,
456+
top: 128,
457+
}}
458+
>
459+
<div
460+
data-uid='mydiv'
461+
data-testid='mydiv'
462+
className='top-28 left-28 w-28 h-28 bg-black absolute ${classes}'
463+
/>
464+
</Scene>
465+
</Storyboard>
466+
)
467+
468+
`,
469+
[TailwindConfigPath]: `
470+
const TailwindConfig = { }
471+
export default TailwindConfig
472+
`,
473+
'app.css': `
474+
@tailwind base;
475+
@tailwind components;
476+
@tailwind utilities;`,
477+
})
478+
479+
it('border radius controls show up for elements that have tailwind border radius set', async () => {
480+
const editor = await renderTestEditorWithModel(
481+
TailwindProject('rounded-2xl'),
482+
'await-first-dom-report',
483+
)
484+
await selectComponentsForTest(editor, [fromString('sb/scene/mydiv')])
485+
486+
const borderRadiusControls = BorderRadiusCorners.flatMap((corner) =>
487+
editor.renderedDOM.queryAllByTestId(CircularHandleTestId(corner)),
488+
)
489+
490+
expect(borderRadiusControls.length).toEqual(4)
491+
})
492+
493+
it('border radius controls show up for elements that dont have tailwind border radius set', async () => {
494+
const editor = await renderTestEditorWithModel(TailwindProject(''), 'await-first-dom-report')
495+
await selectComponentsForTest(editor, [fromString('sb/scene/mydiv')])
496+
497+
const borderRadiusControls = BorderRadiusCorners.flatMap((corner) =>
498+
editor.renderedDOM.queryAllByTestId(CircularHandleTestId(corner)),
499+
)
500+
501+
expect(borderRadiusControls.length).toEqual(4)
502+
})
503+
504+
describe('adjust border radius via handles', () => {
505+
it('top left', async () => {
506+
const editor = await renderTestEditorWithModel(
507+
TailwindProject('rounded-[10px]'),
508+
'await-first-dom-report',
509+
)
510+
await doDragTest(editor, 'tl', 10, emptyModifiers)
511+
await editor.getDispatchFollowUpActionsFinished()
512+
const div = editor.renderedDOM.getByTestId('mydiv')
513+
expect(div.className).toEqual(
514+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden',
515+
)
516+
})
517+
518+
it('top right', async () => {
519+
const editor = await renderTestEditorWithModel(
520+
TailwindProject('rounded-[10px]'),
521+
'await-first-dom-report',
522+
)
523+
await doDragTest(editor, 'tr', 10, emptyModifiers)
524+
await editor.getDispatchFollowUpActionsFinished()
525+
const div = editor.renderedDOM.getByTestId('mydiv')
526+
expect(div.className).toEqual(
527+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden',
528+
)
529+
})
530+
531+
it('bottom left', async () => {
532+
const editor = await renderTestEditorWithModel(
533+
TailwindProject('rounded-[10px]'),
534+
'await-first-dom-report',
535+
)
536+
await doDragTest(editor, 'tl', 10, emptyModifiers)
537+
await editor.getDispatchFollowUpActionsFinished()
538+
const div = editor.renderedDOM.getByTestId('mydiv')
539+
expect(div.className).toEqual(
540+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden',
541+
)
542+
})
543+
544+
it('bottom right', async () => {
545+
const editor = await renderTestEditorWithModel(
546+
TailwindProject('rounded-[10px]'),
547+
'await-first-dom-report',
548+
)
549+
await doDragTest(editor, 'tl', 10, emptyModifiers)
550+
await editor.getDispatchFollowUpActionsFinished()
551+
const div = editor.renderedDOM.getByTestId('mydiv')
552+
expect(div.className).toEqual(
553+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[22px] overflow-hidden',
554+
)
555+
})
556+
})
557+
describe('adjust border radius via handles with non-arbitrary tailwind classes', () => {
558+
it('top left', async () => {
559+
const editor = await renderTestEditorWithModel(
560+
TailwindProject('rounded-2xl'),
561+
'await-first-dom-report',
562+
)
563+
await doDragTest(editor, 'tl', 10, emptyModifiers)
564+
await editor.getDispatchFollowUpActionsFinished()
565+
const div = editor.renderedDOM.getByTestId('mydiv')
566+
expect(div.className).toEqual(
567+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden',
568+
)
569+
})
570+
571+
it('top right', async () => {
572+
const editor = await renderTestEditorWithModel(
573+
TailwindProject('rounded-2xl'),
574+
'await-first-dom-report',
575+
)
576+
await doDragTest(editor, 'tr', 10, emptyModifiers)
577+
await editor.getDispatchFollowUpActionsFinished()
578+
const div = editor.renderedDOM.getByTestId('mydiv')
579+
expect(div.className).toEqual(
580+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden',
581+
)
582+
})
583+
584+
it('bottom left', async () => {
585+
const editor = await renderTestEditorWithModel(
586+
TailwindProject('rounded-2xl'),
587+
'await-first-dom-report',
588+
)
589+
await doDragTest(editor, 'tl', 10, emptyModifiers)
590+
await editor.getDispatchFollowUpActionsFinished()
591+
const div = editor.renderedDOM.getByTestId('mydiv')
592+
expect(div.className).toEqual(
593+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden',
594+
)
595+
})
596+
597+
it('bottom right', async () => {
598+
const editor = await renderTestEditorWithModel(
599+
TailwindProject('rounded-2xl'),
600+
'await-first-dom-report',
601+
)
602+
await doDragTest(editor, 'tl', 10, emptyModifiers)
603+
await editor.getDispatchFollowUpActionsFinished()
604+
const div = editor.renderedDOM.getByTestId('mydiv')
605+
expect(div.className).toEqual(
606+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-[2rem] overflow-hidden',
607+
)
608+
})
609+
})
610+
describe('adjust border radius via handles, individually', () => {
611+
it('top left', async () => {
612+
const editor = await renderTestEditorWithModel(
613+
TailwindProject('rounded-[10px]'),
614+
'await-first-dom-report',
615+
)
616+
await doDragTest(editor, 'tl', 10, cmdModifier)
617+
await editor.getDispatchFollowUpActionsFinished()
618+
const div = editor.renderedDOM.getByTestId('mydiv')
619+
expect(div.className).toEqual(
620+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[22px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[10px] overflow-hidden',
621+
)
622+
})
623+
624+
it('top right', async () => {
625+
const editor = await renderTestEditorWithModel(
626+
TailwindProject('rounded-[10px]'),
627+
'await-first-dom-report',
628+
)
629+
await doDragTest(editor, 'tr', 10, cmdModifier)
630+
await editor.getDispatchFollowUpActionsFinished()
631+
const div = editor.renderedDOM.getByTestId('mydiv')
632+
expect(div.className).toEqual(
633+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[10px] rounded-tr-[22px] rounded-br-[10px] rounded-bl-[10px] overflow-hidden',
634+
)
635+
})
636+
637+
it('bottom left', async () => {
638+
const editor = await renderTestEditorWithModel(
639+
TailwindProject('rounded-[10px]'),
640+
'await-first-dom-report',
641+
)
642+
await doDragTest(editor, 'bl', 10, cmdModifier)
643+
await editor.getDispatchFollowUpActionsFinished()
644+
const div = editor.renderedDOM.getByTestId('mydiv')
645+
expect(div.className).toEqual(
646+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[10px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[22px] overflow-hidden',
647+
)
648+
})
649+
650+
it('bottom right', async () => {
651+
const editor = await renderTestEditorWithModel(
652+
TailwindProject('rounded-[10px]'),
653+
'await-first-dom-report',
654+
)
655+
await doDragTest(editor, 'br', 10, cmdModifier)
656+
await editor.getDispatchFollowUpActionsFinished()
657+
const div = editor.renderedDOM.getByTestId('mydiv')
658+
expect(div.className).toEqual(
659+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[10px] rounded-tr-[10px] rounded-br-[22px] rounded-bl-[10px] overflow-hidden',
660+
)
661+
})
662+
})
663+
describe('adjust border radius via handles, individually, with non-arbitrary tailwind classes', () => {
664+
it('top left', async () => {
665+
const editor = await renderTestEditorWithModel(
666+
TailwindProject('rounded-2xl'),
667+
'await-first-dom-report',
668+
)
669+
await doDragTest(editor, 'tl', 10, cmdModifier)
670+
await editor.getDispatchFollowUpActionsFinished()
671+
const div = editor.renderedDOM.getByTestId('mydiv')
672+
expect(div.className).toEqual(
673+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[2rem] rounded-tr-2xl rounded-br-2xl rounded-bl-2xl overflow-hidden',
674+
)
675+
})
676+
677+
it('top right', async () => {
678+
const editor = await renderTestEditorWithModel(
679+
TailwindProject('rounded-2xl'),
680+
'await-first-dom-report',
681+
)
682+
await doDragTest(editor, 'tr', 10, cmdModifier)
683+
await editor.getDispatchFollowUpActionsFinished()
684+
const div = editor.renderedDOM.getByTestId('mydiv')
685+
expect(div.className).toEqual(
686+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-2xl rounded-tr-[2rem] rounded-br-2xl rounded-bl-2xl overflow-hidden',
687+
)
688+
})
689+
690+
it('bottom left', async () => {
691+
const editor = await renderTestEditorWithModel(
692+
TailwindProject('rounded-2xl'),
693+
'await-first-dom-report',
694+
)
695+
await doDragTest(editor, 'bl', 10, cmdModifier)
696+
await editor.getDispatchFollowUpActionsFinished()
697+
const div = editor.renderedDOM.getByTestId('mydiv')
698+
expect(div.className).toEqual(
699+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-2xl rounded-tr-2xl rounded-br-2xl rounded-bl-[2rem] overflow-hidden',
700+
)
701+
})
702+
703+
it('bottom right', async () => {
704+
const editor = await renderTestEditorWithModel(
705+
TailwindProject('rounded-2xl'),
706+
'await-first-dom-report',
707+
)
708+
await doDragTest(editor, 'br', 10, cmdModifier)
709+
await editor.getDispatchFollowUpActionsFinished()
710+
const div = editor.renderedDOM.getByTestId('mydiv')
711+
expect(div.className).toEqual(
712+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-2xl rounded-tr-2xl rounded-br-[2rem] rounded-bl-2xl overflow-hidden',
713+
)
714+
})
715+
})
716+
717+
describe('Overflow property handling', () => {
718+
it('does not overwrite existing overflow property', async () => {
719+
const editor = await renderTestEditorWithModel(
720+
TailwindProject('rounded-[10px] overflow-visible'),
721+
'await-first-dom-report',
722+
)
723+
await doDragTest(editor, 'tl', 10, cmdModifier)
724+
await editor.getDispatchFollowUpActionsFinished()
725+
const div = editor.renderedDOM.getByTestId('mydiv')
726+
expect(div.className).toEqual(
727+
'top-28 left-28 w-28 h-28 bg-black absolute overflow-visible rounded-tl-[22px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[10px]',
728+
)
729+
})
730+
731+
it('shows toast message', async () => {
732+
const editor = await renderTestEditorWithModel(
733+
TailwindProject('rounded-[10px]'),
734+
'await-first-dom-report',
735+
)
736+
await doDragTest(editor, 'tl', 10, cmdModifier)
737+
expect(editor.getEditorState().editor.toasts).toHaveLength(1)
738+
expect(editor.getEditorState().editor.toasts[0]).toEqual({
739+
id: 'property-added',
740+
level: 'NOTICE',
741+
message: 'Element now hides overflowing content',
742+
persistent: false,
743+
})
744+
})
745+
})
746+
747+
it('can handle 4-value syntax', async () => {
748+
const editor = await renderTestEditorWithModel(
749+
TailwindProject(
750+
'rounded-tl-[14px] rounded-tr-[15px] rounded-br-[16px] rounded-bl-[17px] overflow-visible',
751+
),
752+
'await-first-dom-report',
753+
)
754+
755+
await doDragTest(editor, 'tl', 10, emptyModifiers)
756+
await editor.getDispatchFollowUpActionsFinished()
757+
const div = editor.renderedDOM.getByTestId('mydiv')
758+
expect(div.className).toEqual(
759+
'top-28 left-28 w-28 h-28 bg-black absolute rounded-tl-[24px] rounded-tr-[15px] rounded-br-[16px] rounded-bl-[17px] overflow-visible',
760+
)
761+
})
762+
})
427763
})
428764

429765
function codeForDragTest(borderRadius: string): string {

0 commit comments

Comments
 (0)