import { getFigmaSquircleSvgPath } from '@blocksuite/affine-shared/utils'; import { css, html, LitElement, svg, type TemplateResult } from 'lit'; import { property, state } from 'lit/decorators.js'; /** * ### A component to use figma 'smoothing radius' * * ```html * *

Smooth Corner

*
* ``` * * **Just wrap your content with it.** * - There is a ResizeObserver inside to observe the size of the content. * - In order to use both border and shadow, we use svg to draw. * - So we need to use `stroke` and `drop-shadow` to replace `border` and `box-shadow`. * * #### required properties * - `borderRadius`: Equal to the border-radius * - `smooth`: From 0 to 1, refer to the figma smoothing radius * * #### customizable style properties * Provides some commonly used styles, dealing with their mapping with SVG attributes, such as: * - `borderWidth` (stroke-width) * - `borderColor` (stroke) * - `bgColor` (fill) * - `bgOpacity` (fill-opacity) * * #### More customization * Use css to customize this component, such as drop-shadow: * ```css * smooth-corner { * filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.1)); * } * ``` */ export class SmoothCorner extends LitElement { static override styles = css` :host { position: relative; } .smooth-corner-bg, .smooth-corner-border { position: absolute; top: 0; left: 0; pointer-events: none; } .smooth-corner-border { z-index: 2; } .smooth-corner-content { position: relative; z-index: 1; width: 100%; height: 100%; } `; private readonly _resizeObserver: ResizeObserver | null = null; get _path() { return getFigmaSquircleSvgPath({ width: this.width, height: this.height, cornerRadius: this.borderRadius, // defaults to 0 cornerSmoothing: this.smooth, // cornerSmoothing goes from 0 to 1 }); } constructor() { super(); this._resizeObserver = new ResizeObserver(entries => { for (const entry of entries) { this.width = entry.contentRect.width; this.height = entry.contentRect.height; } }); } private _getSvg(className: string, path: TemplateResult) { return svg` ${path} `; } override connectedCallback(): void { super.connectedCallback(); this._resizeObserver?.observe(this); } override disconnectedCallback(): void { super.disconnectedCallback(); this._resizeObserver?.unobserve(this); } override render() { return html`${this._getSvg( 'smooth-corner-bg', svg`` )} ${this._getSvg( 'smooth-corner-border', svg`` )}
`; } /** * Background color of the element */ @property({ type: String }) accessor bgColor: string = 'white'; /** * Background opacity of the element */ @property({ type: Number }) accessor bgOpacity: number = 1; /** * Border color of the element */ @property({ type: String }) accessor borderColor: string = 'black'; /** * Equal to the border-radius */ @property({ type: Number }) accessor borderRadius = 0; /** * Border width of the element in px */ @property({ type: Number }) accessor borderWidth: number = 2; @state() accessor height: number = 0; /** * From 0 to 1 */ @property({ type: Number }) accessor smooth: number = 0; @state() accessor width: number = 0; } declare global { interface HTMLElementTagNameMap { 'smooth-corner': SmoothCorner; } }