refactor: converter pulse-libs de submodule→pasta normal
- ERA submodule gitlink → AGORA 28 arquivos TS diretamente no workspace - Atoms (10): Button, Badge, Card, GradientText, Divider, ThemeToggle + stubs 3D - Molecules (3): FeatureCard, Navbar, Footer - Organisms (4): HeroSection, FeaturesGrid, CtaBlock, StatsGrid - Templates (3): MainLayout, MinimalLayout, PageWithSidebar - lib/index.ts: TOKENS export (color/space/radius) + barrel exports - Repo próprio mantém: https://git.octal.tec.br/Roberto/pulse-libs - Usado por: pulse-3d-landing, test.octal.tec.br, projetos futuros
This commit is contained in:
-1
Submodule pulse-libs deleted from 29a7a5adb9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@pulse-libs/ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": { ".": "./src/index.ts" },
|
||||
"peerDependencies": { "react": "^18" }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function Badge({children,variant='neutral',size='md',style}:{
|
||||
children:React.ReactNode,variant?:'accent'|'purple'|'green'|'red'|'neutral',size?:'sm'|'md',style?:React.CSSProperties
|
||||
}) {
|
||||
const colors:{[k:string]:{bg:string,border:string,color:string}} = {
|
||||
accent: {bg:'rgba(37,99,235,.12)', border:'1px solid rgba(37,99,235,.4)', color:'#60a5fa'},
|
||||
purple: {bg:'rgba(124,58,237,.12)',border:'1px solid rgba(124,58,237,.4)',color:'#a78bfa'},
|
||||
green: {bg:'rgba(52,211,153,.1)', border:'1px solid rgba(52,211,153,.4)', color:'#34d399'},
|
||||
red: {bg:'rgba(239,68,68,.1)', border:'1px solid rgba(239,68,68,.4)', color:'#f87171'},
|
||||
neutral: {bg:'rgba(100,116,139,.1)',border:'1px solid rgba(100,116,139,.3)',color:'#94a3b8'},
|
||||
}
|
||||
const sizes:{[k:string]:React.CSSProperties} = {sm:{padding:'1px 8px',fontSize:'.68rem'},md:{padding:'2px 10px',fontSize:'.75rem'}}
|
||||
return <span style={{display:'inline-flex',alignItems:'center',gap:4,...sizes[size],borderRadius:99,fontFamily:"'JetBrains Mono',monospace",fontWeight:600,letterSpacing:'.06em',textTransform:'uppercase',...colors[variant],...style}}>{children}</span>
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export function Button({children, onClick, variant='primary', loading=false, disabled=false, ...rest}:{
|
||||
children: React.ReactNode, onClick?:()=>void, variant?:'primary'|'secondary'|'ghost'|'danger',
|
||||
loading?:boolean, disabled?:boolean, [k:string]:any
|
||||
}) {
|
||||
const styles: Record<string,React.CSSProperties> = {
|
||||
base: {display:'inline-flex',alignItems:'center',gap:8,padding:'10px 22px',borderRadius:99,border:none,cursor:disabled?'not-allowed':'pointer',fontWeight:700,fontFamily:'Inter,sans-serif',fontSize:'.95rem',transition:'all .18s',opacity:disabled||loading?'.55':1,position:'relative' as const},
|
||||
primary: {background:'linear-gradient(135deg,#2563eb,#1d4ed8)',color:'#fff',boxShadow:'0 0 28px rgba(37,99,235,.28)'},
|
||||
secondary: {background:'linear-gradient(135deg,#7c3aed,#6d28d9)',color:'#fff',boxShadow:'0 0 28px rgba(124,58,237,.22)'},
|
||||
ghost: {background:'transparent',border:'1px solid #334155',color:'#e4e4e7'},
|
||||
danger: {background:'linear-gradient(135deg,#dc2626,#b91c1c)',color:'#fff'},
|
||||
}
|
||||
return (
|
||||
<button onClick={disabled||loading?undefined:onClick} style={{...styles.base,...styles[variant],...(rest as any).style}} disabled={disabled||loading} {...rest}>
|
||||
{loading && <Spinner size={16}/>}
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
function Spinner({size=16}:{size?:number}){return <span style={{width:size,height:size,border:'2px solid rgba(255,255,255,.3)',borderTopColor:'#fff',borderRadius:'50%',display:'inline-block',animation:'spin .7s linear infinite'}}/>}
|
||||
@@ -0,0 +1,11 @@
|
||||
export function Card({children, variant='default', hover=false, style, ...rest}:{
|
||||
children:React.ReactNode, variant?:'default'|'elevated'|'glass', hover?:boolean, style?:React.CSSProperties, [k:string]:any
|
||||
}) {
|
||||
const base:React.CSSProperties = {padding:'1.6rem 1.8rem',borderRadius:16,backdropFilter:'blur(16px)',transition:'all .22s ease',...rest.style}
|
||||
const variants:{[k:string]:React.CSSProperties} = {
|
||||
default: {background:'rgba(15,17,23,.72)',border:'1px solid rgba(51,65,85,.55)',boxShadow:'0 4px 20px rgba(0,0,0,.15)'},
|
||||
elevated:{background:'rgba(22,25,35,.85)',border:'1px solid rgba(51,65,85,.65)',boxShadow:'0 8px 32px rgba(0,0,0,.28)'},
|
||||
glass: {background:'rgba(255,255,255,.03)',border:'1px solid rgba(255,255,255,.07)',boxShadow:'none'},
|
||||
}
|
||||
return <div style={{...base,...variants[variant],...style}} onMouseEnter={hover?()=>{(rest as any).onMouseEnter?.()}:undefined} onMouseLeave={hover?()=>{(rest as any).onMouseLeave?.()}:undefined}>{children}</div>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export function Divider({label, style}:{label?:string, style?:React.CSSProperties}) {
|
||||
return (
|
||||
<div style={{display:'flex',alignItems:'center',gap:16,width:'100%',...style}}>
|
||||
<div style={{flex:1,height:1,background:'linear-gradient(90deg,transparent,rgba(37,99,235,.18),transparent)'}}/>
|
||||
{label && <span style={{fontSize:'.75rem',color:'#64748b',fontFamily:"'JetBrains Mono',monospace",whiteSpace:'nowrap'}}>{label}</span>}
|
||||
<div style={{flex:1,height:1,background:'linear-gradient(90deg,transparent,rgba(37,99,235,.18),transparent)'}}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// @deprecated — Usar FloatingMesh do @pulse-libs/three
|
||||
export { default as FloatingMesh3d } from './_deprecated/FloatingMesh3d'
|
||||
@@ -0,0 +1,2 @@
|
||||
// @deprecated — Consulte Text do @react-three/drei
|
||||
export { default as FloatingText3d } from './_deprecated/FloatingText3d'
|
||||
@@ -0,0 +1,6 @@
|
||||
export function GradientText({children, from='#60a5fa', to='#a78bfa', via, size='inherit', weight=700, style}:{
|
||||
children:React.ReactNode, from?:string, to?:string, via?:string, size?:string, weight?:number, style?:React.CSSProperties
|
||||
}) {
|
||||
const grad = via ? `linear-gradient(135deg,${from} 0%,${via} 60%,${to} 100%)` : `linear-gradient(135deg,${from} 0%,${to} 100%)`
|
||||
return <span style={{background:grad,WebkitBackgroundClip:'text',WebkitTextFillColor:'transparent',backgroundClip:'text',fontSize:size,fontWeight:weight,...style}}>{children}</span>
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// @deprecated — Injetar <pointLight> direto na cena Three.js
|
||||
export { default as LightGlow3d } from './_deprecated/LightGlow3d'
|
||||
@@ -0,0 +1,2 @@
|
||||
// @deprecated — Usar ParticleField do @pulse-libs/three
|
||||
export { default as ParticleField3d } from './_deprecated/ParticleField3d'
|
||||
@@ -0,0 +1,12 @@
|
||||
import {useState} from 'react'
|
||||
export function ThemeToggle({defaultDark=true}:{defaultDark?:boolean}) {
|
||||
const [dark, setDark] = useState(defaultDark)
|
||||
return (
|
||||
<button
|
||||
onClick={()=>{const next=!dark;setDark(next);document.documentElement.setAttribute('data-theme',next?'dark':'light')}}
|
||||
aria-label="Toggle theme"
|
||||
style={{position:'fixed',top:16,right:16,zIndex:9999,padding:'7px 14px',borderRadius:99,border:'none',cursor:'pointer',color:'#fff',fontWeight:700,fontFamily:'Inter,sans-serif',fontSize:'.85rem',background:dark?'linear-gradient(135deg,#2563eb,#7c3aed)':'linear-gradient(135deg,#fbbf24,#f97316)',boxShadow:'0 0 16px rgba(0,0,0,.3)',transition:'all .2s'}}>
|
||||
{dark?'🌙':'☀️'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// stub — implementação movida para @pulse-libs/three
|
||||
@@ -0,0 +1 @@
|
||||
// stub — implementação movida para @pulse-libs/three
|
||||
@@ -0,0 +1,7 @@
|
||||
export { Button } from './Button'
|
||||
export { Badge } from './Badge'
|
||||
export { Card } from './Card'
|
||||
export { GradientText } from './GradientText'
|
||||
export { Divider } from './Divider'
|
||||
export { ThemeToggle } from './ThemeToggle'
|
||||
export * from './_deprecated'
|
||||
@@ -0,0 +1,19 @@
|
||||
// @pulse-libs/ui — Design System compartilhado
|
||||
// Atomic Design · React 18 · TypeScript — MIT 2026
|
||||
export * from './atoms'
|
||||
export * from './molecules'
|
||||
export * from './organisms'
|
||||
export * from './templates'
|
||||
|
||||
// ─── Design Tokens (CSS custom properties) ───
|
||||
export const TOKENS = {
|
||||
color: {
|
||||
bg: '#050510', bg2: '#0c0c18',
|
||||
text: '#e4e4e7', dim: '#94a3b8',
|
||||
accent: '#2563eb', accent2:'#60a5fa',
|
||||
secondary: '#7c3aed', secondary2:'#a78bfa',
|
||||
border: 'rgba(51,65,85,.55)',
|
||||
},
|
||||
space: { sm:4, md:8, lg:16, xl:24, xxl:48 },
|
||||
radius: { sm:4, md:8, lg:12, xl:16, round:9999 },
|
||||
} as const
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Card } from '../atoms'
|
||||
interface FeatureCardProps { title:string, description:string, icon?:string, delay?:number, variant?:'default'|'accent', style?:React.CSSProperties }
|
||||
export function FeatureCard({title,description,icon,delay=0,variant='default',style}:FeatureCardProps){
|
||||
const bg = variant==='accent'?'linear-gradient(135deg,rgba(37,99,235,.06),rgba(124,58,237,.04))':'transparent'
|
||||
return (
|
||||
<Card hover style={{cursor:'default',position:'relative',overflow:'hidden',...style}}>
|
||||
{variant==='accent' && <div style={{position:'absolute',inset:0,background:bg,pointerEvents:'none'}}/>}
|
||||
{icon && <div style={{fontSize:'2rem',marginBottom:'.7rem',display:'block'}}>{icon}</div>}
|
||||
<h3 style={{fontSize:'1.05rem',fontWeight:700,marginBottom:'.4rem',color:'#e4e4e7'}}>{title}</h3>
|
||||
<p style={{fontSize:'.85rem',color:'#94a3b8',lineHeight:1.65}}>{description}</p>
|
||||
<div style={{marginTop:'1rem',display:'flex',gap:'.45rem',flexWrap:'wrap'}}>
|
||||
{variant==='accent' && <span style={{padding:'1px 8px',borderRadius:99,background:'rgba(37,99,235,.12)',border:'1px solid rgba(37,99,235,.3)',color:'#60a5fa',fontSize:'.68rem',fontFamily:"'JetBrains Mono',monospace",fontWeight:600}}>core</span>}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
interface FooterProps { brand?:string, year?:number, links:{label:string,href:string}[] }
|
||||
export function Footer({brand='Pulse 3D',year=__DATE__,links=[]}:FooterProps){
|
||||
return (
|
||||
<footer style={{padding:'1.8rem 2rem',borderTop:'1px solid rgba(51,65,85,.4)',display:'flex',justifyContent:'space-between',alignItems:'center',flexWrap:'wrap',gap:'1rem'}}>
|
||||
<span style={{color:'#64748b',fontSize:'.75rem'}}>{'\u2699'} {brand} \u00b7 MIT \u00b7 {new Date().getFullYear()}</span>
|
||||
<ul style={{display:'flex',gap:'1.2rem',listStyle:'none',margin:0,padding:0}}>
|
||||
{links.map(l=><li key={l.href}><a href={l.href} style={{color:'#60a5fa',fontSize:'.75rem',textDecoration:'none'}} onMouseEnter={e=>{e.currentTarget.style.textDecoration='underline'}} onMouseLeave={e=>{e.currentTarget.style.textDecoration='none'}}>{l.label}</a></li>)}
|
||||
</ul>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { GradientText, Button } from '../atoms'
|
||||
interface NavProps { logo:string, links:{label:string,href:string}[], ctaLabel?:string, ctaHref?:string }
|
||||
export function Navbar({logo,links,ctaLabel,ctaHref='#'}:NavProps){
|
||||
return (
|
||||
<nav role="navigation" aria-label="Main" style={{position:'sticky',top:0,zIndex:100,display:'flex',alignItems:'center',justifyContent:'space-between',padding:'10px 2rem',background:'rgba(5,5,16,.82)',backdropFilter:'blur(16px)',borderBottom:'1px solid rgba(51,65,85,.3)'}}>
|
||||
<a href="/" aria-label="Home" style={{fontSize:'1rem',fontWeight:900,color:'#e4e4e7',letterSpacing:'-.03em',textDecoration:'none'}}>
|
||||
<GradientText from="#60a5fa" to="#a78bfa">{logo}</GradientText>
|
||||
</a>
|
||||
<ul style={{display:'flex',alignItems:'center',gap:'1.5rem',listStyle:'none',margin:0,padding:0}}>
|
||||
{links.map(l=> <li key={l.href}><a href={l.href} style={{color:'#94a3b8',fontSize:'.88rem',textDecoration:'none',transition:'color .15s'}} onMouseEnter={e=>{e.currentTarget.style.color='#60a5fa'}} onMouseLeave={e=>{e.currentTarget.style.color='#94a3b8'}}>{l.label}</a></li>)}
|
||||
{ctaLabel && <li><Button onClick={()=>{window.location.href=ctaHref}} variant="primary" style={{padding:'6px 16px',fontSize:'.83rem'}}>{ctaLabel}</Button></li>}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { FeatureCard } from './FeatureCard'
|
||||
export { Navbar } from './Navbar'
|
||||
export { Footer } from './Footer'
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Button, GradientText } from '../atoms'
|
||||
interface CtaProps { title:string, description:string, primary:{label:string,onClick():void}, secondary?:{label:string,onClick():void}, dark?:boolean }
|
||||
export function CtaBlock({title,description,primary,secondary,dark=false}:CtaProps){
|
||||
const bg = dark ? 'rgba(5,5,16,.96)' : 'rgba(15,17,23,.5)'
|
||||
return (
|
||||
<section style={{padding:'6rem 2rem',textAlign:'center',background:'linear-gradient(135deg,rgba(37,99,235,.05),rgba(124,58,237,.05))',borderTop:'1px solid rgba(51,65,85,.3)'}}>
|
||||
<h2 style={{fontSize:'clamp(1.6rem,5vw,3rem)',fontWeight:900,marginBottom:'.8rem',lineHeight:1.15}}><GradientText from="#60a5fa" to="#a78bfa">{title}</GradientText></h2>
|
||||
<p style={{color:'#94a3b8',maxWidth:520,margin:'0 auto 2rem',fontSize:'1.05rem',lineHeight:1.65}}>{description}</p>
|
||||
<div style={{display:'flex',gap:'1rem',flexWrap:'wrap',justifyContent:'center'}}>
|
||||
<Button variant="primary" onClick={primary.onClick} style={{boxShadow:'0 0 40px rgba(37,99,235,.3)'}}>{primary.label}</Button>
|
||||
{secondary && <Button variant="ghost" onClick={()=>window.location.href='/'} style={{border:'1px solid #334155',color:'#e4e4e7'}}>{secondary.label}</Button>}
|
||||
</div>
|
||||
<p style={{marginTop:'2rem',fontSize:'.72rem',color:'#475569'}}>{'\u2699'} @pulse-libs/ui — MIT — {new Date().getFullYear()}</p>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Card } from '../atoms'
|
||||
interface Feature { icon:string, title:string, description:string }
|
||||
interface FeaturesProps { title?:string, features:Feature[], style?:React.CSSProperties }
|
||||
export function FeaturesGrid({title,features,style}:FeaturesProps){
|
||||
return (
|
||||
<section style={{padding:'3.5rem 2rem',maxWidth:1100,margin:'0 auto',...style}}>
|
||||
{title && <h2 style={{fontSize:'clamp(1.4rem,4vw,2.6rem)',fontWeight:800,lineHeight:1.15,color:'#e4e4e7',marginBottom:'.5rem'}}><span style={{background:'linear-gradient(135deg,#60a5fa,#a78bfa)',WebkitBackgroundClip:'text',WebkitTextFillColor:'transparent'}}>{title}</span></h2>}
|
||||
<p style={{color:'#94a3b8',maxWidth:580,marginBottom:'2rem',fontSize:'.95rem',lineHeight:1.6}}>Componentes que compõem o sistema — do átomo ao organismo.</p>
|
||||
<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(260px,1fr))',gap:'1.2rem'}}>
|
||||
{features.map((f,i)=><Card key={i} hover style={{cursor:'default'}}><div style={{fontSize:'2.2rem',marginBottom:'.8rem'}}>{f.icon}</div><h3 style={{fontSize:'1.04rem',fontWeight:700,marginBottom:'.4rem',color:'#e4e4e7'}}>{f.title}</h3><p style={{fontSize:'.86rem',color:'#94a3b8',lineHeight:1.62}}>{f.description}</p></Card>) }
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Card, Badge, GradientText, Button } from '../atoms'
|
||||
import type { JSX } from 'react'
|
||||
interface HeroProps { badge?:string, title:string, description:string, cta:{label:string,onClick():void}[], showScrollHint?:boolean, style?:React.CSSProperties }
|
||||
export function HeroSection({badge,title,description,cta=[],showScrollHint=true,style}:HeroProps){
|
||||
return (
|
||||
<section style={{minHeight:'100vh',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',textAlign:'center',padding:'2rem',background:'radial-gradient(ellipse at 50% 20%,rgba(37,99,235,.10) 0%,transparent 65%),radial-gradient(ellipse at 80% 75%,rgba(124,58,237,.07) 0%,transparent 60%)',...style}} role="banner">
|
||||
{badge && <Badge variant="accent">{badge}</Badge>}
|
||||
<h1 style={{fontSize:'clamp(2.2rem,7vw,5.2rem)',fontWeight:900,lineHeight:1.04,letterSpacing:'-.03em',marginBottom:'1rem'}}>
|
||||
<GradientText from="#60a5fa" to="#a78bfa">{title}</GradientText>
|
||||
</h1>
|
||||
<p style={{fontSize:'clamp(.95rem,2.2vw,1.2rem)',color:'#94a3b8',maxWidth:620,marginBottom:'2.5rem',lineHeight:1.6}}>{description}</p>
|
||||
<div style={{display:'flex',gap:'1rem',flexWrap:'wrap',justifyContent:'center'}}>
|
||||
{cta.map((b,i)=><Button key={i} variant={i===0?'primary':'ghost'} onClick={b.onClick}>{b.label}</Button>)}
|
||||
</div>
|
||||
{showScrollHint && <div style={{position:'absolute',bottom:'2.5rem',left:'50%',transform:'translateX(-50%)',color:'#64748b',fontSize:'.72rem',letterSpacing:'.18em',textTransform:'uppercase',display:'flex',flexDirection:'column',alignItems:'center',gap:'.4rem'}}><span>scroll para explorar</span><div style={{animation:'bounce 1.5s infinite'}}>↓</div></div>}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
interface Stat { label:string, value:string|number, color?:string }
|
||||
export function StatsGrid({stats}:{stats:Stat[]}){
|
||||
return (
|
||||
<div style={{padding:'3rem 2rem',maxWidth:900,margin:'0 auto',display:'grid',gridTemplateColumns:'repeat(auto-fit,minmax(160px,1fr))',gap:'1rem',textAlign:'center'}}>
|
||||
{stats.map((s,i)=><div key={i} style={{padding:'1.5rem',background:'rgba(15,17,23,.6)',border:'1px solid rgba(51,65,85,.4)',borderRadius:16,backdropFilter:'blur(12px)'}}>
|
||||
<div style={{fontSize:'2rem',fontWeight:900,color:s.color||'#60a5fa'}}>{s.value}</div>
|
||||
<div style={{fontSize:'.8rem',color:'#94a3b8',marginTop:'.2rem'}}>{s.label}</div>
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export { HeroSection } from './HeroSection'
|
||||
export { FeaturesGrid } from './FeaturesGrid'
|
||||
export { CtaBlock } from './CtaBlock'
|
||||
export { StatsGrid } from './StatsGrid'
|
||||
@@ -0,0 +1 @@
|
||||
// Pages: index.ts
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Navbar, Footer, Divider } from '..'
|
||||
interface LayoutProps { children:ReactNode, nav:{logo:string,links:{label:string,href:string}[],cta?:{label:string,href:string}}, footer?:{brand:string,links:{label:string,href:string}[]} }
|
||||
export function MainLayout({children,nav,footer}:LayoutProps){
|
||||
return (
|
||||
<div style={{minHeight:'100vh',display:'flex',flexDirection:'column',background:'#050510'}}>
|
||||
<Navbar logo={nav.logo} links={nav.links} ctaLabel={nav.cta?.label} ctaHref={nav.cta?.href}/>
|
||||
<main style={{flex:1}}>{children}</main>
|
||||
{footer && <Footer brand={footer.brand} links={footer.links}/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
interface MinimalProps { children:React.ReactNode, centered?:boolean }
|
||||
export function MinimalLayout({children,centered=true}:MinimalProps){
|
||||
return <div style={{minHeight:'100vh',display:'flex',alignItems:centered?'center':'flex-start',justifyContent:'center',padding:'4rem 2rem',background:'#050510',color:'#e4e4e7'}}>{children}</div>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { PageWithSidebar } from './PageWithSidebar'
|
||||
@@ -0,0 +1,2 @@
|
||||
export { MainLayout } from './MainLayout'
|
||||
export { MinimalLayout } from './MinimalLayout'
|
||||
Reference in New Issue
Block a user