On this page
Demo FSD
Date: 2025-22-09
Status: accepted
Project Structure
src/
pages/
login/
ui/page.tsx
features/
auth/
ui/login-form.tsx
model/use-login.ts
entities/
user/
model/types.ts
shared/
ui/ # Facade for Core UI
index.ts
core-ui/ # Internal UI-kit (Atomic Design)
atoms/
Button.tsx
Input.tsx
molecules/
FormField.tsx
organisms/
Modal.tsx
index.ts
Core UI (Atomic Design)
core-ui/atoms/Button.tsx
import React from "react";
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: "primary" | "secondary";
};
export const Button: React.FC<ButtonProps> = ({ variant = "primary", ...props }) => {
const base = "px-4 py-2 rounded font-medium";
const styles =
variant === "primary"
? "bg-blue-600 text-white hover:bg-blue-700"
: "bg-gray-200 text-gray-800 hover:bg-gray-300";
return <button className={`${base} ${styles}`} {...props} />;
};core-ui/atoms/Input.tsx
import React from "react";
type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
export const Input: React.FC<InputProps> = (props) => {
return (
<input
{...props}
className="border border-gray-300 rounded px-3 py-2 w-full focus:outline-none focus:ring focus:ring-blue-400"
/>
);
};core-ui/molecules/FormField.tsx
import React from "react";
type FormFieldProps = {
label: string;
children: React.ReactNode;
};
export const FormField: React.FC<FormFieldProps> = ({ label, children }) => (
<label className="block mb-4">
<span className="text-gray-700 mb-1 block">{label}</span>
{children}
</label>
);core-ui/index.ts
export { Button } from "./atoms/Button";
export { Input } from "./atoms/Input";
export { FormField } from "./molecules/FormField";Shared Layer (FSD)
shared/ui/index.ts
// Facade for Core UI — upper layers should not import directly from core-ui
export { Button, Input, FormField } from "core-ui";Feature: Auth
features/auth/model/use-login.ts
import { useState } from "react";
export function useLogin() {
const [loading, setLoading] = useState(false);
const login = async (email: string, password: string) => {
setLoading(true);
// simulate API request
await new Promise((res) => setTimeout(res, 1000));
setLoading(false);
return { id: 1, name: "Demo User", email };
};
return { login, loading };
}features/auth/ui/login-form.tsx
import React, { useState } from "react";
import { Button, Input, FormField } from "@/shared/ui";
import { useLogin } from "../model/use-login";
export const LoginForm: React.FC = () => {
const { login, loading } = useLogin();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const user = await login(email, password);
console.log("Logged in:", user);
};
return (
<form onSubmit={onSubmit} className="max-w-sm mx-auto mt-8">
<FormField label="Email">
<Input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</FormField>
<FormField label="Password">
<Input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</FormField>
<Button type="submit" disabled={loading}>
{loading ? "Loading..." : "Login"}
</Button>
</form>
);
};Page (SSR / Routing)
pages/login/ui/page.tsx
import React from "react";
import { LoginForm } from "@/features/auth/ui/login-form";
export default function LoginPage() {
return (
<div className="flex items-center justify-center min-h-screen bg-gray-50">
<div className="bg-white shadow p-6 rounded w-full max-w-md">
<h1 className="text-xl font-bold mb-4">Sign in</h1>
<LoginForm />
</div>
</div>
);
}Summary
- Core UI (Atomic): independent reusable components (Button, Input, FormField).
- Shared UI (FSD): facade for Core UI to enforce dependency rules (Included Business Logic).
- Feature (Auth):
LoginFormfeature using Core UI components. - Page:
/loginpage assembling the feature and layout.