├── .npmrc ├── apps └── docs │ ├── components │ ├── theme-switcher.tsx │ ├── squircle-provider.tsx │ ├── plus.tsx │ ├── rect.tsx │ ├── theme-provider.tsx │ ├── bprogress-provider.tsx │ ├── logo │ │ ├── vercel-logo.tsx │ │ ├── x-icon.tsx │ │ ├── tailwindcss-logo.tsx │ │ ├── css-logo.tsx │ │ └── logo.tsx │ ├── squircle.tsx │ ├── section.tsx │ ├── ui │ │ ├── label.tsx │ │ ├── input.tsx │ │ ├── tabs.tsx │ │ ├── button.tsx │ │ └── slider.tsx │ ├── docs │ │ ├── theme-switcher.tsx │ │ ├── footer.tsx │ │ ├── type-table.tsx │ │ └── tailwind-type-table.tsx │ ├── slider.tsx │ ├── footer.tsx │ ├── header.tsx │ ├── path-morphing.tsx │ ├── color-picker.tsx │ ├── code-editor │ │ ├── render.tsx │ │ └── index.tsx │ ├── playground.tsx │ └── animate-ui │ │ ├── radix │ │ ├── collapsible.tsx │ │ ├── switch.tsx │ │ ├── popover.tsx │ │ └── tabs.tsx │ │ ├── effects │ │ └── motion-effect.tsx │ │ ├── text │ │ └── sliding-number.tsx │ │ └── components │ │ └── tabs.tsx │ ├── app │ ├── favicon.ico │ ├── api │ │ └── search │ │ │ └── route.ts │ ├── layout.config.tsx │ ├── docs │ │ ├── layout.tsx │ │ └── [[...slug]] │ │ │ └── page.tsx │ ├── layout.tsx │ ├── globals.css │ └── page.tsx │ ├── public │ ├── og-image.png │ └── ipad-demo.png │ ├── postcss.config.mjs │ ├── source.config.ts │ ├── .source │ ├── source.config.mjs │ └── index.ts │ ├── lib │ ├── utils.ts │ └── source.ts │ ├── next.config.ts │ ├── eslint.config.mjs │ ├── mdx-components.tsx │ ├── components.json │ ├── content │ └── docs │ │ ├── css │ │ ├── border-color.mdx │ │ ├── background-color.mdx │ │ ├── border-width.mdx │ │ ├── mask-image.mdx │ │ ├── background.mdx │ │ ├── border-smoothing.mdx │ │ └── border-radius.mdx │ │ ├── meta.json │ │ ├── tailwindcss │ │ ├── background-color.mdx │ │ ├── border-color.mdx │ │ ├── border-width.mdx │ │ ├── background.mdx │ │ ├── mask-image.mdx │ │ └── border-smoothing.mdx │ │ ├── limitations.mdx │ │ ├── getting-started.mdx │ │ └── index.mdx │ ├── .gitignore │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── packages ├── tailwindcss │ ├── .gitignore │ ├── .npmignore │ ├── package.json │ ├── LICENSE │ ├── README.md │ └── index.css ├── core │ ├── .gitignore │ ├── .npmignore │ ├── tsup.config.ts │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ └── squircle-generator.ts │ ├── package.json │ ├── LICENSE │ └── README.md └── paint-polyfill │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ ├── util.js │ └── realm.js │ └── LICENCE ├── pnpm-workspace.yaml ├── .vscode └── settings.json ├── turbo.json ├── package.json ├── .gitignore └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/docs/components/theme-switcher.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/tailwindcss/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .npmrc 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .npmrc 3 | dist 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/paint-polyfill/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .npmrc 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/ 3 | !README.md 4 | !LICENSE 5 | !package.json 6 | -------------------------------------------------------------------------------- /packages/paint-polyfill/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/ 3 | !README.md 4 | !LICENSE 5 | !package.json 6 | -------------------------------------------------------------------------------- /packages/tailwindcss/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !index.css 3 | !README.md 4 | !LICENSE 5 | !package.json 6 | -------------------------------------------------------------------------------- /apps/docs/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imskyleen/squircle/HEAD/apps/docs/app/favicon.ico -------------------------------------------------------------------------------- /apps/docs/public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imskyleen/squircle/HEAD/apps/docs/public/og-image.png -------------------------------------------------------------------------------- /apps/docs/public/ipad-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imskyleen/squircle/HEAD/apps/docs/public/ipad-demo.png -------------------------------------------------------------------------------- /apps/docs/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /apps/docs/source.config.ts: -------------------------------------------------------------------------------- 1 | import { defineDocs } from 'fumadocs-mdx/config'; 2 | 3 | export const docs = defineDocs({ 4 | dir: 'content/docs', 5 | }); 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ], 7 | "[mdx]": { 8 | "editor.formatOnSave": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/docs/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { createFromSource } from 'fumadocs-core/search/server'; 3 | 4 | export const { GET } = createFromSource(source); 5 | -------------------------------------------------------------------------------- /apps/docs/.source/source.config.mjs: -------------------------------------------------------------------------------- 1 | // source.config.ts 2 | import { defineDocs } from "fumadocs-mdx/config"; 3 | var docs = defineDocs({ 4 | dir: "content/docs" 5 | }); 6 | export { 7 | docs 8 | }; 9 | -------------------------------------------------------------------------------- /apps/docs/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /apps/docs/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { docs } from '@/.source'; 2 | import { loader } from 'fumadocs-core/source'; 3 | 4 | export const source = loader({ 5 | baseUrl: '/docs', 6 | source: docs.toFumadocsSource(), 7 | }); 8 | -------------------------------------------------------------------------------- /apps/docs/next.config.ts: -------------------------------------------------------------------------------- 1 | import { createMDX } from 'fumadocs-mdx/next'; 2 | 3 | const withMDX = createMDX(); 4 | 5 | const config = { 6 | reactStrictMode: true, 7 | }; 8 | 9 | export default withMDX(config); 10 | -------------------------------------------------------------------------------- /packages/paint-polyfill/README.md: -------------------------------------------------------------------------------- 1 | # CSS Paint Polyfill that support @layer 2 | 3 | The [css-paint-polyfill](https://github.com/GoogleChromeLabs/css-paint-polyfill) package does not support @layer and animation tracking. So we've added support. 4 | -------------------------------------------------------------------------------- /apps/docs/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import { Logo } from '@/components/logo/logo'; 2 | import { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; 3 | 4 | export const baseOptions: BaseLayoutProps = { 5 | nav: { 6 | title: , 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /apps/docs/components/squircle-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { init } from '@squircle/core'; 5 | 6 | export function SquircleProvider({ children }: { children: React.ReactNode }) { 7 | React.useEffect(() => void init(), []); 8 | return children; 9 | } 10 | -------------------------------------------------------------------------------- /apps/docs/components/plus.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Plus = (props: React.SVGProps) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/core/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.ts', 'src/squircle-generator.ts'], 5 | format: ['esm', 'cjs'], 6 | globalName: 'Squircle', 7 | dts: true, 8 | outDir: 'dist', 9 | splitting: false, 10 | minify: true, 11 | sourcemap: false, 12 | }); 13 | -------------------------------------------------------------------------------- /apps/docs/components/rect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Rect = (props: React.SVGProps) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /apps/docs/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /apps/docs/components/bprogress-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ProgressProvider } from '@bprogress/next/app'; 4 | 5 | const BProgressProvider = ({ children }: { children: React.ReactNode }) => { 6 | return ( 7 | 8 | {children} 9 | 10 | ); 11 | }; 12 | 13 | export { BProgressProvider }; 14 | -------------------------------------------------------------------------------- /packages/tailwindcss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@squircle/tailwindcss", 3 | "version": "1.0.6", 4 | "main": "./index.css", 5 | "exports": { 6 | ".": "./index.css" 7 | }, 8 | "keywords": [ 9 | "tailwindcss", 10 | "squircle" 11 | ], 12 | "homepage": "https://squircle.skyleen.dev", 13 | "author": "Skyleen", 14 | "description": "TailwindCSS plugin for Squircle", 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /apps/docs/components/logo/vercel-logo.tsx: -------------------------------------------------------------------------------- 1 | export default function VercelLogo(props: React.SVGProps) { 2 | return ( 3 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/docs/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /apps/docs/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultComponents from 'fumadocs-ui/mdx'; 2 | import type { MDXComponents } from 'mdx/types'; 3 | import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock'; 4 | 5 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 6 | return { 7 | ...defaultComponents, 8 | pre: ({ ...props }) => ( 9 | 10 |
{props.children}
11 |
12 | ), 13 | ...components, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.com/schema.json", 3 | "ui": "tui", 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "inputs": ["$TURBO_DEFAULT$", ".env*"], 8 | "outputs": [".next/**", "!.next/cache/**"] 9 | }, 10 | "lint": { 11 | "dependsOn": ["^lint"] 12 | }, 13 | "check-types": { 14 | "dependsOn": ["^check-types"] 15 | }, 16 | "dev": { 17 | "cache": false, 18 | "persistent": true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "squircle", 3 | "private": true, 4 | "scripts": { 5 | "build": "turbo run build", 6 | "dev": "turbo run dev", 7 | "lint": "turbo run lint", 8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"", 9 | "check-types": "turbo run check-types" 10 | }, 11 | "devDependencies": { 12 | "prettier": "^3.5.3", 13 | "turbo": "^2.5.2", 14 | "typescript": "5.8.2" 15 | }, 16 | "packageManager": "pnpm@9.0.0", 17 | "engines": { 18 | "node": ">=18" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/docs/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /apps/docs/components/squircle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Squircle = (props: React.SVGProps) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | -------------------------------------------------------------------------------- /apps/docs/components/logo/x-icon.tsx: -------------------------------------------------------------------------------- 1 | export default function XIcon(props: React.SVGProps) { 2 | return ( 3 | 11 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/docs/content/docs/css/border-color.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Border Color 3 | description: Customize the color of the border of a squircle. 4 | --- 5 | 6 | import { TypeTable } from '@/components/docs/type-table'; 7 | 8 | ## Variables 9 | 10 | 18 | 19 | ## Usage 20 | 21 | You can customize the color of the border of a squircle by using the `--squircle-border-color` variable. 22 | 23 | ```css 24 | .squircle { 25 | --squircle-border-color: #000; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /apps/docs/content/docs/css/background-color.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Background Color 3 | description: Customize the background color of a squircle. 4 | --- 5 | 6 | import { TypeTable } from '@/components/docs/type-table'; 7 | 8 | ## Variables 9 | 10 | 18 | 19 | ## Usage 20 | 21 | You can customize the background color of a squircle by using the `--squircle-background-color` variable. 22 | 23 | ```css 24 | .squircle { 25 | --squircle-background-color: #000; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "incremental": false, 7 | "isolatedModules": true, 8 | "lib": ["es2022", "DOM", "DOM.Iterable"], 9 | "module": "ESNext", 10 | "moduleDetection": "force", 11 | "moduleResolution": "Node", 12 | "noUncheckedIndexedAccess": true, 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "target": "ES2022", 17 | "types": ["node"], 18 | "outDir": "dist" 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "dist"] 22 | } 23 | -------------------------------------------------------------------------------- /apps/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /apps/docs/content/docs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Squircle", 3 | "root": true, 4 | "pages": [ 5 | "index", 6 | "getting-started", 7 | "limitations", 8 | "---CSS---", 9 | "css/background", 10 | "css/background-color", 11 | "css/border-color", 12 | "css/border-radius", 13 | "css/border-smoothing", 14 | "css/border-width", 15 | "css/mask-image", 16 | "---Tailwind CSS---", 17 | "tailwindcss/background", 18 | "tailwindcss/background-color", 19 | "tailwindcss/border-color", 20 | "tailwindcss/border-radius", 21 | "tailwindcss/border-smoothing", 22 | "tailwindcss/border-width", 23 | "tailwindcss/mask-image" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /apps/docs/components/section.tsx: -------------------------------------------------------------------------------- 1 | export const Section = ({ children }: { children: React.ReactNode }) => { 2 | return ( 3 |
4 |
5 |
6 |
{children}
7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | async function getDefaultUrl() { 2 | try { 3 | return new URL('./squircle-generator.mjs', import.meta.url).href; 4 | } catch { 5 | console.log('Failed to load squircle-generator.mjs'); 6 | return 'https://unpkg.com/@squircle/core/dist/squircle-generator.js'; 7 | } 8 | } 9 | 10 | export async function init(props?: { 11 | url?: string | null; 12 | disablePolyfill?: boolean; 13 | }) { 14 | const moduleUrl = props?.url ?? (await getDefaultUrl()); 15 | 16 | if (!('paintWorklet' in CSS) && !props?.disablePolyfill) { 17 | // @ts-ignore 18 | await import('@squircle/paint-polyfill'); 19 | } 20 | // @ts-ignore 21 | await CSS.paintWorklet.addModule(moduleUrl); 22 | } 23 | -------------------------------------------------------------------------------- /apps/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/docs/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /packages/paint-polyfill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@squircle/paint-polyfill", 3 | "version": "1.0.6", 4 | "source": "src/index.js", 5 | "main": "./dist/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "exports": { 10 | ".": "./dist/index.js" 11 | }, 12 | "scripts": { 13 | "build": "microbundle -f iife -o dist/index.js", 14 | "dev": "concurrently serve \"microbundle watch -f iife\"" 15 | }, 16 | "keywords": [], 17 | "author": "Google Chrome Developers ", 18 | "license": "Apache-2.0", 19 | "description": "An improved CSS Paint Polyfill that support @layer and some other features", 20 | "devDependencies": { 21 | "concurrently": "^9.1.2", 22 | "microbundle": "^0.15.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/docs/components/logo/tailwindcss-logo.tsx: -------------------------------------------------------------------------------- 1 | export default function TailwindCSSLogo(props: React.SVGProps) { 2 | return ( 3 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/docs/components/docs/theme-switcher.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Moon, Sun } from 'lucide-react'; 4 | import { useTheme } from 'next-themes'; 5 | import { useEffect, useState } from 'react'; 6 | import { Switch } from '../animate-ui/radix/switch'; 7 | 8 | export const ThemeSwitcher = ({ className }: { className?: string }) => { 9 | const { resolvedTheme: theme, setTheme } = useTheme(); 10 | 11 | const [isClient, setIsClient] = useState(false); 12 | 13 | useEffect(() => { 14 | setIsClient(true); 15 | }, []); 16 | 17 | return ( 18 | isClient && ( 19 | } 22 | rightIcon={} 23 | checked={theme === 'dark'} 24 | onCheckedChange={(checked) => setTheme(checked ? 'dark' : 'light')} 25 | /> 26 | ) 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /apps/docs/components/slider.tsx: -------------------------------------------------------------------------------- 1 | import { Slider as ShadcnSlider } from '@/components/ui/slider'; 2 | import { Label } from './ui/label'; 3 | 4 | export const Slider = ({ 5 | label, 6 | min, 7 | max, 8 | step, 9 | value, 10 | onValueChange, 11 | decimalPlaces, 12 | }: { 13 | label: string; 14 | min: number; 15 | max: number; 16 | step?: number; 17 | decimalPlaces?: number; 18 | value: number; 19 | onValueChange: (value: number) => void; 20 | }) => { 21 | return ( 22 |
23 | 24 | onValueChange(v[0])} 32 | /> 33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /apps/docs/content/docs/tailwindcss/background-color.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Background Color 3 | description: Customize the background color of a squircle. 4 | --- 5 | 6 | import { TailwindTypeTable } from '@/components/docs/tailwind-type-table'; 7 | 8 | ## Variables 9 | 10 | ': { 13 | class: 'squircle-', 14 | styles: `--squircle-background-color: ;`, 15 | }, 16 | }} 17 | /> 18 | 19 | ## Usage 20 | 21 | You can use a tailwind color class to customize the background color of a squircle. 22 | 23 | ```tsx 24 |
25 | ``` 26 | 27 | Or you can use a custom color. 28 | 29 | ```tsx 30 |
31 | ``` 32 | 33 | You can also customize the background color opacity. 34 | 35 | ```tsx 36 |
37 | ``` 38 | -------------------------------------------------------------------------------- /apps/docs/content/docs/tailwindcss/border-color.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Border Color 3 | description: Customize the border color of a squircle. 4 | --- 5 | 6 | import { TailwindTypeTable } from '@/components/docs/tailwind-type-table'; 7 | 8 | ## Variables 9 | 10 | ': { 13 | class: 'squircle-border-', 14 | styles: `--squircle-border-color: ;`, 15 | }, 16 | }} 17 | /> 18 | 19 | ## Usage 20 | 21 | You can use a tailwind color class to customize the border color of a squircle. 22 | 23 | ```tsx 24 |
25 | ``` 26 | 27 | Or you can use a custom color. 28 | 29 | ```tsx 30 |
31 | ``` 32 | 33 | You can also customize the background color opacity. 34 | 35 | ```tsx 36 |
37 | ``` 38 | -------------------------------------------------------------------------------- /apps/docs/content/docs/css/border-width.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Border Width 3 | description: Customize the width of the border of a squircle. 4 | --- 5 | 6 | import { TypeTable } from '@/components/docs/type-table'; 7 | 8 | ## Variables 9 | 10 | 18 | 19 | 20 | Other values like rem, em, vh, vw, etc. are not supported. 21 | 22 | 23 | ## Usage 24 | 25 | You can customize the width of the border of a squircle by using the `--squircle-border-width` variable. 26 | 27 | ```css 28 | .squircle { 29 | --squircle-border-width: 2px; 30 | } 31 | ``` 32 | 33 | The value can be in **px** or a **simple number**: 34 | 35 | ```css 36 | .squircle { 37 | --squircle-border-width: 2; 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /apps/docs/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { DocsLayout } from 'fumadocs-ui/layouts/docs'; 3 | import type { ReactNode } from 'react'; 4 | import { baseOptions } from '@/app/layout.config'; 5 | import XIcon from '@/components/logo/x-icon'; 6 | import { ThemeSwitcher } from '@/components/docs/theme-switcher'; 7 | 8 | export default function Layout({ children }: { children: ReactNode }) { 9 | return ( 10 | , 15 | url: 'https://x.com/imskyleen', 16 | text: 'X', 17 | type: 'icon', 18 | }, 19 | ]} 20 | tree={source.pageTree} 21 | themeSwitch={{ 22 | component: , 23 | }} 24 | {...baseOptions} 25 | > 26 | {children} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/docs/content/docs/tailwindcss/border-width.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Border Width 3 | description: Customize the border width of a squircle. 4 | --- 5 | 6 | import { TailwindTypeTable } from '@/components/docs/tailwind-type-table'; 7 | 8 | ## Variables 9 | 10 | ': { 17 | class: 'squircle-border-', 18 | styles: `--squircle-border-width: ;`, 19 | }, 20 | }} 21 | /> 22 | 23 | 24 | Other values than px such as rem, em, vh, vw, etc. are not supported. 25 | 26 | 27 | ## Usage 28 | 29 | ```tsx 30 |
31 | ``` 32 | 33 | You can also customize the border width. 34 | 35 | ```tsx 36 |
37 | ``` 38 | -------------------------------------------------------------------------------- /apps/docs/content/docs/css/mask-image.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mask Image 3 | description: Paint a squircle mask on an element. 4 | --- 5 | 6 | import { TypeTable } from '@/components/docs/type-table'; 7 | 8 | ## Variables 9 | 10 | 18 | 19 | ## Usage 20 | 21 | ```css 22 | .squircle { 23 | mask-image: paint(squircle); 24 | --squircle-mode: mask-image; 25 | } 26 | ``` 27 | 28 | We advise you to use a no-repeat mask-image to avoid certain mini visual bugs that might occur on slow devices when resizing a squircle (only on browsers not compatible with `CSS.paintWorklet`). 29 | 30 | ```css 31 | .squircle { 32 | mask-image: paint(squircle); 33 | --squircle-mode: mask-image; 34 | 35 | @supports not (mask-image: paint(squircle)) { 36 | mask-repeat: no-repeat; 37 | } 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /apps/docs/components/docs/footer.tsx: -------------------------------------------------------------------------------- 1 | export const Footer = () => { 2 | return ( 3 |
4 |
5 |
6 |

7 | By{' '} 8 | 13 | Skyleen 14 | 15 | . The source code is available on{' '} 16 | 21 | GitHub 22 | 23 | . 24 |

25 |
26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /apps/docs/content/docs/css/background.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Background 3 | description: Paint a squircle background on an element. 4 | --- 5 | 6 | import { TypeTable } from '@/components/docs/type-table'; 7 | 8 | ## Variables 9 | 10 | 18 | 19 | 20 | The `background` mode is the default mode, so you don't need to set it explicitly here. 21 | 22 | 23 | ## Usage 24 | 25 | ```css 26 | .squircle { 27 | background: paint(squircle); 28 | } 29 | ``` 30 | 31 | We advise you to use a no-repeat background to avoid certain mini visual bugs that might occur on slow devices when resizing a squircle (only on browsers not compatible with `CSS.paintWorklet`). 32 | 33 | ```css 34 | .squircle { 35 | background: paint(squircle); 36 | 37 | @supports not (background: paint(squircle)) { 38 | background-repeat: no-repeat; 39 | } 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /apps/docs/components/footer.tsx: -------------------------------------------------------------------------------- 1 | export const Footer = () => { 2 | return ( 3 |
4 |
5 |

6 | By{' '} 7 | 13 | Skyleen 14 | 15 | . The source code is available on{' '} 16 | 22 | GitHub 23 | 24 | . 25 |

26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /apps/docs/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@squircle/core", 3 | "version": "1.0.6", 4 | "homepage": "https://squircle.skyleen.dev", 5 | "author": "Skyleen", 6 | "description": "A library for creating beautiful squircles in CSS, offering full customization and seamless integration as well as Tailwind CSS integration.", 7 | "main": "./dist/index.js", 8 | "module": "./dist/index.mjs", 9 | "types": "./dist/index.d.ts", 10 | "files": [ 11 | "dist" 12 | ], 13 | "exports": { 14 | ".": { 15 | "types": "./dist/index.d.ts", 16 | "import": "./dist/index.mjs", 17 | "require": "./dist/index.js" 18 | } 19 | }, 20 | "scripts": { 21 | "build": "tsup", 22 | "dev": "tsup --watch" 23 | }, 24 | "keywords": [ 25 | "squircle" 26 | ], 27 | "license": "MIT", 28 | "devDependencies": { 29 | "@squircle/paint-polyfill": "workspace:*", 30 | "@types/node": "^22.15.2", 31 | "tsup": "^8.4.0", 32 | "typescript": "^5.8.3" 33 | }, 34 | "peerDependencies": { 35 | "@squircle/paint-polyfill": "^1.0.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Elliot Sutton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/tailwindcss/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Elliot Sutton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/docs/content/docs/tailwindcss/background.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Background 3 | description: Paint a squircle background on an element. 4 | --- 5 | 6 | import { TailwindTypeTable } from '@/components/docs/tailwind-type-table'; 7 | 8 | ## Variables 9 | 10 | 37 | 38 | ## Usage 39 | 40 | ```tsx 41 |
42 | ``` 43 | -------------------------------------------------------------------------------- /apps/docs/content/docs/tailwindcss/mask-image.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mask Image 3 | description: Paint a squircle mask on an element. 4 | --- 5 | 6 | import { TailwindTypeTable } from '@/components/docs/tailwind-type-table'; 7 | 8 | ## Variables 9 | 10 | 37 | 38 | ## Usage 39 | 40 | ```tsx 41 |
42 | ``` 43 | -------------------------------------------------------------------------------- /apps/docs/app/docs/[[...slug]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { source } from '@/lib/source'; 2 | import { 3 | DocsBody, 4 | DocsDescription, 5 | DocsPage, 6 | DocsTitle, 7 | } from 'fumadocs-ui/page'; 8 | import { notFound } from 'next/navigation'; 9 | import { getMDXComponents } from '@/mdx-components'; 10 | import { Footer } from '@/components/docs/footer'; 11 | 12 | export default async function Page(props: { 13 | params: Promise<{ slug?: string[] }>; 14 | }) { 15 | const params = await props.params; 16 | const page = source.getPage(params.slug); 17 | if (!page) notFound(); 18 | 19 | const MDX = page.data.body; 20 | 21 | return ( 22 | }} 26 | tableOfContent={{ style: 'clerk' }} 27 | > 28 | {page.data.title} 29 | {page.data.description} 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | export async function generateStaticParams() { 38 | return source.generateParams(); 39 | } 40 | 41 | export async function generateMetadata(props: { 42 | params: Promise<{ slug?: string[] }>; 43 | }) { 44 | const params = await props.params; 45 | const page = source.getPage(params.slug); 46 | if (!page) notFound(); 47 | 48 | return { 49 | title: page.data.title, 50 | description: page.data.description, 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /apps/docs/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /apps/docs/components/header.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useTheme } from 'next-themes'; 4 | import { Logo } from './logo/logo'; 5 | import { MoonIcon, SunIcon } from 'lucide-react'; 6 | import { Button } from './ui/button'; 7 | import { motion } from 'motion/react'; 8 | import Link from 'next/link'; 9 | 10 | export const Header = () => { 11 | const { resolvedTheme, setTheme } = useTheme(); 12 | 13 | return ( 14 |
15 |
16 | 17 | 18 |
19 | 28 | 29 | 42 |
43 |
44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /packages/paint-polyfill/src/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | * 13 | * Modifications by Skyleen - 2025 14 | * Edited to add support for @layer, animation tracking, and some other features 15 | */ 16 | 17 | /** Canvas#toBlob() ponyfill */ 18 | export function canvasToBlob(canvas, callback, type, quality) { 19 | if (canvas.toBlob) return canvas.toBlob(callback, type, quality); 20 | 21 | let bin = atob(canvas.toDataURL(type, quality).split(',')[1]), 22 | arr = new Uint8Array(bin.length); 23 | for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i); 24 | callback(new Blob([arr], { type })); 25 | } 26 | 27 | /** Basically fetch(u).then( r => r.text() ) */ 28 | export function fetchText(url, callback) { 29 | let xhr = new XMLHttpRequest(); 30 | xhr.onreadystatechange = () => { 31 | if (xhr.readyState === 4) { 32 | callback(xhr.responseText); 33 | } 34 | }; 35 | xhr.open('GET', url, true); 36 | xhr.send(); 37 | } 38 | 39 | /** Object.defineProperty() ponyfill */ 40 | export function defineProperty(obj, name, def) { 41 | if (Object.defineProperty) { 42 | Object.defineProperty(obj, name, def); 43 | } else { 44 | obj[name] = def.get(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/paint-polyfill/src/realm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | * 13 | * Modifications by Skyleen - 2025 14 | * Edited to add support for @layer, animation tracking, and some other features 15 | */ 16 | 17 | export function Realm(scope, parentElement) { 18 | let frame = document.createElement('iframe'); 19 | frame.style.cssText = 20 | 'position:absolute; left:0; top:-999px; width:1px; height:1px;'; 21 | parentElement.appendChild(frame); 22 | let win = frame.contentWindow, 23 | doc = win.document, 24 | vars = 'var window,$hook'; 25 | for (let i in win) { 26 | if (!(i in scope) && i !== 'eval') { 27 | vars += ','; 28 | vars += i; 29 | } 30 | } 31 | for (let i in scope) { 32 | vars += ','; 33 | vars += i; 34 | vars += '=self.'; 35 | vars += i; 36 | } 37 | let script = doc.createElement('script'); 38 | script.appendChild( 39 | doc.createTextNode( 40 | `function $hook(self,console) {"use strict"; 41 | ${vars};return function() {return eval(arguments[0])}}`, 42 | ), 43 | ); 44 | doc.body.appendChild(script); 45 | this.exec = win.$hook(scope, console); 46 | // this.destroy = () => { parentElement.removeChild(frame); }; 47 | } 48 | -------------------------------------------------------------------------------- /apps/docs/content/docs/css/border-smoothing.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Border Smoothing 3 | description: Customize the smoothing of a squircle. 4 | --- 5 | 6 | import { TypeTable } from '@/components/docs/type-table'; 7 | 8 | ## Variables 9 | 10 | 34 | 35 | 36 | We recommend a value **between 0.2 and 1** for best results. 37 | 38 | 39 | ## Usage 40 | 41 | You can customize the smoothing of a squircle by using the `--squircle-border-smoothing` variable. 42 | 43 | ```css 44 | .squircle { 45 | --squircle-border-smoothing: 0.5; 46 | } 47 | ``` 48 | 49 | You can also customize the border smoothing of each corner: 50 | 51 | ```css 52 | .squircle { 53 | --squircle-border-top-left-smoothing: 0.2; 54 | --squircle-border-top-right-smoothing: 0.4; 55 | --squircle-border-bottom-right-smoothing: 0.6; 56 | --squircle-border-bottom-left-smoothing: 0.8; 57 | } 58 | -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "postinstall": "fumadocs-mdx" 11 | }, 12 | "dependencies": { 13 | "@bprogress/next": "^3.2.12", 14 | "@radix-ui/react-collapsible": "^1.1.8", 15 | "@radix-ui/react-label": "^2.1.4", 16 | "@radix-ui/react-popover": "^1.1.11", 17 | "@radix-ui/react-slider": "^1.3.2", 18 | "@radix-ui/react-slot": "^1.2.0", 19 | "@radix-ui/react-switch": "^1.2.2", 20 | "@radix-ui/react-tabs": "^1.1.9", 21 | "@squircle/core": "workspace:*", 22 | "@squircle/tailwindcss": "workspace:*", 23 | "@squircle/paint-polyfill": "workspace:*", 24 | "class-variance-authority": "^0.7.1", 25 | "clsx": "^2.1.1", 26 | "css-paint-polyfill": "^3.4.0", 27 | "flubber": "^0.4.2", 28 | "fumadocs-core": "^15.2.12", 29 | "fumadocs-mdx": "^11.6.1", 30 | "fumadocs-ui": "^15.2.12", 31 | "lucide-react": "^0.503.0", 32 | "mdx": "^0.3.1", 33 | "motion": "^12.9.2", 34 | "next": "15.3.1", 35 | "next-themes": "^0.4.6", 36 | "react": "^19.0.0", 37 | "react-color-palette": "^7.3.0", 38 | "react-dom": "^19.0.0", 39 | "react-use-measure": "^2.1.7", 40 | "shiki": "^3.3.0", 41 | "tailwind-merge": "^3.2.0" 42 | }, 43 | "devDependencies": { 44 | "@eslint/eslintrc": "^3", 45 | "@tailwindcss/postcss": "^4", 46 | "@types/flubber": "^0.4.0", 47 | "@types/mdx": "^2.0.13", 48 | "@types/node": "^20", 49 | "@types/react": "^19", 50 | "@types/react-dom": "^19", 51 | "eslint": "^9", 52 | "eslint-config-next": "15.3.1", 53 | "tailwindcss": "^4", 54 | "tw-animate-css": "^1.2.8", 55 | "typescript": "^5" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/docs/content/docs/css/border-radius.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Border Radius 3 | description: Customize the radius of a squircle. 4 | --- 5 | 6 | import { TypeTable } from '@/components/docs/type-table'; 7 | 8 | ## Variables 9 | 10 | 34 | 35 | ## Usage 36 | 37 | You can customize the border radius of a squircle by using the `--squircle-border-radius` variable. 38 | 39 | ```css 40 | .squircle { 41 | --squircle-border-radius: 10px; 42 | } 43 | ``` 44 | 45 | The value can be in **px** or a **simple number**: 46 | 47 | ```css 48 | .squircle { 49 | --squircle-border-radius: 10; 50 | } 51 | ``` 52 | 53 | 54 | Other values like rem, em, vh, vw, etc. are not supported. 55 | 56 | 57 | You can also customize the border radius of each corner: 58 | 59 | ```css 60 | .squircle { 61 | --squircle-border-top-left-radius: 5px; 62 | --squircle-border-top-right-radius: 10px; 63 | --squircle-border-bottom-right-radius: 15px; 64 | --squircle-border-bottom-left-radius: 20px; 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /apps/docs/components/path-morphing.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { interpolate } from 'flubber'; 4 | import { 5 | animate, 6 | motion, 7 | MotionValue, 8 | useMotionValue, 9 | useTransform, 10 | } from 'motion/react'; 11 | import { useEffect, useState } from 'react'; 12 | 13 | const getIndex = (_: string, index: number) => index; 14 | 15 | function useFlubber(progress: MotionValue, paths: string[]) { 16 | return useTransform(progress, paths.map(getIndex), paths, { 17 | mixer: (a, b) => interpolate(a, b, { maxSegmentLength: 0.1 }), 18 | }); 19 | } 20 | 21 | export function PathMorphing({ 22 | paths, 23 | color, 24 | ...props 25 | }: { 26 | paths: string[]; 27 | color: string; 28 | } & React.SVGProps) { 29 | const [pathIndex, setPathIndex] = useState(0); 30 | const progress = useMotionValue(pathIndex); 31 | const fill = useTransform( 32 | progress, 33 | paths.map(getIndex), 34 | paths.map(() => color), 35 | ); 36 | const path = useFlubber(progress, paths); 37 | 38 | useEffect(() => { 39 | const animation = animate(progress, pathIndex, { 40 | duration: 0.8, 41 | ease: 'easeInOut', 42 | onComplete: () => { 43 | setTimeout(() => { 44 | if (pathIndex === paths.length - 1) { 45 | progress.set(0); 46 | setPathIndex(1); 47 | } else { 48 | setPathIndex(pathIndex + 1); 49 | } 50 | }, 3000); 51 | }, 52 | }); 53 | 54 | // if (pathIndex === paths.length - 1) { 55 | // animation.stop(); 56 | // } 57 | 58 | return () => animation.stop(); 59 | }, [pathIndex, paths.length, progress]); 60 | 61 | return ( 62 |
63 | 64 | 65 | 66 | 67 | 68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /apps/docs/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as TabsPrimitive from '@radix-ui/react-tabs'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | function Tabs({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 18 | ); 19 | } 20 | 21 | function TabsList({ 22 | className, 23 | ...props 24 | }: React.ComponentProps) { 25 | return ( 26 | 34 | ); 35 | } 36 | 37 | function TabsTrigger({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ); 51 | } 52 | 53 | function TabsContent({ 54 | className, 55 | ...props 56 | }: React.ComponentProps) { 57 | return ( 58 | 63 | ); 64 | } 65 | 66 | export { Tabs, TabsList, TabsTrigger, TabsContent }; 67 | -------------------------------------------------------------------------------- /apps/docs/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', 14 | destructive: 15 | 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', 16 | outline: 17 | 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', 18 | secondary: 19 | 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', 20 | ghost: 21 | 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', 22 | link: 'text-primary underline-offset-4 hover:underline', 23 | }, 24 | size: { 25 | default: 'h-9 px-4 py-2 has-[>svg]:px-3', 26 | sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', 27 | lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', 28 | icon: 'size-9', 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: 'default', 33 | size: 'default', 34 | }, 35 | }, 36 | ); 37 | 38 | function Button({ 39 | className, 40 | variant, 41 | size, 42 | asChild = false, 43 | ...props 44 | }: React.ComponentProps<'button'> & 45 | VariantProps & { 46 | asChild?: boolean; 47 | }) { 48 | const Comp = asChild ? Slot : 'button'; 49 | 50 | return ( 51 | 56 | ); 57 | } 58 | 59 | export { Button, buttonVariants }; 60 | -------------------------------------------------------------------------------- /apps/docs/components/logo/css-logo.tsx: -------------------------------------------------------------------------------- 1 | export default function CSSLogo(props: React.SVGProps) { 2 | return ( 3 | 10 | 14 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/docs/content/docs/limitations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Limitations 3 | --- 4 | 5 | While Squircle is designed to work seamlessly across all modern browsers, there are a few limitations to keep in mind: 6 | 7 | - Squircle uses a custom polyfill, `@squircle/paint-polyfill`, a heavily improved fork of `css-paint-polyfill`. 8 | This polyfill brings CSS Houdini compatibility to browsers that do not support `CSS.paintWorklet` natively. 9 | 10 | - However, **very old browsers**, such as **Internet Explorer** and **some early versions of Edge**, are not supported. These browsers do not support the necessary underlying APIs to run the polyfill reliably. 11 | _(Note: Internet Explorer is not officially tested and should be considered unsupported.)_ 12 | 13 | - In browsers without native CSS Houdini support (thus relying on `@squircle/paint-polyfill`), there may be **minor performance overhead**. 14 | This is because the polyfill **actively listens** to style changes and dynamically **replaces paint operations** in the DOM, which can slightly impact performance, especially in large or highly dynamic applications. 15 | 16 | 17 | Additionally, when using animation libraries such as [Motion](https://motion.dev/) in browsers that do not support `CSS.paintWorklet` (**Firefox** and **Safari**), performance issues may become more noticeable. 18 | Since the polyfill relies on a MutationObserver to detect and respond to style changes, animations that rapidly and repeatedly update styles can trigger frequent DOM updates, potentially resulting in less smooth animations. 19 | We are currently exploring solutions to mitigate this impact. 20 | 21 | 22 | In fully CSS Houdini-compatible browsers, Squircle has near-zero performance impact and runs natively without any emulation layer. 23 | 24 | ## Browser Support 25 | 26 | Squircle is designed to work seamlessly across all modern browsers: 27 | 28 | | Browser | Native support | With polyfill | 29 | |:--------|:-------:|:-------:| 30 | | **Chrome** | ✅ 65+ | ✅ | 31 | | **Edge** | ✅ 79+ | ✅ | 32 | | **Firefox** | ❌ | ☑️ | 33 | | **Opera** | ✅ 52+ | ✅ | 34 | | **Safari** | ❌ | ☑️ | 35 | | **Chrome Android** | ✅ 65+ | ✅ | 36 | | **Firefox for Android** | ❌ | ☑️ | 37 | | **Opera Android** | ✅ 47+ | ✅ | 38 | | **Safari on iOS** | ❌ | ☑️ | 39 | | **Samsung Internet** | ✅ 9+ | ✅ | 40 | | **WebView Android** | ✅ 65+ | ✅ | 41 | | **Internet Explorer** | ❌ | ❌ | 42 | 43 | - ✅: Supported natively 44 | - ☑️: Supported with `@squircle/paint-polyfill` ([see limitations](#limitations)) 45 | - ❌: Not supported 46 | -------------------------------------------------------------------------------- /apps/docs/components/color-picker.tsx: -------------------------------------------------------------------------------- 1 | import { Label } from './ui/label'; 2 | import { 3 | ColorPicker as ColorPickerPrimitive, 4 | ColorService, 5 | type IColor, 6 | } from 'react-color-palette'; 7 | import 'react-color-palette/css'; 8 | 9 | import { 10 | Popover, 11 | PopoverContent, 12 | PopoverTrigger, 13 | } from './animate-ui/radix/popover'; 14 | import { Input } from './ui/input'; 15 | import { cn } from '@/lib/utils'; 16 | import { useState } from 'react'; 17 | 18 | export const ColorPicker = ({ 19 | label, 20 | color, 21 | setColor, 22 | }: { 23 | label: string; 24 | color: IColor; 25 | setColor: (color: IColor) => void; 26 | }) => { 27 | const [localColor, setLocalColor] = useState(color.hex); 28 | 29 | return ( 30 |
31 | 32 | 33 |
34 | { 38 | if (e.target.value.startsWith('#')) { 39 | setLocalColor(e.target.value); 40 | } else { 41 | setLocalColor('#'); 42 | } 43 | setColor(ColorService.convert('hex', e.target.value)); 44 | }} 45 | onBlur={() => { 46 | setLocalColor(color.hex); 47 | }} 48 | className="h-10 text-lg rounded-lg bg-background shadow-none" 49 | /> 50 | 51 | 52 |
79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /apps/docs/content/docs/getting-started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | --- 4 | 5 | import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; 6 | import { Button } from '@/components/ui/button'; 7 | import { ThumbsUp } from 'lucide-react'; 8 | 9 | ## Installation 10 | 11 | 12 | 13 | ```bash 14 | npm install @squircle/core @squircle/paint-polyfill 15 | ``` 16 | 17 | 18 | ```bash 19 | pnpm add @squircle/core @squircle/paint-polyfill 20 | ``` 21 | 22 | 23 | ```bash 24 | yarn add @squircle/core @squircle/paint-polyfill 25 | ``` 26 | 27 | 28 | ```bash 29 | bun add @squircle/core @squircle/paint-polyfill 30 | ``` 31 | 32 | 33 | 34 | ## Import 35 | 36 | ```ts 37 | import { init } from '@squircle/core'; 38 | ``` 39 | 40 | ## Initialize 41 | 42 | ```ts 43 | init(); 44 | ``` 45 | 46 | ## Initialize in React 47 | 48 | ```tsx title="squircle-provider.tsx" 49 | 'use client'; 50 | 51 | import * as React from 'react'; 52 | import { init } from '@squircle/core'; 53 | 54 | export function SquircleProvider({ children }: { children: React.ReactNode }) { 55 | React.useEffect(() => void init(), []); 56 | return children; 57 | } 58 | ``` 59 | 60 | Then use the `SquircleProvider` component to wrap your app. 61 | 62 | ```tsx 63 | 64 | 65 | 66 | ``` 67 | 68 | ## Usage 69 | 70 | ### CSS 71 | 72 | ```css 73 | .squircle { 74 | background: paint(squircle); 75 | --squircle-background-color: red; 76 | --squircle-border-color: blue; 77 | --squircle-border-width: 10px; 78 | --squircle-border-radius: 30px; 79 | } 80 | ``` 81 | 82 | ### Tailwind CSS 83 | 84 | 85 | Only support Tailwind >= 4.0. 86 | 87 | 88 | #### Install 89 | 90 | 91 | 92 | ```bash 93 | npm install @squircle/tailwindcss 94 | ``` 95 | 96 | 97 | ```bash 98 | pnpm add @squircle/tailwindcss 99 | ``` 100 | 101 | 102 | ```bash 103 | yarn add @squircle/tailwindcss 104 | ``` 105 | 106 | 107 | ```bash 108 | bun add @squircle/tailwindcss 109 | ``` 110 | 111 | 112 | 113 | #### Import 114 | 115 | ```css title="globals.css" 116 | @import '@squircle/tailwindcss'; 117 | ``` 118 | 119 | #### Usage 120 | 121 | ```html 122 |
123 | ``` 124 | -------------------------------------------------------------------------------- /apps/docs/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | ## Why Squircle? 6 | 7 | Creating customizable squircles with CSS can quickly become complex. When I searched for a simple, modern solution that could be easily integrated — especially with Tailwind CSS — I couldn't find any plugin that combined: 8 | 9 | - true ease of use, 10 | - a high level of customization, 11 | - broad browser compatibility, 12 | - active maintenance and modern best practices. 13 | 14 | That's why **Squircle** was born. 15 | 16 | I combined and improved the best ideas and existing projects to offer a powerful yet accessible solution. Squircle is based on: 17 | 18 | - CSS Houdini, inspired by [Pavel Laptev](https://github.com/PavelLaptev/css-houdini-squircle)'s work, which I used as a foundation to build a more flexible and highly customizable solution. 19 | - [css-paint-polyfill](https://github.com/GoogleChromeLabs/css-paint-polyfill) by Google Chrome Labs, which I forked and extended to: 20 | - support CSS `@layer` usage, 21 | - enable smooth property animations, 22 | - handle dynamic theme switching (light/dark mode). 23 | - some optimizations for performance and browser compatibility. 24 | 25 | Finally, I developed a dedicated Tailwind CSS plugin, designed with the latest best practices from Tailwind 4, to make Squircle easy to use in any modern project. 26 | 27 | Squircle is the ideal solution to create smooth, aesthetic, and highly customizable squircles — with minimal setup and maximum flexibility. 28 | 29 | ## How Squircle Works 30 | 31 | Squircle leverages **CSS Houdini** and the `CSS.paintWorklet` API to create fully customizable squircles directly in the browser. 32 | 33 | To ensure broad browser compatibility, Squircle integrates an improved version of [css-paint-polyfill](https://github.com/GoogleChromeLabs/css-paint-polyfill), which I forked and enhanced. This polyfill allows Squircle to work seamlessly across all modern browsers. 34 | 35 | To initialize Squircle, you need to call an `init()` function in your code. This function registers the `CSS.paintWorklet` module and ensures that squircles can be rendered even if the browser doesn't support Houdini natively. 36 | 37 | Once initialized, customization is fully handled through `CSS variables`. You can easily control the shape, size, smoothing, colors, and other properties directly in your styles. 38 | 39 | Since Squircle uses a **paint** operation under the hood, it essentially draws the squircle on a canvas. 40 | You can use this squircle rendering in two main ways: 41 | 42 | - As a **background** (`background: paint(squircle)`), 43 | - As a **mask-image** (`mask-image: paint(squircle)`). 44 | 45 | This approach allows you to create complex squircles that would be difficult or impossible to achieve with traditional CSS properties. 46 | -------------------------------------------------------------------------------- /apps/docs/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import { Geist, Geist_Mono } from 'next/font/google'; 3 | import './globals.css'; 4 | import { ThemeProvider } from '@/components/theme-provider'; 5 | import { SquircleProvider } from '@/components/squircle-provider'; 6 | import { RootProvider } from 'fumadocs-ui/provider'; 7 | import { BProgressProvider } from '@/components/bprogress-provider'; 8 | 9 | export const metadata: Metadata = { 10 | title: { 11 | template: '%s - Squircle', 12 | default: 'Squircle', 13 | }, 14 | description: 15 | 'A library for creating beautiful squircles in CSS, offering full customization and seamless integration as well as Tailwind CSS integration.', 16 | authors: [ 17 | { 18 | name: 'imskyleen', 19 | url: 'https://github.com/imskyleen', 20 | }, 21 | ], 22 | openGraph: { 23 | title: 'Squircle', 24 | description: 25 | 'A library for creating beautiful squircles in CSS, offering full customization and seamless integration as well as Tailwind CSS integration.', 26 | url: 'https://squircle.skyleen.dev', 27 | siteName: 'Squircle', 28 | images: [ 29 | { 30 | url: 'https://squircle.skyleen.dev/og-image.png', 31 | width: 1200, 32 | height: 630, 33 | alt: 'Squircle', 34 | }, 35 | ], 36 | locale: 'en_US', 37 | type: 'website', 38 | }, 39 | twitter: { 40 | card: 'summary_large_image', 41 | site: '@imskyleen', 42 | title: 'Squircle', 43 | description: 44 | 'A library for creating beautiful squircles in CSS, offering full customization and seamless integration as well as Tailwind CSS integration.', 45 | images: [ 46 | { 47 | url: 'https://squircle.skyleen.dev/og-image.png', 48 | width: 1200, 49 | height: 630, 50 | alt: 'Squircle', 51 | }, 52 | ], 53 | }, 54 | }; 55 | 56 | const geistSans = Geist({ 57 | variable: '--font-geist-sans', 58 | subsets: ['latin'], 59 | }); 60 | 61 | const geistMono = Geist_Mono({ 62 | variable: '--font-geist-mono', 63 | subsets: ['latin'], 64 | }); 65 | 66 | export default function RootLayout({ 67 | children, 68 | }: Readonly<{ 69 | children: React.ReactNode; 70 | }>) { 71 | return ( 72 | 73 | 76 | 82 | 83 | 84 | {children} 85 | 86 | 87 | 88 | 89 | 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /apps/docs/components/code-editor/render.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { motion } from 'motion/react'; 5 | import Image from 'next/image'; 6 | import { Star } from 'lucide-react'; 7 | 8 | const COLORS = ['#d0dfe1', '#e3dbe5', '#dcd5ce', '#817f82']; 9 | 10 | export const CodeEditorRender = () => { 11 | const [color, setColor] = React.useState(COLORS[0]); 12 | 13 | return ( 14 | 15 | 16 | iPad Air 23 | 24 |
25 |

26 | iPad Apple Air 11" Puce M3 128 Go Wifi 7th generation 2025 27 |

28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 4.1 (123) 37 | 38 |
39 | 40 |
41 | {COLORS.map((c) => ( 42 | setColor(c)} 45 | className="relative squircle squircle-5 size-6" 46 | style={ 47 | { '--squircle-background-color': c } as React.CSSProperties 48 | } 49 | > 50 | {color === c && ( 51 | 56 | )} 57 | 58 | ))} 59 |
60 | 61 | 65 | Add to cart 66 | 67 |
68 |
69 |
70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /packages/tailwindcss/README.md: -------------------------------------------------------------------------------- 1 | # Squircle with Tailwind CSS 2 | 3 | A simple and flexible way to make Squircles with Tailwind CSS. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install @squircle/core @squircle/paint-polyfill @squircle/tailwindcss 9 | ``` 10 | 11 | ## Import 12 | 13 | ```ts 14 | import { init } from '@squircle/core'; 15 | ``` 16 | 17 | ## Initialize 18 | 19 | ```ts 20 | init(); 21 | ``` 22 | 23 | ## Initialize in React 24 | 25 | ```tsx title="squircle-provider.tsx" 26 | 'use client'; 27 | 28 | import * as React from 'react'; 29 | import { init } from '@squircle/core'; 30 | 31 | export function SquircleProvider({ children }: { children: React.ReactNode }) { 32 | React.useEffect(() => void init(), []); 33 | return children; 34 | } 35 | ``` 36 | 37 | Then use the `SquircleProvider` component to wrap your app. 38 | 39 | ```tsx 40 | 41 | 42 | 43 | ``` 44 | 45 | ## Usage 46 | 47 | **Only support Tailwind >= 4.0.** 48 | 49 | ### Import 50 | 51 | ```css title="globals.css" 52 | @import '@squircle/tailwindcss'; 53 | ``` 54 | 55 | ### Use 56 | 57 | ```html 58 |
59 | ``` 60 | 61 | ## Beta phase 62 | 63 | Squircle is in beta phase, so there may be bugs and optimization issues for browsers not compatible with `CSS.paintWorklet`. 64 | 65 | ## Limitations 66 | 67 | While Squircle is designed to work seamlessly across all modern browsers, there are a few limitations to keep in mind: 68 | 69 | - Squircle uses a custom polyfill, `@squircle/paint-polyfill`, a heavily improved fork of `css-paint-polyfill`. 70 | This polyfill brings CSS Houdini compatibility to browsers that do not support `CSS.paintWorklet` natively. 71 | 72 | - However, **very old browsers**, such as **Internet Explorer** and **some early versions of Edge**, are not supported. These browsers do not support the necessary underlying APIs to run the polyfill reliably. 73 | _(Note: Internet Explorer is not officially tested and should be considered unsupported.)_ 74 | 75 | - In browsers without native CSS Houdini support (thus relying on `@squircle/paint-polyfill`), there may be **minor performance overhead**. 76 | This is because the polyfill **actively listens** to style changes and dynamically **replaces paint operations** in the DOM, which can slightly impact performance, especially in large or highly dynamic applications. 77 | 78 | ⚠ Additionally, when using animation libraries such as [Motion](https://motion.dev/) in browsers that do not support `CSS.paintWorklet` (**Firefox** and **Safari**), performance issues may become more noticeable. 79 | Since the polyfill relies on a MutationObserver to detect and respond to style changes, animations that rapidly and repeatedly update styles can trigger frequent DOM updates, potentially resulting in less smooth animations. 80 | We are currently exploring solutions to mitigate this impact. 81 | 82 | ## Issues 83 | 84 | If you encounter any problems, do not hesitate to [open an issue](https://github.com/imskyleen/squircle/issues) or [make a PR here](https://github.com/imskyleen/squircle). 85 | 86 | ## LICENSE 87 | 88 | MIT 89 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # Squircle 2 | 3 | A simple and flexible way to make Squircles with CSS. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install @squircle/core @squircle/paint-polyfill 9 | ``` 10 | 11 | ## Import 12 | 13 | ```ts 14 | import { init } from '@squircle/core'; 15 | ``` 16 | 17 | ## Initialize 18 | 19 | ```ts 20 | init(); 21 | ``` 22 | 23 | ## Initialize in React 24 | 25 | ```tsx title="squircle-provider.tsx" 26 | 'use client'; 27 | 28 | import * as React from 'react'; 29 | import { init } from '@squircle/core'; 30 | 31 | export function SquircleProvider({ children }: { children: React.ReactNode }) { 32 | React.useEffect(() => void init(), []); 33 | return children; 34 | } 35 | ``` 36 | 37 | Then use the `SquircleProvider` component to wrap your app. 38 | 39 | ```tsx 40 | 41 | 42 | 43 | ``` 44 | 45 | ## Usage 46 | 47 | ### CSS 48 | 49 | ```css 50 | .squircle { 51 | background: paint(squircle); 52 | --squircle-background-color: red; 53 | --squircle-border-color: blue; 54 | --squircle-border-width: 10px; 55 | --squircle-border-radius: 30px; 56 | } 57 | ``` 58 | 59 | ### Tailwind CSS 60 | 61 | Use the [@squircle/tailwindcss](https://www.npmjs.com/package/@squircle/tailwindcss) module. 62 | 63 | ## Beta phase 64 | 65 | Squircle is in beta phase, so there may be bugs and optimization issues for browsers not compatible with `CSS.paintWorklet`. 66 | 67 | ## Limitations 68 | 69 | While Squircle is designed to work seamlessly across all modern browsers, there are a few limitations to keep in mind: 70 | 71 | - Squircle uses a custom polyfill, `@squircle/paint-polyfill`, a heavily improved fork of `css-paint-polyfill`. 72 | This polyfill brings CSS Houdini compatibility to browsers that do not support `CSS.paintWorklet` natively. 73 | 74 | - However, **very old browsers**, such as **Internet Explorer** and **some early versions of Edge**, are not supported. These browsers do not support the necessary underlying APIs to run the polyfill reliably. 75 | _(Note: Internet Explorer is not officially tested and should be considered unsupported.)_ 76 | 77 | - In browsers without native CSS Houdini support (thus relying on `@squircle/paint-polyfill`), there may be **minor performance overhead**. 78 | This is because the polyfill **actively listens** to style changes and dynamically **replaces paint operations** in the DOM, which can slightly impact performance, especially in large or highly dynamic applications. 79 | 80 | ⚠ Additionally, when using animation libraries such as [Motion](https://motion.dev/) in browsers that do not support `CSS.paintWorklet` (**Firefox** and **Safari**), performance issues may become more noticeable. 81 | Since the polyfill relies on a MutationObserver to detect and respond to style changes, animations that rapidly and repeatedly update styles can trigger frequent DOM updates, potentially resulting in less smooth animations. 82 | We are currently exploring solutions to mitigate this impact. 83 | 84 | ## Issues 85 | 86 | If you encounter any problems, do not hesitate to [open an issue](https://github.com/imskyleen/squircle/issues) or [make a PR here](https://github.com/imskyleen/squircle). 87 | 88 | ## LICENSE 89 | 90 | MIT 91 | -------------------------------------------------------------------------------- /apps/docs/components/playground.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { Slider } from './slider'; 5 | import { ColorPicker } from './color-picker'; 6 | import { useColor } from 'react-color-palette'; 7 | 8 | export const Playground = () => { 9 | const [radius, setRadius] = useState(40); 10 | const [smooth, setSmooth] = useState(0.8); 11 | const [borderSize, setBorderSize] = useState(10); 12 | const [backgroundColor, setBackgroundColor] = useColor('#2dd4bf'); 13 | const [borderColor, setBorderColor] = useColor('#99f6e4'); 14 | 15 | return ( 16 |
17 |
18 |
19 |

Try it out

20 | 21 | setRadius(v)} 27 | /> 28 | setSmooth(v)} 36 | /> 37 | setBorderSize(v)} 43 | /> 44 | 49 | 54 |
55 |
56 | 57 |
58 |
79 |
80 |
81 | ); 82 | }; 83 | -------------------------------------------------------------------------------- /apps/docs/components/docs/type-table.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Info as InfoIcon } from 'lucide-react'; 4 | import Link from 'fumadocs-core/link'; 5 | import { cva } from 'class-variance-authority'; 6 | import { 7 | Popover, 8 | PopoverContent, 9 | PopoverTrigger, 10 | } from '@/components/animate-ui/radix/popover'; 11 | import type { ReactNode } from 'react'; 12 | import { cn } from '@/lib/utils'; 13 | 14 | export function Info({ children }: { children: ReactNode }): ReactNode { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | 27 | interface ObjectType { 28 | /** 29 | * Additional description of the field 30 | */ 31 | description?: ReactNode; 32 | type: string; 33 | typeDescription?: ReactNode; 34 | /** 35 | * Optional link to the type 36 | */ 37 | typeDescriptionLink?: string; 38 | deprecated?: boolean; 39 | } 40 | 41 | const field = cva('inline-flex flex-row items-center gap-1'); 42 | const code = cva( 43 | 'rounded-md bg-fd-secondary p-1 text-fd-secondary-foreground', 44 | { 45 | variants: { 46 | color: { 47 | primary: 'bg-fd-primary/10 text-fd-primary', 48 | deprecated: 'line-through text-fd-primary/50', 49 | }, 50 | }, 51 | }, 52 | ); 53 | 54 | export function TypeTable({ type }: { type: Record }) { 55 | return ( 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {Object.entries(type).map(([key, value]) => ( 66 | 67 | 81 | 94 | 95 | ))} 96 | 97 |
VariableType
68 |
69 | 76 | {key} 77 | 78 | {value.description ? {value.description} : null} 79 |
80 |
82 |
83 | {value.type} 84 | {value.typeDescription ? ( 85 | {value.typeDescription} 86 | ) : null} 87 | {value.typeDescriptionLink ? ( 88 | 89 | 90 | 91 | ) : null} 92 |
93 |
98 |
99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /apps/docs/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as SliderPrimitive from '@radix-ui/react-slider'; 5 | import { AnimatePresence, motion } from 'motion/react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | import { SlidingNumber } from '../animate-ui/text/sliding-number'; 9 | 10 | function Slider({ 11 | className, 12 | defaultValue, 13 | value, 14 | min = 0, 15 | decimalPlaces = 0, 16 | max = 100, 17 | ...props 18 | }: React.ComponentProps & { 19 | decimalPlaces?: number; 20 | }) { 21 | const _values = React.useMemo( 22 | () => 23 | Array.isArray(value) 24 | ? value 25 | : Array.isArray(defaultValue) 26 | ? defaultValue 27 | : [min, max], 28 | [value, defaultValue, min, max], 29 | ); 30 | 31 | const [isHovered, setIsHovered] = React.useState(false); 32 | 33 | return ( 34 | 46 | 52 | 58 | 59 | {Array.from({ length: _values.length }, (_, index) => ( 60 | setIsHovered(true)} 65 | onMouseLeave={() => setIsHovered(false)} 66 | > 67 | 68 | {isHovered && ( 69 | 76 | 80 | 81 | )} 82 | 83 | 84 | ))} 85 | 86 | ); 87 | } 88 | 89 | export { Slider }; 90 | -------------------------------------------------------------------------------- /apps/docs/components/animate-ui/radix/collapsible.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; 5 | import { 6 | AnimatePresence, 7 | motion, 8 | type HTMLMotionProps, 9 | type Transition, 10 | } from 'motion/react'; 11 | 12 | type CollapsibleContextType = { 13 | isOpen: boolean; 14 | }; 15 | 16 | const CollapsibleContext = React.createContext< 17 | CollapsibleContextType | undefined 18 | >(undefined); 19 | 20 | const useCollapsible = (): CollapsibleContextType => { 21 | const context = React.useContext(CollapsibleContext); 22 | if (!context) { 23 | throw new Error('useCollapsible must be used within a Collapsible'); 24 | } 25 | return context; 26 | }; 27 | 28 | type CollapsibleProps = React.ComponentProps; 29 | 30 | function Collapsible({ children, ...props }: CollapsibleProps) { 31 | const [isOpen, setIsOpen] = React.useState( 32 | props?.open ?? props?.defaultOpen ?? false, 33 | ); 34 | 35 | React.useEffect(() => { 36 | if (props?.open !== undefined) setIsOpen(props.open); 37 | }, [props?.open]); 38 | 39 | const handleOpenChange = React.useCallback( 40 | (open: boolean) => { 41 | setIsOpen(open); 42 | props.onOpenChange?.(open); 43 | }, 44 | [props], 45 | ); 46 | 47 | return ( 48 | 49 | 54 | {children} 55 | 56 | 57 | ); 58 | } 59 | 60 | type CollapsibleTriggerProps = React.ComponentProps< 61 | typeof CollapsiblePrimitive.Trigger 62 | >; 63 | 64 | function CollapsibleTrigger(props: CollapsibleTriggerProps) { 65 | return ( 66 | 67 | ); 68 | } 69 | 70 | type CollapsibleContentProps = React.ComponentProps< 71 | typeof CollapsiblePrimitive.Content 72 | > & 73 | HTMLMotionProps<'div'> & { 74 | transition?: Transition; 75 | }; 76 | 77 | function CollapsibleContent({ 78 | className, 79 | children, 80 | transition = { type: 'spring', stiffness: 150, damping: 22 }, 81 | ...props 82 | }: CollapsibleContentProps) { 83 | const { isOpen } = useCollapsible(); 84 | 85 | return ( 86 | 87 | {isOpen && ( 88 | 89 | 100 | {children} 101 | 102 | 103 | )} 104 | 105 | ); 106 | } 107 | 108 | export { 109 | Collapsible, 110 | CollapsibleTrigger, 111 | CollapsibleContent, 112 | useCollapsible, 113 | type CollapsibleContextType, 114 | type CollapsibleProps, 115 | type CollapsibleTriggerProps, 116 | type CollapsibleContentProps, 117 | }; 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Squircle 2 | 3 | A simple and flexible way to make Squircles with CSS. 4 | 5 | ## Documentation 6 | 7 | [See full documentation here.](https://squircle.skyleen.dev) 8 | 9 | ## Installation 10 | 11 | ```bash 12 | npm install @squircle/core @squircle/paint-polyfill 13 | ``` 14 | 15 | ## Import 16 | 17 | ```ts 18 | import { init } from '@squircle/core'; 19 | ``` 20 | 21 | ## Initialize 22 | 23 | ```ts 24 | init(); 25 | ``` 26 | 27 | ## Initialize in React 28 | 29 | ```tsx title="squircle-provider.tsx" 30 | 'use client'; 31 | 32 | import * as React from 'react'; 33 | import { init } from '@squircle/core'; 34 | 35 | export function SquircleProvider({ children }: { children: React.ReactNode }) { 36 | React.useEffect(() => void init(), []); 37 | return children; 38 | } 39 | ``` 40 | 41 | Then use the `SquircleProvider` component to wrap your app. 42 | 43 | ```tsx 44 | 45 | 46 | 47 | ``` 48 | 49 | ## Usage 50 | 51 | ### CSS 52 | 53 | ```css 54 | .squircle { 55 | background: paint(squircle); 56 | --squircle-background-color: red; 57 | --squircle-border-color: blue; 58 | --squircle-border-width: 10px; 59 | --squircle-border-radius: 30px; 60 | } 61 | ``` 62 | 63 | ### Tailwind CSS 64 | 65 | **Only support Tailwind >= 4.0.** 66 | 67 | ### Import 68 | 69 | ```css title="globals.css" 70 | @import '@squircle/tailwindcss'; 71 | ``` 72 | 73 | ### Use 74 | 75 | ```html 76 |
77 | ``` 78 | 79 | ## Beta phase 80 | 81 | Squircle is in beta phase, so there may be bugs and optimization issues for browsers not compatible with `CSS.paintWorklet`. 82 | 83 | ## Limitations 84 | 85 | While Squircle is designed to work seamlessly across all modern browsers, there are a few limitations to keep in mind: 86 | 87 | - Squircle uses a custom polyfill, `@squircle/paint-polyfill`, a heavily improved fork of `css-paint-polyfill`. 88 | This polyfill brings CSS Houdini compatibility to browsers that do not support `CSS.paintWorklet` natively. 89 | 90 | - However, **very old browsers**, such as **Internet Explorer** and **some early versions of Edge**, are not supported. These browsers do not support the necessary underlying APIs to run the polyfill reliably. 91 | _(Note: Internet Explorer is not officially tested and should be considered unsupported.)_ 92 | 93 | - In browsers without native CSS Houdini support (thus relying on `@squircle/paint-polyfill`), there may be **minor performance overhead**. 94 | This is because the polyfill **actively listens** to style changes and dynamically **replaces paint operations** in the DOM, which can slightly impact performance, especially in large or highly dynamic applications. 95 | 96 | ⚠ Additionally, when using animation libraries such as [Motion](https://motion.dev/) in browsers that do not support `CSS.paintWorklet` (**Firefox** and **Safari**), performance issues may become more noticeable. 97 | Since the polyfill relies on a MutationObserver to detect and respond to style changes, animations that rapidly and repeatedly update styles can trigger frequent DOM updates, potentially resulting in less smooth animations. 98 | We are currently exploring solutions to mitigate this impact. 99 | 100 | ## Issues 101 | 102 | If you encounter any problems, do not hesitate to [open an issue](https://github.com/imskyleen/squircle/issues) or [make a PR here](https://github.com/imskyleen/squircle). 103 | 104 | ## LICENSE 105 | 106 | MIT 107 | -------------------------------------------------------------------------------- /apps/docs/components/animate-ui/effects/motion-effect.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { 5 | AnimatePresence, 6 | motion, 7 | useInView, 8 | type HTMLMotionProps, 9 | type UseInViewOptions, 10 | type Transition, 11 | type Variant, 12 | } from 'motion/react'; 13 | 14 | type MotionEffectProps = HTMLMotionProps<'div'> & { 15 | children: React.ReactNode; 16 | className?: string; 17 | transition?: Transition; 18 | delay?: number; 19 | inView?: boolean; 20 | inViewMargin?: UseInViewOptions['margin']; 21 | inViewOnce?: boolean; 22 | blur?: string | boolean; 23 | slide?: 24 | | { 25 | direction?: 'up' | 'down' | 'left' | 'right'; 26 | offset?: number; 27 | } 28 | | boolean; 29 | fade?: { initialOpacity?: number; opacity?: number } | boolean; 30 | zoom?: 31 | | { 32 | initialScale?: number; 33 | scale?: number; 34 | } 35 | | boolean; 36 | }; 37 | 38 | function MotionEffect({ 39 | ref, 40 | children, 41 | className, 42 | transition = { type: 'spring', stiffness: 200, damping: 20 }, 43 | delay = 0, 44 | inView = false, 45 | inViewMargin = '0px', 46 | inViewOnce = true, 47 | blur = false, 48 | slide = false, 49 | fade = false, 50 | zoom = false, 51 | ...props 52 | }: MotionEffectProps) { 53 | const localRef = React.useRef(null); 54 | React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement); 55 | 56 | const inViewResult = useInView(localRef, { 57 | once: inViewOnce, 58 | margin: inViewMargin, 59 | }); 60 | const isInView = !inView || inViewResult; 61 | 62 | const hiddenVariant: Variant = {}; 63 | const visibleVariant: Variant = {}; 64 | 65 | if (slide) { 66 | const offset = typeof slide === 'boolean' ? 100 : (slide.offset ?? 100); 67 | const direction = 68 | typeof slide === 'boolean' ? 'left' : (slide.direction ?? 'left'); 69 | const axis = direction === 'up' || direction === 'down' ? 'y' : 'x'; 70 | hiddenVariant[axis] = 71 | direction === 'left' || direction === 'up' ? -offset : offset; 72 | visibleVariant[axis] = 0; 73 | } 74 | 75 | if (fade) { 76 | hiddenVariant.opacity = 77 | typeof fade === 'boolean' ? 0 : (fade.initialOpacity ?? 0); 78 | visibleVariant.opacity = 79 | typeof fade === 'boolean' ? 1 : (fade.opacity ?? 1); 80 | } 81 | 82 | if (zoom) { 83 | hiddenVariant.scale = 84 | typeof zoom === 'boolean' ? 0.5 : (zoom.initialScale ?? 0.5); 85 | visibleVariant.scale = typeof zoom === 'boolean' ? 1 : (zoom.scale ?? 1); 86 | } 87 | 88 | if (blur) { 89 | hiddenVariant.filter = 90 | typeof blur === 'boolean' ? 'blur(10px)' : `blur(${blur})`; 91 | visibleVariant.filter = 'blur(0px)'; 92 | } 93 | 94 | return ( 95 | 96 | 113 | {children} 114 | 115 | 116 | ); 117 | } 118 | 119 | export { MotionEffect, type MotionEffectProps }; 120 | -------------------------------------------------------------------------------- /apps/docs/components/docs/tailwind-type-table.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Info as InfoIcon } from 'lucide-react'; 4 | import { cva } from 'class-variance-authority'; 5 | import { 6 | Popover, 7 | PopoverContent, 8 | PopoverTrigger, 9 | } from '@/components/animate-ui/radix/popover'; 10 | import { useState, type ReactNode } from 'react'; 11 | import { cn } from '@/lib/utils'; 12 | import { Button } from '../ui/button'; 13 | 14 | export function Info({ children }: { children: ReactNode }): ReactNode { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | 27 | interface ObjectType { 28 | class: ReactNode; 29 | styles: ReactNode; 30 | } 31 | 32 | const field = cva('inline-flex flex-row items-center gap-1'); 33 | const code = cva( 34 | 'rounded-md bg-fd-secondary p-1 text-fd-secondary-foreground', 35 | { 36 | variants: { 37 | color: { 38 | primary: 'bg-fd-primary/10 text-fd-primary', 39 | }, 40 | }, 41 | }, 42 | ); 43 | 44 | export function TypeTable({ 45 | type, 46 | className, 47 | }: { 48 | type: Record; 49 | className?: string; 50 | }) { 51 | return ( 52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {Object.entries(type).map(([key, value]) => { 62 | return ( 63 | 64 | 77 | 84 | 85 | ); 86 | })} 87 | 88 |
ClassStyles
65 |
66 | 73 | {key} 74 | 75 |
76 |
78 |
79 | 80 | {value.styles} 81 | 82 |
83 |
89 |
90 | ); 91 | } 92 | 93 | export function TailwindTypeTable({ 94 | type, 95 | }: { 96 | type: Record; 97 | }) { 98 | const [isOpened, setIsOpened] = useState(false); 99 | 100 | if (Object.keys(type).length > 10) { 101 | return ( 102 |
103 |
108 | 109 |
110 | 111 |
119 | 126 |
127 |
128 | ); 129 | } 130 | return ; 131 | } 132 | -------------------------------------------------------------------------------- /apps/docs/components/animate-ui/radix/switch.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as SwitchPrimitives from '@radix-ui/react-switch'; 5 | import { motion, type HTMLMotionProps } from 'motion/react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | type SwitchProps = React.ComponentProps & 10 | HTMLMotionProps<'button'> & { 11 | leftIcon?: React.ReactNode; 12 | rightIcon?: React.ReactNode; 13 | thumbIcon?: React.ReactNode; 14 | }; 15 | 16 | function Switch({ 17 | className, 18 | leftIcon, 19 | rightIcon, 20 | thumbIcon, 21 | onCheckedChange, 22 | ...props 23 | }: SwitchProps) { 24 | const [isChecked, setIsChecked] = React.useState( 25 | props?.checked ?? props?.defaultChecked ?? false, 26 | ); 27 | const [isTapped, setIsTapped] = React.useState(false); 28 | 29 | React.useEffect(() => { 30 | if (props?.checked !== undefined) setIsChecked(props.checked); 31 | }, [props?.checked]); 32 | 33 | const handleCheckedChange = React.useCallback( 34 | (checked: boolean) => { 35 | setIsChecked(checked); 36 | onCheckedChange?.(checked); 37 | }, 38 | [onCheckedChange], 39 | ); 40 | 41 | return ( 42 | 47 | setIsTapped(true)} 56 | onTapCancel={() => setIsTapped(false)} 57 | onTap={() => setIsTapped(false)} 58 | {...props} 59 | > 60 | {leftIcon && ( 61 | 69 | {typeof leftIcon !== 'string' ? leftIcon : null} 70 | 71 | )} 72 | 73 | {rightIcon && ( 74 | 82 | {typeof rightIcon !== 'string' ? rightIcon : null} 83 | 84 | )} 85 | 86 | 87 | 105 | {thumbIcon && typeof thumbIcon !== 'string' ? thumbIcon : null} 106 | 107 | 108 | 109 | 110 | ); 111 | } 112 | 113 | export { Switch, type SwitchProps }; 114 | -------------------------------------------------------------------------------- /apps/docs/components/animate-ui/radix/popover.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as PopoverPrimitive from '@radix-ui/react-popover'; 5 | import { 6 | AnimatePresence, 7 | motion, 8 | type HTMLMotionProps, 9 | type Transition, 10 | } from 'motion/react'; 11 | 12 | import { cn } from '@/lib/utils'; 13 | 14 | type PopoverContextType = { 15 | isOpen: boolean; 16 | }; 17 | 18 | const PopoverContext = React.createContext( 19 | undefined, 20 | ); 21 | 22 | const usePopover = (): PopoverContextType => { 23 | const context = React.useContext(PopoverContext); 24 | if (!context) { 25 | throw new Error('usePopover must be used within a Popover'); 26 | } 27 | return context; 28 | }; 29 | 30 | type Side = 'top' | 'bottom' | 'left' | 'right'; 31 | 32 | const getInitialPosition = (side: Side) => { 33 | switch (side) { 34 | case 'top': 35 | return { y: 15 }; 36 | case 'bottom': 37 | return { y: -15 }; 38 | case 'left': 39 | return { x: 15 }; 40 | case 'right': 41 | return { x: -15 }; 42 | } 43 | }; 44 | 45 | type PopoverProps = React.ComponentProps; 46 | 47 | function Popover({ children, ...props }: PopoverProps) { 48 | const [isOpen, setIsOpen] = React.useState( 49 | props?.open ?? props?.defaultOpen ?? false, 50 | ); 51 | 52 | React.useEffect(() => { 53 | if (props?.open !== undefined) setIsOpen(props.open); 54 | }, [props?.open]); 55 | 56 | const handleOpenChange = React.useCallback( 57 | (open: boolean) => { 58 | setIsOpen(open); 59 | props.onOpenChange?.(open); 60 | }, 61 | [props], 62 | ); 63 | 64 | return ( 65 | 66 | 71 | {children} 72 | 73 | 74 | ); 75 | } 76 | 77 | type PopoverTriggerProps = React.ComponentProps< 78 | typeof PopoverPrimitive.Trigger 79 | >; 80 | 81 | function PopoverTrigger(props: PopoverTriggerProps) { 82 | return ; 83 | } 84 | 85 | type PopoverContentProps = React.ComponentProps< 86 | typeof PopoverPrimitive.Content 87 | > & 88 | HTMLMotionProps<'div'> & { 89 | transition?: Transition; 90 | }; 91 | 92 | function PopoverContent({ 93 | className, 94 | align = 'center', 95 | side = 'bottom', 96 | sideOffset = 4, 97 | transition = { type: 'spring', stiffness: 300, damping: 25 }, 98 | children, 99 | ...props 100 | }: PopoverContentProps) { 101 | const { isOpen } = usePopover(); 102 | const initialPosition = getInitialPosition(side); 103 | 104 | return ( 105 | 106 | {isOpen && ( 107 | 108 | 115 | 128 | {children} 129 | 130 | 131 | 132 | )} 133 | 134 | ); 135 | } 136 | 137 | type PopoverAnchorProps = React.ComponentProps; 138 | 139 | function PopoverAnchor({ ...props }: PopoverAnchorProps) { 140 | return ; 141 | } 142 | 143 | export { 144 | Popover, 145 | PopoverTrigger, 146 | PopoverContent, 147 | PopoverAnchor, 148 | usePopover, 149 | type PopoverContextType, 150 | type PopoverProps, 151 | type PopoverTriggerProps, 152 | type PopoverContentProps, 153 | type PopoverAnchorProps, 154 | }; 155 | -------------------------------------------------------------------------------- /apps/docs/.source/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck -- skip type checking 2 | import * as docs_16 from "../content/docs/tailwindcss/mask-image.mdx?collection=docs&hash=1745867615408" 3 | import * as docs_15 from "../content/docs/tailwindcss/border-width.mdx?collection=docs&hash=1745867615408" 4 | import * as docs_14 from "../content/docs/tailwindcss/border-smoothing.mdx?collection=docs&hash=1745867615408" 5 | import * as docs_13 from "../content/docs/tailwindcss/border-radius.mdx?collection=docs&hash=1745867615408" 6 | import * as docs_12 from "../content/docs/tailwindcss/border-color.mdx?collection=docs&hash=1745867615408" 7 | import * as docs_11 from "../content/docs/tailwindcss/background.mdx?collection=docs&hash=1745867615408" 8 | import * as docs_10 from "../content/docs/tailwindcss/background-color.mdx?collection=docs&hash=1745867615408" 9 | import * as docs_9 from "../content/docs/css/mask-image.mdx?collection=docs&hash=1745867615408" 10 | import * as docs_8 from "../content/docs/css/border-width.mdx?collection=docs&hash=1745867615408" 11 | import * as docs_7 from "../content/docs/css/border-smoothing.mdx?collection=docs&hash=1745867615408" 12 | import * as docs_6 from "../content/docs/css/border-radius.mdx?collection=docs&hash=1745867615408" 13 | import * as docs_5 from "../content/docs/css/border-color.mdx?collection=docs&hash=1745867615408" 14 | import * as docs_4 from "../content/docs/css/background.mdx?collection=docs&hash=1745867615408" 15 | import * as docs_3 from "../content/docs/css/background-color.mdx?collection=docs&hash=1745867615408" 16 | import * as docs_2 from "../content/docs/limitations.mdx?collection=docs&hash=1745867615408" 17 | import * as docs_1 from "../content/docs/index.mdx?collection=docs&hash=1745867615408" 18 | import * as docs_0 from "../content/docs/getting-started.mdx?collection=docs&hash=1745867615408" 19 | import { _runtime } from "fumadocs-mdx" 20 | import * as _source from "../source.config" 21 | export const docs = _runtime.docs([{ info: {"path":"getting-started.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/getting-started.mdx"}, data: docs_0 }, { info: {"path":"index.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/index.mdx"}, data: docs_1 }, { info: {"path":"limitations.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/limitations.mdx"}, data: docs_2 }, { info: {"path":"css/background-color.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/css/background-color.mdx"}, data: docs_3 }, { info: {"path":"css/background.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/css/background.mdx"}, data: docs_4 }, { info: {"path":"css/border-color.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/css/border-color.mdx"}, data: docs_5 }, { info: {"path":"css/border-radius.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/css/border-radius.mdx"}, data: docs_6 }, { info: {"path":"css/border-smoothing.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/css/border-smoothing.mdx"}, data: docs_7 }, { info: {"path":"css/border-width.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/css/border-width.mdx"}, data: docs_8 }, { info: {"path":"css/mask-image.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/css/mask-image.mdx"}, data: docs_9 }, { info: {"path":"tailwindcss/background-color.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/tailwindcss/background-color.mdx"}, data: docs_10 }, { info: {"path":"tailwindcss/background.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/tailwindcss/background.mdx"}, data: docs_11 }, { info: {"path":"tailwindcss/border-color.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/tailwindcss/border-color.mdx"}, data: docs_12 }, { info: {"path":"tailwindcss/border-radius.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/tailwindcss/border-radius.mdx"}, data: docs_13 }, { info: {"path":"tailwindcss/border-smoothing.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/tailwindcss/border-smoothing.mdx"}, data: docs_14 }, { info: {"path":"tailwindcss/border-width.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/tailwindcss/border-width.mdx"}, data: docs_15 }, { info: {"path":"tailwindcss/mask-image.mdx","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/tailwindcss/mask-image.mdx"}, data: docs_16 }], [{"info":{"path":"meta.json","absolutePath":"/Users/elliotsutton/Documents/Skyleen/tailwindcss-squircle/apps/docs/content/docs/meta.json"},"data":{"title":"Squircle","pages":["index","getting-started","limitations","---CSS---","css/background","css/background-color","css/border-color","css/border-radius","css/border-smoothing","css/border-width","css/mask-image","---Tailwind CSS---","tailwindcss/background","tailwindcss/background-color","tailwindcss/border-color","tailwindcss/border-radius","tailwindcss/border-smoothing","tailwindcss/border-width","tailwindcss/mask-image"],"root":true}}]) -------------------------------------------------------------------------------- /apps/docs/app/globals.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/black.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | @import 'tw-animate-css'; 5 | @import '@squircle/tailwindcss'; 6 | 7 | @custom-variant dark (&:is(.dark *)); 8 | 9 | @theme inline { 10 | --color-background: var(--background); 11 | --color-foreground: var(--foreground); 12 | --font-sans: var(--font-geist-sans); 13 | --font-mono: var(--font-geist-mono); 14 | --color-sidebar-ring: var(--sidebar-ring); 15 | --color-sidebar-border: var(--sidebar-border); 16 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 17 | --color-sidebar-accent: var(--sidebar-accent); 18 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 19 | --color-sidebar-primary: var(--sidebar-primary); 20 | --color-sidebar-foreground: var(--sidebar-foreground); 21 | --color-sidebar: var(--sidebar); 22 | --color-chart-5: var(--chart-5); 23 | --color-chart-4: var(--chart-4); 24 | --color-chart-3: var(--chart-3); 25 | --color-chart-2: var(--chart-2); 26 | --color-chart-1: var(--chart-1); 27 | --color-ring: var(--ring); 28 | --color-input: var(--input); 29 | --color-border: var(--border); 30 | --color-destructive: var(--destructive); 31 | --color-accent-foreground: var(--accent-foreground); 32 | --color-accent: var(--accent); 33 | --color-muted-foreground: var(--muted-foreground); 34 | --color-muted: var(--muted); 35 | --color-secondary-foreground: var(--secondary-foreground); 36 | --color-secondary: var(--secondary); 37 | --color-primary-foreground: var(--primary-foreground); 38 | --color-primary: var(--primary); 39 | --color-popover-foreground: var(--popover-foreground); 40 | --color-popover: var(--popover); 41 | --color-card-foreground: var(--card-foreground); 42 | --color-card: var(--card); 43 | --radius-sm: calc(var(--radius) - 4px); 44 | --radius-md: calc(var(--radius) - 2px); 45 | --radius-lg: var(--radius); 46 | --radius-xl: calc(var(--radius) + 4px); 47 | } 48 | 49 | :root { 50 | --radius: 0.625rem; 51 | --background: oklch(1 0 0); 52 | --foreground: oklch(0.145 0 0); 53 | --card: oklch(1 0 0); 54 | --card-foreground: oklch(0.145 0 0); 55 | --popover: oklch(1 0 0); 56 | --popover-foreground: oklch(0.145 0 0); 57 | --primary: oklch(0.205 0 0); 58 | --primary-foreground: oklch(0.985 0 0); 59 | --secondary: oklch(0.97 0 0); 60 | --secondary-foreground: oklch(0.205 0 0); 61 | --muted: oklch(0.97 0 0); 62 | --muted-foreground: oklch(0.556 0 0); 63 | --accent: oklch(0.97 0 0); 64 | --accent-foreground: oklch(0.205 0 0); 65 | --destructive: oklch(0.577 0.245 27.325); 66 | --border: oklch(0.922 0 0); 67 | --input: oklch(0.922 0 0); 68 | --ring: oklch(0.708 0 0); 69 | --chart-1: oklch(0.646 0.222 41.116); 70 | --chart-2: oklch(0.6 0.118 184.704); 71 | --chart-3: oklch(0.398 0.07 227.392); 72 | --chart-4: oklch(0.828 0.189 84.429); 73 | --chart-5: oklch(0.769 0.188 70.08); 74 | --sidebar: oklch(0.985 0 0); 75 | --sidebar-foreground: oklch(0.145 0 0); 76 | --sidebar-primary: oklch(0.205 0 0); 77 | --sidebar-primary-foreground: oklch(0.985 0 0); 78 | --sidebar-accent: oklch(0.97 0 0); 79 | --sidebar-accent-foreground: oklch(0.205 0 0); 80 | --sidebar-border: oklch(0.922 0 0); 81 | --sidebar-ring: oklch(0.708 0 0); 82 | --color-fd-primary: oklch(70.4% 0.14 182.503); 83 | } 84 | 85 | .dark { 86 | --background: oklch(0.145 0 0); 87 | --foreground: oklch(0.985 0 0); 88 | --card: oklch(0.205 0 0); 89 | --card-foreground: oklch(0.985 0 0); 90 | --popover: oklch(0.205 0 0); 91 | --popover-foreground: oklch(0.985 0 0); 92 | --primary: oklch(0.922 0 0); 93 | --primary-foreground: oklch(0.205 0 0); 94 | --secondary: oklch(0.269 0 0); 95 | --secondary-foreground: oklch(0.985 0 0); 96 | --muted: oklch(0.269 0 0); 97 | --muted-foreground: oklch(0.708 0 0); 98 | --accent: oklch(0.269 0 0); 99 | --accent-foreground: oklch(0.985 0 0); 100 | --destructive: oklch(0.704 0.191 22.216); 101 | --border: oklch(1 0 0 / 10%); 102 | --input: oklch(1 0 0 / 15%); 103 | --ring: oklch(0.556 0 0); 104 | --chart-1: oklch(0.488 0.243 264.376); 105 | --chart-2: oklch(0.696 0.17 162.48); 106 | --chart-3: oklch(0.769 0.188 70.08); 107 | --chart-4: oklch(0.627 0.265 303.9); 108 | --chart-5: oklch(0.645 0.246 16.439); 109 | --sidebar: oklch(0.205 0 0); 110 | --sidebar-foreground: oklch(0.985 0 0); 111 | --sidebar-primary: oklch(0.488 0.243 264.376); 112 | --sidebar-primary-foreground: oklch(0.985 0 0); 113 | --sidebar-accent: oklch(0.269 0 0); 114 | --sidebar-accent-foreground: oklch(0.985 0 0); 115 | --sidebar-border: oklch(1 0 0 / 10%); 116 | --sidebar-ring: oklch(0.556 0 0); 117 | --color-fd-primary: oklch(70.4% 0.14 182.503); 118 | } 119 | 120 | @layer base { 121 | * { 122 | @apply border-border outline-ring/50; 123 | } 124 | body { 125 | @apply bg-background text-foreground; 126 | } 127 | .bg-pattern { 128 | @apply [--pattern-fg:var(--color-border)] bg-[image:repeating-linear-gradient(315deg,_var(--pattern-fg)_0,_var(--pattern-fg)_1px,_transparent_0,_transparent_50%)] bg-[size:10px_10px] bg-fixed; 129 | } 130 | .bg-dots { 131 | @apply relative overflow-hidden bg-[image:radial-gradient(var(--pattern-fg)_1px,_transparent_0)] bg-[size:10px_10px] bg-fixed [--pattern-fg:var(--color-neutral-950)]/10 dark:[--pattern-fg:var(--color-white)]/10; 132 | } 133 | a[href^='/docs/installation'] > svg[data-icon='true'] { 134 | @apply !hidden; 135 | } 136 | #nd-docs-layout { 137 | @apply relative; 138 | } 139 | .bprogress { 140 | @apply relative; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /apps/docs/components/logo/logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Logo = (props: React.SVGProps) => { 4 | return ( 5 |
6 | 7 | 11 | 12 | 13 |
14 |

BETA

15 |
16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /apps/docs/components/animate-ui/radix/tabs.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as TabsPrimitive from '@radix-ui/react-tabs'; 5 | import { type HTMLMotionProps, type Transition, motion } from 'motion/react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | import { 9 | MotionHighlight, 10 | MotionHighlightItem, 11 | } from '@/components/animate-ui/effects/motion-highlight'; 12 | 13 | type TabsProps = React.ComponentProps; 14 | 15 | function Tabs({ className, ...props }: TabsProps) { 16 | return ( 17 | 22 | ); 23 | } 24 | 25 | type TabsListProps = React.ComponentProps & { 26 | activeClassName?: string; 27 | transition?: Transition; 28 | }; 29 | 30 | function TabsList({ 31 | ref, 32 | children, 33 | className, 34 | activeClassName, 35 | transition = { 36 | type: 'spring', 37 | stiffness: 200, 38 | damping: 25, 39 | }, 40 | ...props 41 | }: TabsListProps) { 42 | const localRef = React.useRef(null); 43 | React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement); 44 | 45 | const [activeValue, setActiveValue] = React.useState( 46 | undefined, 47 | ); 48 | 49 | const getActiveValue = React.useCallback(() => { 50 | if (!localRef.current) return; 51 | const activeTab = localRef.current.querySelector( 52 | '[data-state="active"]', 53 | ); 54 | if (!activeTab) return; 55 | setActiveValue(activeTab.getAttribute('data-value') ?? undefined); 56 | }, []); 57 | 58 | React.useEffect(() => { 59 | getActiveValue(); 60 | 61 | const observer = new MutationObserver(getActiveValue); 62 | 63 | if (localRef.current) { 64 | observer.observe(localRef.current, { 65 | attributes: true, 66 | childList: true, 67 | subtree: true, 68 | }); 69 | } 70 | 71 | return () => { 72 | observer.disconnect(); 73 | }; 74 | }, [getActiveValue]); 75 | 76 | return ( 77 | 83 | 92 | {children} 93 | 94 | 95 | ); 96 | } 97 | 98 | type TabsTriggerProps = React.ComponentProps; 99 | 100 | function TabsTrigger({ className, value, ...props }: TabsTriggerProps) { 101 | return ( 102 | 103 | 112 | 113 | ); 114 | } 115 | 116 | type TabsContentProps = React.ComponentProps & 117 | HTMLMotionProps<'div'> & { 118 | transition?: Transition; 119 | }; 120 | 121 | function TabsContent({ 122 | className, 123 | children, 124 | transition = { 125 | duration: 0.5, 126 | ease: 'easeInOut', 127 | }, 128 | ...props 129 | }: TabsContentProps) { 130 | return ( 131 | 132 | 142 | {children} 143 | 144 | 145 | ); 146 | } 147 | 148 | type TabsContentsProps = HTMLMotionProps<'div'> & { 149 | children: React.ReactNode; 150 | className?: string; 151 | transition?: Transition; 152 | }; 153 | 154 | function TabsContents({ 155 | children, 156 | className, 157 | transition = { type: 'spring', stiffness: 200, damping: 25 }, 158 | ...props 159 | }: TabsContentsProps) { 160 | const containerRef = React.useRef(null); 161 | 162 | const [height, setHeight] = React.useState(0); 163 | 164 | React.useEffect(() => { 165 | if (!containerRef.current) return; 166 | 167 | const resizeObserver = new ResizeObserver((entries) => { 168 | const newHeight = entries[0].contentRect.height; 169 | requestAnimationFrame(() => { 170 | setHeight(newHeight); 171 | }); 172 | }); 173 | 174 | resizeObserver.observe(containerRef.current); 175 | 176 | return () => { 177 | resizeObserver.disconnect(); 178 | }; 179 | }, [children]); 180 | 181 | React.useLayoutEffect(() => { 182 | if (containerRef.current) { 183 | const initialHeight = containerRef.current.getBoundingClientRect().height; 184 | setHeight(initialHeight); 185 | } 186 | }, [children]); 187 | 188 | return ( 189 | 197 |
{children}
198 |
199 | ); 200 | } 201 | 202 | export { 203 | Tabs, 204 | TabsList, 205 | TabsTrigger, 206 | TabsContent, 207 | TabsContents, 208 | type TabsProps, 209 | type TabsListProps, 210 | type TabsTriggerProps, 211 | type TabsContentProps, 212 | type TabsContentsProps, 213 | }; 214 | -------------------------------------------------------------------------------- /packages/core/src/squircle-generator.ts: -------------------------------------------------------------------------------- 1 | interface PaintGlobalScope { 2 | registerPaint(name: string, ctor: unknown): void; 3 | } 4 | 5 | interface Geometry { 6 | readonly width: number; 7 | readonly height: number; 8 | } 9 | 10 | interface StyleMap { 11 | get(prop: string): CSSStyleValue; 12 | } 13 | 14 | interface Painter { 15 | paint(ctx: CanvasRenderingContext2D, geom: Geometry, props: StyleMap): void; 16 | } 17 | 18 | declare const registerPaint: PaintGlobalScope['registerPaint']; 19 | 20 | /** 21 | * Draws a squircle shape on the given canvas context, 22 | * filling with fillColor and stroking with strokeColor. 23 | */ 24 | function renderSquircle( 25 | ctx: CanvasRenderingContext2D, 26 | geom: Geometry, 27 | cornerRadii: [number, number, number, number], 28 | cornerSmooth: [number, number, number, number], 29 | borderWidth: number, 30 | fillColor: string, 31 | strokeColor: string, 32 | isMask: boolean, 33 | ): void { 34 | const halfBorder = borderWidth / 2; 35 | const [tl, tr, br, bl] = cornerRadii; 36 | const [tlSmooth, trSmooth, brSmooth, blSmooth] = cornerSmooth; 37 | ctx.beginPath(); 38 | // Top right corner 39 | ctx.moveTo(tl, halfBorder); 40 | ctx.lineTo(geom.width - tr, halfBorder); 41 | ctx.bezierCurveTo( 42 | geom.width - tr / trSmooth, 43 | halfBorder, 44 | geom.width - halfBorder, 45 | tr / trSmooth, 46 | geom.width - halfBorder, 47 | tr, 48 | ); 49 | // Bottom right corner 50 | ctx.lineTo(geom.width - halfBorder, geom.height - br); 51 | ctx.bezierCurveTo( 52 | geom.width - halfBorder, 53 | geom.height - br / brSmooth, 54 | geom.width - br / brSmooth, 55 | geom.height - halfBorder, 56 | geom.width - br, 57 | geom.height - halfBorder, 58 | ); 59 | // Bottom left corner 60 | ctx.lineTo(bl, geom.height - halfBorder); 61 | ctx.bezierCurveTo( 62 | bl / blSmooth, 63 | geom.height - halfBorder, 64 | halfBorder, 65 | geom.height - bl / blSmooth, 66 | halfBorder, 67 | geom.height - bl, 68 | ); 69 | // Top left corner 70 | ctx.lineTo(halfBorder, tl); 71 | ctx.bezierCurveTo( 72 | halfBorder, 73 | tl / tlSmooth, 74 | tl / tlSmooth, 75 | halfBorder, 76 | tl, 77 | halfBorder, 78 | ); 79 | ctx.closePath(); 80 | 81 | if (isMask && borderWidth > 0) { 82 | ctx.strokeStyle = strokeColor; 83 | ctx.lineWidth = borderWidth; 84 | ctx.stroke(); 85 | } else if (isMask) { 86 | ctx.fillStyle = fillColor; 87 | ctx.fill(); 88 | } else { 89 | ctx.fillStyle = fillColor; 90 | ctx.fill(); 91 | if (borderWidth > 0) { 92 | ctx.strokeStyle = strokeColor; 93 | ctx.lineWidth = borderWidth; 94 | ctx.stroke(); 95 | } 96 | } 97 | } 98 | 99 | const BORDER_KEYS = [ 100 | 'top-left', 101 | 'top-right', 102 | 'bottom-right', 103 | 'bottom-left', 104 | ] as const; 105 | 106 | class SquirclePainter implements Painter { 107 | static get contextOptions() { 108 | return { alpha: true }; 109 | } 110 | 111 | static get inputProperties(): string[] { 112 | // only custom properties can be read by the paint worklet 113 | return [ 114 | '--squircle-border-radius', 115 | '--squircle-border-top-left-radius', 116 | '--squircle-border-top-right-radius', 117 | '--squircle-border-bottom-right-radius', 118 | '--squircle-border-bottom-left-radius', 119 | '--squircle-border-smoothing', 120 | '--squircle-border-top-left-smoothing', 121 | '--squircle-border-top-right-smoothing', 122 | '--squircle-border-bottom-right-smoothing', 123 | '--squircle-border-bottom-left-smoothing', 124 | '--squircle-border-width', 125 | '--squircle-border-color', 126 | '--squircle-background-color', 127 | '--squircle-mode', 128 | ]; 129 | } 130 | 131 | paint(ctx: CanvasRenderingContext2D, geom: Geometry, props: StyleMap): void { 132 | const SMOOTH_BASE = 10; 133 | const DIST_RATIO = 1.8; 134 | 135 | // generic smoothing fallback 136 | const rawGlobalSmooth = props 137 | .get('--squircle-border-smoothing')! 138 | .toString(); 139 | const defaultSmooth = 140 | parseFloat(rawGlobalSmooth) * SMOOTH_BASE || SMOOTH_BASE; 141 | 142 | // per-corner smoothing 143 | let cornerSmooth = BORDER_KEYS.map( 144 | (k) => 145 | parseFloat(props.get(`--squircle-border-${k}-smoothing`)!.toString()) * 146 | SMOOTH_BASE, 147 | ) as [number, number, number, number]; 148 | 149 | // fallback sur le global si NaN 150 | cornerSmooth = cornerSmooth.map((s) => 151 | Number.isNaN(s) ? defaultSmooth : s, 152 | ) as [number, number, number, number]; 153 | 154 | // read per-corner radii 155 | let cornerRadii = BORDER_KEYS.map( 156 | (k) => 157 | parseFloat(props.get(`--squircle-border-${k}-radius`)!.toString()) * 158 | DIST_RATIO, 159 | ) as [number, number, number, number]; 160 | 161 | // fallback shorthand expansion if any is NaN 162 | if (cornerRadii.some((r) => Number.isNaN(r))) { 163 | const raw = props.get('--squircle-border-radius')!.toString(); 164 | const toks = (raw.match(/\d+(\.\d+)?/g) || []).map( 165 | (n) => parseFloat(n) * DIST_RATIO, 166 | ); 167 | 168 | let tl = toks[0] ?? 0; 169 | let tr = toks[1] ?? tl; 170 | let br = toks[2] ?? tl; 171 | let bl = toks[3] ?? tr; 172 | if (toks.length === 1) tr = br = bl = tl; 173 | else if (toks.length === 2) (br = tl), (bl = tr); 174 | else if (toks.length === 3) bl = tr; 175 | 176 | cornerRadii = cornerRadii.map((r, i) => 177 | Number.isNaN(r) ? [tl, tr, br, bl][i] : r, 178 | ) as [number, number, number, number]; 179 | } 180 | 181 | // read border width & colours 182 | const borderWidth = 183 | parseFloat(props.get('--squircle-border-width')!.toString()) || 0; 184 | const fillColor = props.get('--squircle-background-color')!.toString(); 185 | const strokeColor = props.get('--squircle-border-color')!.toString(); 186 | 187 | // cap radii 188 | const maxR = Math.min(geom.width, geom.height) / 2; 189 | cornerRadii = cornerRadii.map((r) => Math.min(r, maxR)) as [ 190 | number, 191 | number, 192 | number, 193 | number, 194 | ]; 195 | 196 | const isMask = props.get('--squircle-mode')?.toString() === 'mask-image'; 197 | 198 | renderSquircle( 199 | ctx, 200 | geom, 201 | cornerRadii, 202 | cornerSmooth, 203 | borderWidth, 204 | fillColor, 205 | strokeColor, 206 | isMask, 207 | ); 208 | } 209 | } 210 | 211 | if (typeof registerPaint === 'function') { 212 | registerPaint('squircle', SquirclePainter); 213 | } 214 | -------------------------------------------------------------------------------- /apps/docs/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { CodeEditor } from '@/components/code-editor'; 2 | import { CodeEditorRender } from '@/components/code-editor/render'; 3 | import { Footer } from '@/components/footer'; 4 | import { Header } from '@/components/header'; 5 | import CSSLogo from '@/components/logo/css-logo'; 6 | import TailwindCSSLogo from '@/components/logo/tailwindcss-logo'; 7 | import { Playground } from '@/components/playground'; 8 | import { Plus } from '@/components/plus'; 9 | import { Section } from '@/components/section'; 10 | import { Globe, Paintbrush } from 'lucide-react'; 11 | 12 | export default function Home() { 13 | return ( 14 |
15 |
16 | 17 |
18 |
19 |
20 |

21 | 22 | --squircle-border-radius: 40px; 23 | {' '} 24 | 25 | --squircle-background-color:{' '} 26 | 27 | #2dd4bf 28 | 29 | 30 | 31 | ; 32 |

33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 |

42 | Make Squircles simply on the web 43 |

44 | 45 |

46 | Finally, a simple, modular way to create beautiful squircles in 47 | CSS, offering full customization and seamless integration as 48 | well as Tailwind CSS integration. 49 |

50 | 51 |

52 | Inspired by{' '} 53 | 59 | Pavel Laptev 60 | 61 | 's work 62 |

63 |
64 | 65 | 66 | 67 | 68 | 69 |
70 |
71 |
72 |
73 | 74 |
75 | 76 |
77 |
78 |
79 | 80 |
81 |
82 | 83 |
84 |
85 |
86 | 87 |
88 | 89 |
90 |
91 |
92 |
93 | 94 |
95 |

Handling with CSS

96 |

97 | Modify your squircles directly in CSS via various combinable 98 | variables to give a result close to what a native squircle might 99 | be. 100 |

101 |
102 |
103 |
104 | 105 |
106 |

Tailwind CSS Plugin

107 |

108 | Easily customize your squircles with our comprehensive tailwind 109 | plugin, which lets you play around with css variables. 110 |

111 |
112 |
113 |
114 | 115 |
116 |

Highly customizable

117 |

118 | Choose between mask or background mode and customize radius, 119 | smoothing, background color and border size and color. 120 |

121 |
122 |
123 |
124 | 125 |
126 |

High compatibility

127 |

128 | We use CSS Houdini combined with an improved version of 129 | css-paint-polyfill to ensure high compatibility with all modern 130 | browsers. 131 |

132 |
133 |
134 |
135 | 136 |
137 | 138 |
139 | 140 |
141 |
142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /apps/docs/components/animate-ui/text/sliding-number.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { 5 | useSpring, 6 | useTransform, 7 | motion, 8 | useInView, 9 | type MotionValue, 10 | type SpringOptions, 11 | type UseInViewOptions, 12 | } from 'motion/react'; 13 | import useMeasure from 'react-use-measure'; 14 | 15 | import { cn } from '@/lib/utils'; 16 | 17 | type SlidingNumberRollerProps = { 18 | prevValue: number; 19 | value: number; 20 | place: number; 21 | transition: SpringOptions; 22 | }; 23 | 24 | function SlidingNumberRoller({ 25 | prevValue, 26 | value, 27 | place, 28 | transition, 29 | }: SlidingNumberRollerProps) { 30 | const startNumber = Math.floor(prevValue / place) % 10; 31 | const targetNumber = Math.floor(value / place) % 10; 32 | const animatedValue = useSpring(startNumber, transition); 33 | 34 | React.useEffect(() => { 35 | animatedValue.set(targetNumber); 36 | }, [targetNumber, animatedValue]); 37 | 38 | const [measureRef, { height }] = useMeasure(); 39 | 40 | return ( 41 |
46 |
0
47 | {Array.from({ length: 10 }, (_, i) => ( 48 | 55 | ))} 56 |
57 | ); 58 | } 59 | 60 | type SlidingNumberDisplayProps = { 61 | motionValue: MotionValue; 62 | number: number; 63 | height: number; 64 | transition: SpringOptions; 65 | }; 66 | 67 | function SlidingNumberDisplay({ 68 | motionValue, 69 | number, 70 | height, 71 | transition, 72 | }: SlidingNumberDisplayProps) { 73 | const y = useTransform(motionValue, (latest) => { 74 | if (!height) return 0; 75 | const currentNumber = latest % 10; 76 | const offset = (10 + number - currentNumber) % 10; 77 | let translateY = offset * height; 78 | if (offset > 5) translateY -= 10 * height; 79 | return translateY; 80 | }); 81 | 82 | if (!height) { 83 | return {number}; 84 | } 85 | 86 | return ( 87 | 93 | {number} 94 | 95 | ); 96 | } 97 | 98 | type SlidingNumberProps = React.ComponentProps<'span'> & { 99 | number: number | string; 100 | inView?: boolean; 101 | inViewMargin?: UseInViewOptions['margin']; 102 | inViewOnce?: boolean; 103 | padStart?: boolean; 104 | decimalSeparator?: string; 105 | decimalPlaces?: number; 106 | transition?: SpringOptions; 107 | }; 108 | 109 | function SlidingNumber({ 110 | ref, 111 | number, 112 | className, 113 | inView = false, 114 | inViewMargin = '0px', 115 | inViewOnce = true, 116 | padStart = false, 117 | decimalSeparator = '.', 118 | decimalPlaces = 0, 119 | transition = { 120 | stiffness: 200, 121 | damping: 20, 122 | mass: 0.4, 123 | }, 124 | ...props 125 | }: SlidingNumberProps) { 126 | const localRef = React.useRef(null); 127 | React.useImperativeHandle(ref, () => localRef.current!); 128 | 129 | const inViewResult = useInView(localRef, { 130 | once: inViewOnce, 131 | margin: inViewMargin, 132 | }); 133 | const isInView = !inView || inViewResult; 134 | 135 | const prevNumberRef = React.useRef(0); 136 | 137 | const effectiveNumber = React.useMemo( 138 | () => (!isInView ? 0 : Math.abs(Number(number))), 139 | [number, isInView], 140 | ); 141 | 142 | const formatNumber = React.useCallback( 143 | (num: number) => 144 | decimalPlaces != null ? num.toFixed(decimalPlaces) : num.toString(), 145 | [decimalPlaces], 146 | ); 147 | 148 | const numberStr = formatNumber(effectiveNumber); 149 | const [newIntStrRaw, newDecStrRaw = ''] = numberStr.split('.'); 150 | const newIntStr = 151 | padStart && newIntStrRaw.length === 1 ? '0' + newIntStrRaw : newIntStrRaw; 152 | 153 | const prevFormatted = formatNumber(prevNumberRef.current); 154 | const [prevIntStrRaw = '', prevDecStrRaw = ''] = prevFormatted.split('.'); 155 | const prevIntStr = 156 | padStart && prevIntStrRaw.length === 1 157 | ? '0' + prevIntStrRaw 158 | : prevIntStrRaw; 159 | 160 | const adjustedPrevInt = React.useMemo(() => { 161 | return prevIntStr.length > newIntStr.length 162 | ? prevIntStr.slice(-newIntStr.length) 163 | : prevIntStr.padStart(newIntStr.length, '0'); 164 | }, [prevIntStr, newIntStr]); 165 | 166 | const adjustedPrevDec = React.useMemo(() => { 167 | if (!newDecStrRaw) return ''; 168 | return prevDecStrRaw.length > newDecStrRaw.length 169 | ? prevDecStrRaw.slice(0, newDecStrRaw.length) 170 | : prevDecStrRaw.padEnd(newDecStrRaw.length, '0'); 171 | }, [prevDecStrRaw, newDecStrRaw]); 172 | 173 | React.useEffect(() => { 174 | if (isInView) prevNumberRef.current = effectiveNumber; 175 | }, [effectiveNumber, isInView]); 176 | 177 | const intDigitCount = newIntStr.length; 178 | const intPlaces = React.useMemo( 179 | () => 180 | Array.from({ length: intDigitCount }, (_, i) => 181 | Math.pow(10, intDigitCount - i - 1), 182 | ), 183 | [intDigitCount], 184 | ); 185 | const decPlaces = React.useMemo( 186 | () => 187 | newDecStrRaw 188 | ? Array.from({ length: newDecStrRaw.length }, (_, i) => 189 | Math.pow(10, newDecStrRaw.length - i - 1), 190 | ) 191 | : [], 192 | [newDecStrRaw], 193 | ); 194 | 195 | const newDecValue = newDecStrRaw ? parseInt(newDecStrRaw, 10) : 0; 196 | const prevDecValue = adjustedPrevDec ? parseInt(adjustedPrevDec, 10) : 0; 197 | 198 | return ( 199 | 205 | {isInView && Number(number) < 0 && -} 206 | 207 | {intPlaces.map((place) => ( 208 | 215 | ))} 216 | 217 | {newDecStrRaw && ( 218 | <> 219 | {decimalSeparator} 220 | {decPlaces.map((place) => ( 221 | 228 | ))} 229 | 230 | )} 231 | 232 | ); 233 | } 234 | 235 | export { SlidingNumber, type SlidingNumberProps }; 236 | -------------------------------------------------------------------------------- /apps/docs/components/code-editor/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { useTheme } from 'next-themes'; 5 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; 6 | import TailwindCSSLogo from '../logo/tailwindcss-logo'; 7 | import CSSLogo from '../logo/css-logo'; 8 | import { cn } from '@/lib/utils'; 9 | 10 | const Editor = ({ code }: { code: string }) => { 11 | const { resolvedTheme } = useTheme(); 12 | const [highlightedCode, setHighlightedCode] = React.useState(''); 13 | 14 | React.useEffect(() => { 15 | if (!code.length) return; 16 | 17 | const loadHighlightedCode = async () => { 18 | try { 19 | const { codeToHtml } = await import('shiki'); 20 | 21 | const highlighted = await codeToHtml(code, { 22 | lang: 'html', 23 | themes: { 24 | light: 'github-light', 25 | dark: 'github-dark', 26 | }, 27 | defaultColor: resolvedTheme === 'dark' ? 'dark' : 'light', 28 | }); 29 | 30 | setHighlightedCode(highlighted); 31 | } catch (e) { 32 | console.error(e); 33 | } 34 | }; 35 | 36 | loadHighlightedCode(); 37 | }, [resolvedTheme, code]); 38 | 39 | return ( 40 |
44 | ); 45 | }; 46 | 47 | const tailwindCode = `
48 | iPad Air 49 |
50 | {...} 51 |
52 | 55 |
59 | 62 |
63 |
64 | `; 65 | 66 | const cssCode = ` 166 | 167 |
168 | iPad Air 169 |
170 |
171 | 174 | 175 | 176 | 177 |
178 | 179 |
180 |
`; 181 | 182 | export const CodeEditor = ({ className }: { className?: string }) => { 183 | const [mode, setMode] = React.useState<'css' | 'tailwindcss'>('css'); 184 | 185 | return ( 186 | setMode(value as 'css' | 'tailwindcss')} 189 | > 190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | 199 |
200 | 201 | 202 |
206 | 207 | CSS 208 |
209 |
210 | 211 |
215 | 216 | Tailwind CSS 217 |
218 |
219 |
220 |
221 |
222 | 223 |
224 | 225 | 226 | 227 | 231 | 232 | 233 |
234 |
235 |
236 |
237 | ); 238 | }; 239 | -------------------------------------------------------------------------------- /apps/docs/components/animate-ui/components/tabs.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { motion, type Transition, type HTMLMotionProps } from 'motion/react'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | import { 8 | MotionHighlight, 9 | MotionHighlightItem, 10 | } from '@/components/animate-ui/effects/motion-highlight'; 11 | 12 | type TabsContextType = { 13 | activeValue: T; 14 | handleValueChange: (value: T) => void; 15 | registerTrigger: (value: T, node: HTMLElement | null) => void; 16 | }; 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 19 | const TabsContext = React.createContext | undefined>( 20 | undefined, 21 | ); 22 | 23 | function useTabs(): TabsContextType { 24 | const context = React.useContext(TabsContext); 25 | if (!context) { 26 | throw new Error('useTabs must be used within a TabsProvider'); 27 | } 28 | return context; 29 | } 30 | 31 | type BaseTabsProps = React.ComponentProps<'div'> & { 32 | children: React.ReactNode; 33 | }; 34 | 35 | type UnControlledTabsProps = BaseTabsProps & { 36 | defaultValue?: T; 37 | value?: never; 38 | onValueChange?: never; 39 | }; 40 | 41 | type ControlledTabsProps = BaseTabsProps & { 42 | value: T; 43 | onValueChange?: (value: T) => void; 44 | defaultValue?: never; 45 | }; 46 | 47 | type TabsProps = 48 | | UnControlledTabsProps 49 | | ControlledTabsProps; 50 | 51 | function Tabs({ 52 | defaultValue, 53 | value, 54 | onValueChange, 55 | children, 56 | className, 57 | ...props 58 | }: TabsProps) { 59 | const [activeValue, setActiveValue] = React.useState( 60 | defaultValue ?? null, 61 | ); 62 | const triggersRef = React.useRef(new Map()); 63 | const initialSet = React.useRef(false); 64 | const isControlled = value !== undefined; 65 | 66 | React.useEffect(() => { 67 | if ( 68 | !isControlled && 69 | activeValue === undefined && 70 | triggersRef.current.size > 0 && 71 | !initialSet.current 72 | ) { 73 | const firstTab = Array.from(triggersRef.current.keys())[0]; 74 | setActiveValue(firstTab as T); 75 | initialSet.current = true; 76 | } 77 | }, [activeValue, isControlled]); 78 | 79 | const registerTrigger = (value: string, node: HTMLElement | null) => { 80 | if (node) { 81 | triggersRef.current.set(value, node); 82 | if (!isControlled && activeValue === undefined && !initialSet.current) { 83 | setActiveValue(value as T); 84 | initialSet.current = true; 85 | } 86 | } else { 87 | triggersRef.current.delete(value); 88 | } 89 | }; 90 | 91 | const handleValueChange = (val: T) => { 92 | if (!isControlled) setActiveValue(val); 93 | else onValueChange?.(val); 94 | }; 95 | 96 | return ( 97 | 104 |
109 | {children} 110 |
111 |
112 | ); 113 | } 114 | 115 | type TabsListProps = React.ComponentProps<'div'> & { 116 | children: React.ReactNode; 117 | activeClassName?: string; 118 | transition?: Transition; 119 | }; 120 | 121 | function TabsList({ 122 | children, 123 | className, 124 | activeClassName, 125 | transition = { 126 | type: 'spring', 127 | stiffness: 200, 128 | damping: 25, 129 | }, 130 | ...props 131 | }: TabsListProps) { 132 | const { activeValue } = useTabs(); 133 | 134 | return ( 135 | 141 |
150 | {children} 151 |
152 |
153 | ); 154 | } 155 | 156 | type TabsTriggerProps = HTMLMotionProps<'button'> & { 157 | value: string; 158 | children: React.ReactNode; 159 | }; 160 | 161 | function TabsTrigger({ 162 | ref, 163 | value, 164 | children, 165 | className, 166 | ...props 167 | }: TabsTriggerProps) { 168 | const { activeValue, handleValueChange, registerTrigger } = useTabs(); 169 | 170 | const localRef = React.useRef(null); 171 | React.useImperativeHandle(ref, () => localRef.current as HTMLButtonElement); 172 | 173 | React.useEffect(() => { 174 | registerTrigger(value, localRef.current); 175 | return () => registerTrigger(value, null); 176 | }, [value, registerTrigger]); 177 | 178 | return ( 179 | 180 | handleValueChange(value)} 186 | data-state={activeValue === value ? 'active' : 'inactive'} 187 | className={cn( 188 | 'inline-flex cursor-pointer items-center size-full justify-center whitespace-nowrap rounded-sm px-2 py-1 text-sm font-medium ring-offset-background transition-transform focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground data-[state=active]:[&_svg]:text-teal-400 z-[1]', 189 | className, 190 | )} 191 | {...props} 192 | > 193 | {children} 194 | 195 | 196 | ); 197 | } 198 | 199 | type TabsContentsProps = React.ComponentProps<'div'> & { 200 | children: React.ReactNode; 201 | transition?: Transition; 202 | }; 203 | 204 | function TabsContents({ 205 | children, 206 | className, 207 | transition = { 208 | type: 'spring', 209 | stiffness: 300, 210 | damping: 30, 211 | bounce: 0, 212 | restDelta: 0.01, 213 | }, 214 | ...props 215 | }: TabsContentsProps) { 216 | const { activeValue } = useTabs(); 217 | const childrenArray = React.Children.toArray(children); 218 | const activeIndex = childrenArray.findIndex( 219 | (child): child is React.ReactElement<{ value: string }> => 220 | React.isValidElement(child) && 221 | typeof child.props === 'object' && 222 | child.props !== null && 223 | 'value' in child.props && 224 | child.props.value === activeValue, 225 | ); 226 | 227 | return ( 228 |
233 | 238 | {childrenArray.map((child, index) => ( 239 |
240 | {child} 241 |
242 | ))} 243 |
244 |
245 | ); 246 | } 247 | 248 | type TabsContentProps = HTMLMotionProps<'div'> & { 249 | value: string; 250 | children: React.ReactNode; 251 | }; 252 | 253 | function TabsContent({ 254 | children, 255 | value, 256 | className, 257 | ...props 258 | }: TabsContentProps) { 259 | const { activeValue } = useTabs(); 260 | const isActive = activeValue === value; 261 | return ( 262 | 272 | {children} 273 | 274 | ); 275 | } 276 | 277 | export { 278 | Tabs, 279 | TabsList, 280 | TabsTrigger, 281 | TabsContents, 282 | TabsContent, 283 | useTabs, 284 | type TabsContextType, 285 | type TabsProps, 286 | type TabsListProps, 287 | type TabsTriggerProps, 288 | type TabsContentsProps, 289 | type TabsContentProps, 290 | }; 291 | -------------------------------------------------------------------------------- /apps/docs/content/docs/tailwindcss/border-smoothing.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Border Smoothing 3 | description: Customize the border smoothing of a squircle. 4 | --- 5 | 6 | import { TailwindTypeTable } from '@/components/docs/tailwind-type-table'; 7 | 8 | ## Variables 9 | 10 | ': { 13 | class: 'squircle-smooth-', 14 | styles: `--squircle-border-smoothing: ;`, 15 | }, 16 | 'squircle-smooth-xs': { 17 | class: 'squircle-smooth-xs', 18 | styles: `--squircle-border-smoothing: 0.2;`, 19 | }, 20 | 'squircle-smooth-sm': { 21 | class: 'squircle-smooth-sm', 22 | styles: `--squircle-border-smoothing: 0.4;`, 23 | }, 24 | 'squircle-smooth-md': { 25 | class: 'squircle-smooth-md', 26 | styles: `--squircle-border-smoothing: 0.6;`, 27 | }, 28 | 'squircle-smooth-lg': { 29 | class: 'squircle-smooth-lg', 30 | styles: `--squircle-border-smoothing: 0.8;`, 31 | }, 32 | 'squircle-smooth-xl': { 33 | class: 'squircle-smooth-xl', 34 | styles: `--squircle-border-smoothing: 1;`, 35 | }, 36 | 'squircle-smooth-r-': { 37 | class: 'squircle-smooth-r-', 38 | styles: `--squircle-border-top-right-smoothing: ; 39 | --squircle-border-bottom-right-smoothing: ;`, 40 | }, 41 | 'squircle-smooth-r-xs': { 42 | class: 'squircle-smooth-r-xs', 43 | styles: `--squircle-border-top-right-smoothing: 0.2; 44 | --squircle-border-bottom-right-smoothing: 0.2;`, 45 | }, 46 | 'squircle-smooth-r-sm': { 47 | class: 'squircle-smooth-r-sm', 48 | styles: `--squircle-border-top-right-smoothing: 0.4; 49 | --squircle-border-bottom-right-smoothing: 0.4;`, 50 | }, 51 | 'squircle-smooth-r-md': { 52 | class: 'squircle-smooth-r-md', 53 | styles: `--squircle-border-top-right-smoothing: 0.6; 54 | --squircle-border-bottom-right-smoothing: 0.6;`, 55 | }, 56 | 'squircle-smooth-r-lg': { 57 | class: 'squircle-smooth-r-lg', 58 | styles: `--squircle-border-top-right-smoothing: 0.8; 59 | --squircle-border-bottom-right-smoothing: 0.8;`, 60 | }, 61 | 'squircle-smooth-r-xl': { 62 | class: 'squircle-smooth-r-xl', 63 | styles: `--squircle-border-top-right-smoothing: 1; 64 | --squircle-border-bottom-right-smoothing: 1;`, 65 | }, 66 | 'squircle-smooth-l-': { 67 | class: 'squircle-smooth-l-', 68 | styles: `--squircle-border-top-left-smoothing: ; 69 | --squircle-border-bottom-left-smoothing: ;`, 70 | }, 71 | 'squircle-smooth-l-xs': { 72 | class: 'squircle-smooth-l-xs', 73 | styles: `--squircle-border-top-left-smoothing: 0.2; 74 | --squircle-border-bottom-left-smoothing: 0.2;`, 75 | }, 76 | 'squircle-smooth-l-sm': { 77 | class: 'squircle-smooth-l-sm', 78 | styles: `--squircle-border-top-left-smoothing: 0.4; 79 | --squircle-border-bottom-left-smoothing: 0.4;`, 80 | }, 81 | 'squircle-smooth-l-md': { 82 | class: 'squircle-smooth-l-md', 83 | styles: `--squircle-border-top-left-smoothing: 0.6; 84 | --squircle-border-bottom-left-smoothing: 0.6;`, 85 | }, 86 | 'squircle-smooth-l-lg': { 87 | class: 'squircle-smooth-l-lg', 88 | styles: `--squircle-border-top-left-smoothing: 0.8; 89 | --squircle-border-bottom-left-smoothing: 0.8;`, 90 | }, 91 | 'squircle-smooth-l-xl': { 92 | class: 'squircle-smooth-l-xl', 93 | styles: `--squircle-border-top-left-smoothing: 1; 94 | --squircle-border-bottom-left-smoothing: 1;`, 95 | }, 96 | 'squircle-smooth-t-': { 97 | class: 'squircle-smooth-t-', 98 | styles: `--squircle-border-top-right-smoothing: ; 99 | --squircle-border-top-left-smoothing: ;`, 100 | }, 101 | 'squircle-smooth-t-xs': { 102 | class: 'squircle-smooth-t-xs', 103 | styles: `--squircle-border-top-right-smoothing: 0.2; 104 | --squircle-border-top-left-smoothing: 0.2;`, 105 | }, 106 | 'squircle-smooth-t-sm': { 107 | class: 'squircle-smooth-t-sm', 108 | styles: `--squircle-border-top-right-smoothing: 0.4; 109 | --squircle-border-top-left-smoothing: 0.4;`, 110 | }, 111 | 'squircle-smooth-t-md': { 112 | class: 'squircle-smooth-t-md', 113 | styles: `--squircle-border-top-right-smoothing: 0.6; 114 | --squircle-border-top-left-smoothing: 0.6;`, 115 | }, 116 | 'squircle-smooth-t-lg': { 117 | class: 'squircle-smooth-t-lg', 118 | styles: `--squircle-border-top-right-smoothing: 0.8; 119 | --squircle-border-top-left-smoothing: 0.8;`, 120 | }, 121 | 'squircle-smooth-t-xl': { 122 | class: 'squircle-smooth-t-xl', 123 | styles: `--squircle-border-top-right-smoothing: 1; 124 | --squircle-border-top-left-smoothing: 1;`, 125 | }, 126 | 'squircle-smooth-b-': { 127 | class: 'squircle-smooth-b-', 128 | styles: `--squircle-border-bottom-right-smoothing: ; 129 | --squircle-border-bottom-left-smoothing: ;`, 130 | }, 131 | 'squircle-smooth-b-xs': { 132 | class: 'squircle-smooth-b-xs', 133 | styles: `--squircle-border-bottom-right-smoothing: 0.2; 134 | --squircle-border-bottom-left-smoothing: 0.2;`, 135 | }, 136 | 'squircle-smooth-b-sm': { 137 | class: 'squircle-smooth-b-sm', 138 | styles: `--squircle-border-bottom-right-smoothing: 0.4; 139 | --squircle-border-bottom-left-smoothing: 0.4;`, 140 | }, 141 | 'squircle-smooth-b-md': { 142 | class: 'squircle-smooth-b-md', 143 | styles: `--squircle-border-bottom-right-smoothing: 0.6; 144 | --squircle-border-bottom-left-smoothing: 0.6;`, 145 | }, 146 | 'squircle-smooth-b-lg': { 147 | class: 'squircle-smooth-b-lg', 148 | styles: `--squircle-border-bottom-right-smoothing: 0.8; 149 | --squircle-border-bottom-left-smoothing: 0.8;`, 150 | }, 151 | 'squircle-smooth-b-xl': { 152 | class: 'squircle-smooth-b-xl', 153 | styles: `--squircle-border-bottom-right-smoothing: 1; 154 | --squircle-border-bottom-left-smoothing: 1;`, 155 | }, 156 | 'squircle-smooth-tr-': { 157 | class: 'squircle-smooth-tr-', 158 | styles: "--squircle-border-top-right-smoothing: ;", 159 | }, 160 | 'squircle-smooth-tr-xs': { 161 | class: 'squircle-smooth-tr-xs', 162 | styles: "--squircle-border-top-right-smoothing: 0.2;", 163 | }, 164 | 'squircle-smooth-tr-sm': { 165 | class: 'squircle-smooth-tr-sm', 166 | styles: "--squircle-border-top-right-smoothing: 0.4;", 167 | }, 168 | 'squircle-smooth-tr-md': { 169 | class: 'squircle-smooth-tr-md', 170 | styles: "--squircle-border-top-right-smoothing: 0.6;", 171 | }, 172 | 'squircle-smooth-tr-lg': { 173 | class: 'squircle-smooth-tr-lg', 174 | styles: "--squircle-border-top-right-smoothing: 0.8;", 175 | }, 176 | 'squircle-smooth-tr-xl': { 177 | class: 'squircle-smooth-tr-xl', 178 | styles: "--squircle-border-top-right-smoothing: 1;", 179 | }, 180 | 'squircle-smooth-tl-': { 181 | class: 'squircle-smooth-tl-', 182 | styles: "--squircle-border-top-left-smoothing: ;", 183 | }, 184 | 'squircle-smooth-tl-xs': { 185 | class: 'squircle-smooth-tl-xs', 186 | styles: "--squircle-border-top-left-smoothing: 0.2;", 187 | }, 188 | 'squircle-smooth-tl-sm': { 189 | class: 'squircle-smooth-tl-sm', 190 | styles: "--squircle-border-top-left-smoothing: 0.4;", 191 | }, 192 | 'squircle-smooth-tl-md': { 193 | class: 'squircle-smooth-tl-md', 194 | styles: "--squircle-border-top-left-smoothing: 0.6;", 195 | }, 196 | 'squircle-smooth-tl-lg': { 197 | class: 'squircle-smooth-tl-lg', 198 | styles: "--squircle-border-top-left-smoothing: 0.8;", 199 | }, 200 | 'squircle-smooth-tl-xl': { 201 | class: 'squircle-smooth-tl-xl', 202 | styles: "--squircle-border-top-left-smoothing: 1;", 203 | }, 204 | 'squircle-smooth-br-': { 205 | class: 'squircle-smooth-br-', 206 | styles: "--squircle-border-bottom-right-smoothing: ;", 207 | }, 208 | 'squircle-smooth-br-xs': { 209 | class: 'squircle-smooth-br-xs', 210 | styles: "--squircle-border-bottom-right-smoothing: 0.2;", 211 | }, 212 | 'squircle-smooth-br-sm': { 213 | class: 'squircle-smooth-br-sm', 214 | styles: "--squircle-border-bottom-right-smoothing: 0.4;", 215 | }, 216 | 'squircle-smooth-br-md': { 217 | class: 'squircle-smooth-br-md', 218 | styles: "--squircle-border-bottom-right-smoothing: 0.6;", 219 | }, 220 | 'squircle-smooth-br-lg': { 221 | class: 'squircle-smooth-br-lg', 222 | styles: "--squircle-border-bottom-right-smoothing: 0.8;", 223 | }, 224 | 'squircle-smooth-br-xl': { 225 | class: 'squircle-smooth-br-xl', 226 | styles: "--squircle-border-bottom-right-smoothing: 1;", 227 | }, 228 | 'squircle-smooth-bl-': { 229 | class: 'squircle-smooth-bl-', 230 | styles: "--squircle-border-bottom-left-smoothing: ;", 231 | }, 232 | 'squircle-smooth-bl-xs': { 233 | class: 'squircle-smooth-bl-xs', 234 | styles: "--squircle-border-bottom-left-smoothing: 0.2;", 235 | }, 236 | 'squircle-smooth-bl-sm': { 237 | class: 'squircle-smooth-bl-sm', 238 | styles: "--squircle-border-bottom-left-smoothing: 0.4;", 239 | }, 240 | 'squircle-smooth-bl-md': { 241 | class: 'squircle-smooth-bl-md', 242 | styles: "--squircle-border-bottom-left-smoothing: 0.6;", 243 | }, 244 | 'squircle-smooth-bl-lg': { 245 | class: 'squircle-smooth-bl-lg', 246 | styles: "--squircle-border-bottom-left-smoothing: 0.8;", 247 | }, 248 | 'squircle-smooth-bl-xl': { 249 | class: 'squircle-smooth-bl-xl', 250 | styles: "--squircle-border-bottom-left-smoothing: 1;", 251 | }, 252 | }} 253 | /> 254 | 255 | ## Usage 256 | 257 | ```tsx 258 |
259 | ``` 260 | 261 | You can also customize individual corners. 262 | 263 | ```tsx 264 |
265 | ``` 266 | 267 | You can also use a custom smoothing value. 268 | 269 | ```tsx 270 |
271 | ``` 272 | -------------------------------------------------------------------------------- /packages/tailwindcss/index.css: -------------------------------------------------------------------------------- 1 | @theme squircle { 2 | --squircle-border-radius-none: 0; 3 | --squircle-border-radius-xs: 2px; 4 | --squircle-border-radius-sm: 4px; 5 | --squircle-border-radius-md: 6px; 6 | --squircle-border-radius-lg: 8px; 7 | --squircle-border-radius-xl: 12px; 8 | --squircle-border-radius-2xl: 16px; 9 | --squircle-border-radius-3xl: 24px; 10 | --squircle-border-radius-4xl: 32px; 11 | --squircle-border-radius-5xl: 40px; 12 | --squircle-border-radius-6xl: 50px; 13 | --squircle-border-radius-7xl: 60px; 14 | --squircle-border-radius-full: 9999px; 15 | 16 | --squircle-border-smoothing-xs: 0.2; 17 | --squircle-border-smoothing-sm: 0.4; 18 | --squircle-border-smoothing-md: 0.6; 19 | --squircle-border-smoothing-lg: 0.8; 20 | --squircle-border-smoothing-xl: 1; 21 | } 22 | 23 | @utility squircle { 24 | :where(&) { 25 | background: paint(squircle); 26 | 27 | --squircle-border-radius: 0; 28 | --squircle-border-top-left-radius: 0; 29 | --squircle-border-top-right-radius: 0; 30 | --squircle-border-bottom-right-radius: 0; 31 | --squircle-border-bottom-left-radius: 0; 32 | --squircle-border-smoothing: 0.6; 33 | --squircle-border-top-left-smoothing: 0.6; 34 | --squircle-border-top-right-smoothing: 0.6; 35 | --squircle-border-bottom-right-smoothing: 0.6; 36 | --squircle-border-bottom-left-smoothing: 0.6; 37 | --squircle-border-width: 0; 38 | --squircle-border-color: unset; 39 | --squircle-background-color: unset; 40 | --squircle-mode: background; 41 | 42 | @supports not (background: paint(squircle)) { 43 | background-repeat: no-repeat; 44 | } 45 | } 46 | } 47 | 48 | @utility squircle-mask { 49 | :where(&) { 50 | mask-image: paint(squircle); 51 | 52 | --squircle-border-radius: 0; 53 | --squircle-border-top-left-radius: 0; 54 | --squircle-border-top-right-radius: 0; 55 | --squircle-border-bottom-right-radius: 0; 56 | --squircle-border-bottom-left-radius: 0; 57 | --squircle-border-smoothing: 0.6; 58 | --squircle-border-top-left-smoothing: 0.6; 59 | --squircle-border-top-right-smoothing: 0.6; 60 | --squircle-border-bottom-right-smoothing: 0.6; 61 | --squircle-border-bottom-left-smoothing: 0.6; 62 | --squircle-border-width: 0; 63 | --squircle-border-color: unset; 64 | --squircle-background-color: unset; 65 | --squircle-mode: mask-image; 66 | 67 | @supports not (mask-image: paint(squircle)) { 68 | mask-repeat: no-repeat; 69 | } 70 | } 71 | } 72 | 73 | @utility squircle-* { 74 | --squircle-border-radius: --value( 75 | --squircle-border-radius- *, 76 | [length], 77 | [number], 78 | number 79 | ); 80 | --squircle-border-top-left-radius: --value( 81 | --squircle-border-radius- *, 82 | [length], 83 | [number], 84 | number 85 | ); 86 | --squircle-border-top-right-radius: --value( 87 | --squircle-border-radius- *, 88 | [length], 89 | [number], 90 | number 91 | ); 92 | --squircle-border-bottom-right-radius: --value( 93 | --squircle-border-radius- *, 94 | [length], 95 | [number], 96 | number 97 | ); 98 | --squircle-border-bottom-left-radius: --value( 99 | --squircle-border-radius- *, 100 | [length], 101 | [number], 102 | number 103 | ); 104 | 105 | --squircle-opacity: calc(--modifier(integer) * 1%); 106 | --squircle-background-color: --alpha( 107 | --value(--color- *, [color], 'transparent') / var(--squircle-opacity, 100%) 108 | ); 109 | 110 | @supports not (background: paint(squircle)) { 111 | border-radius: --value( 112 | --squircle-border-radius- *, 113 | [length], 114 | [number], 115 | number 116 | ); 117 | } 118 | } 119 | 120 | @utility squircle-r-* { 121 | --squircle-border-top-right-radius: --value( 122 | --squircle-border-radius- *, 123 | [length], 124 | [number], 125 | number 126 | ); 127 | --squircle-border-bottom-right-radius: --value( 128 | --squircle-border-radius- *, 129 | [length], 130 | [number], 131 | number 132 | ); 133 | 134 | @supports not (background: paint(squircle)) { 135 | border-top-right-radius: --value( 136 | --squircle-border-radius- *, 137 | [length], 138 | [number], 139 | number 140 | ); 141 | border-bottom-right-radius: --value( 142 | --squircle-border-radius- *, 143 | [length], 144 | [number], 145 | number 146 | ); 147 | } 148 | } 149 | 150 | @utility squircle-l-* { 151 | --squircle-border-top-left-radius: --value( 152 | --squircle-border-radius- *, 153 | [length], 154 | [number], 155 | number 156 | ); 157 | --squircle-border-bottom-left-radius: --value( 158 | --squircle-border-radius- *, 159 | [length], 160 | [number], 161 | number 162 | ); 163 | 164 | @supports not (background: paint(squircle)) { 165 | border-top-left-radius: --value( 166 | --squircle-border-radius- *, 167 | [length], 168 | [number], 169 | number 170 | ); 171 | border-bottom-left-radius: --value( 172 | --squircle-border-radius- *, 173 | [length], 174 | [number], 175 | number 176 | ); 177 | } 178 | } 179 | 180 | @utility squircle-t-* { 181 | --squircle-border-top-right-radius: --value( 182 | --squircle-border-radius- *, 183 | [length], 184 | [number], 185 | number 186 | ); 187 | --squircle-border-top-left-radius: --value( 188 | --squircle-border-radius- *, 189 | [length], 190 | [number], 191 | number 192 | ); 193 | 194 | @supports not (background: paint(squircle)) { 195 | border-top-right-radius: --value( 196 | --squircle-border-radius- *, 197 | [length], 198 | [number], 199 | number 200 | ); 201 | border-top-left-radius: --value( 202 | --squircle-border-radius- *, 203 | [length], 204 | [number], 205 | number 206 | ); 207 | } 208 | } 209 | 210 | @utility squircle-b-* { 211 | --squircle-border-bottom-right-radius: --value( 212 | --squircle-border-radius- *, 213 | [length], 214 | [number], 215 | number 216 | ); 217 | --squircle-border-bottom-left-radius: --value( 218 | --squircle-border-radius- *, 219 | [length], 220 | [number], 221 | number 222 | ); 223 | 224 | @supports not (background: paint(squircle)) { 225 | border-bottom-right-radius: --value( 226 | --squircle-border-radius- *, 227 | [length], 228 | [number], 229 | number 230 | ); 231 | border-bottom-left-radius: --value( 232 | --squircle-border-radius- *, 233 | [length], 234 | [number], 235 | number 236 | ); 237 | } 238 | } 239 | 240 | @utility squircle-tr-* { 241 | --squircle-border-top-right-radius: --value( 242 | --squircle-border-radius- *, 243 | [length], 244 | [number], 245 | number 246 | ); 247 | 248 | @supports not (background: paint(squircle)) { 249 | border-top-right-radius: --value( 250 | --squircle-border-radius- *, 251 | [length], 252 | [number], 253 | number 254 | ); 255 | } 256 | } 257 | 258 | @utility squircle-tl-* { 259 | --squircle-border-top-left-radius: --value( 260 | --squircle-border-radius- *, 261 | [length], 262 | [number], 263 | number 264 | ); 265 | 266 | @supports not (background: paint(squircle)) { 267 | border-top-left-radius: --value( 268 | --squircle-border-radius- *, 269 | [length], 270 | [number], 271 | number 272 | ); 273 | } 274 | } 275 | 276 | @utility squircle-br-* { 277 | --squircle-border-bottom-right-radius: --value( 278 | --squircle-border-radius- *, 279 | [length], 280 | [number], 281 | number 282 | ); 283 | 284 | @supports not (background: paint(squircle)) { 285 | border-bottom-right-radius: --value( 286 | --squircle-border-radius- *, 287 | [length], 288 | [number], 289 | number 290 | ); 291 | } 292 | } 293 | 294 | @utility squircle-bl-* { 295 | --squircle-border-bottom-left-radius: --value( 296 | --squircle-border-radius- *, 297 | [length], 298 | [number], 299 | number 300 | ); 301 | 302 | @supports not (background: paint(squircle)) { 303 | border-bottom-left-radius: --value( 304 | --squircle-border-radius- *, 305 | [length], 306 | [number], 307 | number 308 | ); 309 | } 310 | } 311 | 312 | @utility squircle-smooth-* { 313 | --squircle-border-smoothing: --value( 314 | --squircle-border-smoothing- *, 315 | [number], 316 | number 317 | ); 318 | --squircle-border-top-left-smoothing: --value( 319 | --squircle-border-smoothing- *, 320 | [number], 321 | number 322 | ); 323 | --squircle-border-top-right-smoothing: --value( 324 | --squircle-border-smoothing- *, 325 | [number], 326 | number 327 | ); 328 | --squircle-border-bottom-right-smoothing: --value( 329 | --squircle-border-smoothing- *, 330 | [number], 331 | number 332 | ); 333 | --squircle-border-bottom-left-smoothing: --value( 334 | --squircle-border-smoothing- *, 335 | [number], 336 | number 337 | ); 338 | } 339 | 340 | @utility squircle-smooth-r-* { 341 | --squircle-border-top-right-smoothing: --value( 342 | --squircle-border-smoothing- *, 343 | [number], 344 | number 345 | ); 346 | --squircle-border-bottom-right-smoothing: --value( 347 | --squircle-border-smoothing- *, 348 | [number], 349 | number 350 | ); 351 | } 352 | 353 | @utility squircle-smooth-l-* { 354 | --squircle-border-top-left-smoothing: --value( 355 | --squircle-border-smoothing- *, 356 | [number], 357 | number 358 | ); 359 | --squircle-border-bottom-left-smoothing: --value( 360 | --squircle-border-smoothing- *, 361 | [number], 362 | number 363 | ); 364 | } 365 | 366 | @utility squircle-smooth-t-* { 367 | --squircle-border-top-right-smoothing: --value( 368 | --squircle-border-smoothing- *, 369 | [number], 370 | number 371 | ); 372 | --squircle-border-top-left-smoothing: --value( 373 | --squircle-border-smoothing- *, 374 | [number], 375 | number 376 | ); 377 | } 378 | 379 | @utility squircle-smooth-b-* { 380 | --squircle-border-bottom-right-smoothing: --value( 381 | --squircle-border-smoothing- *, 382 | [number], 383 | number 384 | ); 385 | --squircle-border-bottom-left-smoothing: --value( 386 | --squircle-border-smoothing- *, 387 | [number], 388 | number 389 | ); 390 | } 391 | 392 | @utility squircle-smooth-tr-* { 393 | --squircle-border-top-right-smoothing: --value( 394 | --squircle-border-smoothing- *, 395 | [number], 396 | number 397 | ); 398 | } 399 | 400 | @utility squircle-smooth-tl-* { 401 | --squircle-border-top-left-smoothing: --value( 402 | --squircle-border-smoothing- *, 403 | [number], 404 | number 405 | ); 406 | } 407 | 408 | @utility squircle-smooth-br-* { 409 | --squircle-border-bottom-right-smoothing: --value( 410 | --squircle-border-smoothing- *, 411 | [number], 412 | number 413 | ); 414 | } 415 | 416 | @utility squircle-smooth-bl-* { 417 | --squircle-border-bottom-left-smoothing: --value( 418 | --squircle-border-smoothing- *, 419 | [number], 420 | number 421 | ); 422 | } 423 | 424 | @utility squircle-border { 425 | --squircle-border-width: 1px; 426 | } 427 | 428 | @utility squircle-border-* { 429 | --squircle-border-width: --value([length], [number], number); 430 | 431 | --squircle-border-opacity: calc(--modifier(integer) * 1%); 432 | --squircle-border-color: --alpha( 433 | --value(--color- *, [color], 'transparent') / 434 | var(--squircle-border-opacity, 100%) 435 | ); 436 | } 437 | -------------------------------------------------------------------------------- /packages/paint-polyfill/LICENCE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------