Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .changeset/widget-design-system-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
'@lifi/widget': major
---

The widget migrates its components from Material UI to the LI.FI design-system.

Migrated components are styled by a design-system stylesheet instead of the Material UI theme. Load it once alongside the widget:

```ts
import '@lifi/widget/styles.css'
```

To customize migrated components, add a stylesheet loaded after `@lifi/widget/styles.css` so its rules take precedence by source order.

Re-theme the widget by overriding the design-system tokens, with light values in `:root` and dark values in `.dark`:

```css
:root {
--primary: oklch(0.7 0.19 44);
--radius: 1rem;
}

.dark {
--primary: oklch(0.78 0.16 44);
}
```

Restyle a single component by overriding its class in the `components` cascade layer:

```css
@layer components {
.lifi-button-variant-default {
text-transform: uppercase;
}
}
```
5 changes: 5 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
}
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
Expand Down
2 changes: 2 additions & 0 deletions packages/widget-playground-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
},
"author": "Eugene Chybisov <eugene@li.finance>",
"dependencies": {
"@lifi/widget": "workspace:*",
"@lifi/widget-playground": "workspace:*",
"@tanstack/react-query": "^5.101.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"vite-plugin-mkcert": "^2.1.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.1",
"@vitejs/plugin-react": "^6.0.2",
"react-scan": "^0.5.7",
"source-map-explorer": "^2.5.3",
Expand Down
2 changes: 2 additions & 0 deletions packages/widget-playground-vite/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { App } from './App.js'
import '@lifi/widget/styles.css'
import './widget-overrides.css'
import './index.css'
import { reportWebVitals } from './reportWebVitals.js'

Expand Down
80 changes: 80 additions & 0 deletions packages/widget-playground-vite/src/widget-overrides.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@property --rotate {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}

:root {
--primary-foreground: #ffffff;
}

.dark {
--primary: #dd0426;
--secondary: #9197ae;
--primary-foreground: #ffffff;
}

@layer components {
.lifi-button-variant-default {
--radius: 50px;
background-image: linear-gradient(
178deg,
var(--secondary) -64.49%,
var(--primary) 71.88%
);
position: relative;
cursor: pointer;
overflow: clip;

&:hover,
&:hover::after {
background-image: linear-gradient(
178deg,
color-mix(in srgb, var(--secondary), white 15%) -64.49%,
color-mix(in srgb, var(--primary), white 15%) 71.88%
);
}

&:before {
content: "";
background: conic-gradient(
transparent 270deg,
hsla(0, 0%, 100%, 0.7),
transparent
);
position: absolute;
aspect-ratio: 1;
top: 50%;
left: 50%;
width: 100%;
animation: rotate 2s linear infinite;
}

&:after {
content: "";
background-image: linear-gradient(
178deg,
var(--secondary) -64.49%,
var(--primary) 71.88%
);
position: absolute;
inset: 2px;
border-radius: calc(var(--radius) - 2px);
transition: background-image 0.2s ease;
}

span {
z-index: 1;
}
}
}

@keyframes rotate {
0% {
transform: translate(-50%, -50%) scale(1.4) rotate(0turn);
}

100% {
transform: translate(-50%, -50%) scale(1.4) rotate(1turn);
}
}
2 changes: 2 additions & 0 deletions packages/widget-playground-vite/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
// biome-ignore lint/correctness/noUnusedImports: used when testing wallet providers that require https
Expand All @@ -10,6 +11,7 @@ export default defineConfig({
// mkcert(),
nodePolyfills(),
react(),
tailwindcss(),
],
oxc: {
target: 'esnext',
Expand Down
26 changes: 26 additions & 0 deletions packages/widget/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/widget.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"registries": {
"@core": "https://lifinance.github.io/design-system/r/core/{style}/customize/{name}.json",
"@widget": "https://lifinance.github.io/design-system/r/widget/{style}/customize/{name}.json"
},
"aliases": {
"components": "@/registry",
"utils": "@/registry/lib/utils",
"ui": "@/registry/ui",
"lib": "@/registry/lib",
"hooks": "@/registry/hooks"
}
}
19 changes: 17 additions & 2 deletions packages/widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"sideEffects": false,
"exports": {
".": "./src/index.ts",
"./styles.css": "./src/styles/widget.css"
},
"sideEffects": [
"**/*.css"
],
"scripts": {
"watch": "tsdown --watch",
"build": "pnpm clean && pnpm build:version && tsdown",
"build": "pnpm clean && pnpm build:version && tsdown && pnpm build:css",
"build:css": "tailwindcss -i ./src/styles/widget.css -o ./dist/esm/styles/widget.css --minify",
"build:prerelease": "node ../../scripts/prerelease.js && cpy '../../README.md' .",
"build:postrelease": "node ../../scripts/postrelease.js && rm -rf README.md",
"build:version": "node ../../scripts/version.js",
Expand Down Expand Up @@ -44,6 +51,7 @@
"lifi"
],
"dependencies": {
"@base-ui/react": "^1.6.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@lifi/sdk": "^4.0.0",
Expand All @@ -54,22 +62,29 @@
"@mui/system": "^9.1.1",
"@tanstack/react-router": "^1.170.16",
"@tanstack/react-virtual": "^3.14.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"eventemitter3": "^5.0.4",
"i18next": "^26.3.1",
"microdiff": "^1.5.0",
"motion": "^12.38.0",
"react-i18next": "^17.0.8",
"react-intersection-observer": "^10.0.3",
"react-transition-group": "^4.4.5",
"tailwind-merge": "^3.6.0",
"zustand": "^5.0.14"
},
"devDependencies": {
"@tailwindcss/cli": "^4.3.1",
"@types/node": "^26.0.0",
"@types/react-transition-group": "^4.4.12",
"cpy-cli": "^7.0.0",
"madge": "^8.0.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"shadcn": "^4.11.0",
"tailwindcss": "^4.3.1",
"tw-animate-css": "^1.4.0",
"typescript": "^6.0.3",
"vitest": "^4.1.9"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useWalletMenu } from '@lifi/wallet-management'
import { Button } from '@mui/material'
import { useTranslation } from 'react-i18next'
import { useChain } from '../../hooks/useChain.js'
import { useRouteRequiredAccountConnection } from '../../hooks/useRouteRequiredAccountConnection.js'
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
import { cn } from '../../registry/lib/utils.js'
import { Button } from '../../registry/ui/button.js'
import { useFieldValues } from '../../stores/form/useFieldValues.js'
import type { BaseTransactionButtonProps } from './types.js'

Expand All @@ -13,7 +14,7 @@ export const BaseTransactionButton: React.FC<BaseTransactionButtonProps> = ({
disabled,
loading,
route,
sx,
className,
}) => {
const { t } = useTranslation()
const { walletConfig } = useWidgetConfig()
Expand Down Expand Up @@ -56,17 +57,12 @@ export const BaseTransactionButton: React.FC<BaseTransactionButtonProps> = ({

return (
<Button
variant="contained"
color="primary"
className={cn('w-full', className)}
onClick={handleClick}
disabled={connected && disabled}
loading={loading}
loadingPosition="center"
fullWidth
sx={sx}
disabled={Boolean((connected && disabled) || loading)}
data-testid="widget-transaction-button"
>
{getButtonText()}
<span>{getButtonText()}</span>
</Button>
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { RouteExtended } from '@lifi/sdk'
import type { SxProps, Theme } from '@mui/material'

export interface BaseTransactionButtonProps {
onClick?(): void
text?: string
disabled?: boolean
loading?: boolean
route?: RouteExtended
sx?: SxProps<Theme>
className?: string
}
2 changes: 1 addition & 1 deletion packages/widget/src/pages/MainPage/ReviewButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const ReviewButton: React.FC = () => {
onClick={handleClick}
disabled={currentRoute && requiredToAddress && !toAddress}
route={currentRoute}
sx={{ flex: 1 }}
className="flex-1"
/>
)
}
6 changes: 6 additions & 0 deletions packages/widget/src/registry/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs))
}
63 changes: 63 additions & 0 deletions packages/widget/src/registry/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Button as ButtonPrimitive } from '@base-ui/react/button'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '../lib/utils.js'

const variants = {
variant: {
default: 'lifi-button-variant-default',
outline: 'lifi-button-variant-outline',
secondary: 'lifi-button-variant-secondary',
ghost: 'lifi-button-variant-ghost',
destructive: 'lifi-button-variant-destructive',
link: 'lifi-button-variant-link',
},
size: {
default: 'lifi-button-size-default',
xs: 'lifi-button-size-xs',
sm: 'lifi-button-size-sm',
lg: 'lifi-button-size-lg',
icon: 'lifi-button-size-icon',
'icon-xs': 'lifi-button-size-icon-xs',
'icon-sm': 'lifi-button-size-icon-sm',
'icon-lg': 'lifi-button-size-icon-lg',
},
}

const buttonVariants: (
props?: {
[Variant in keyof typeof variants]?:
| keyof (typeof variants)[Variant]
| null
| undefined
}
) => string = cva(
'lifi-button group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
{
variants,
defaultVariants: {
variant: 'default',
size: 'default',
},
}
)

export type ButtonProps = Omit<ButtonPrimitive.Props, 'className'> & {
className?: string
} & VariantProps<typeof buttonVariants>

export function Button({
className,
variant = 'default',
size = 'default',
...props
}: ButtonProps): React.JSX.Element {
return (
<ButtonPrimitive
data-slot="button"
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
}

export { buttonVariants }
Loading
Loading