fkdev UIv2.4.1

Uygulama · Kurulum rehberleri

Her ortamda aynı tasarım.

fkdev UI yalnızca CSS sınıfları ve küçük bir JS dosyasıdır; bu yüzden PHP’den React’e her yerde aynı şekilde çalışır.

01Saf HTML#

Hiçbir araç gerekmez. İlk sayfa şablonunu al, her sayfayı kendi klasörüne index.html olarak koy, sunucuya yükle.

02PHP: düzen ve parçalar#

PHP’de ortak header ve footer’ı bir kez yazıp her sayfada include et. Bu klasör yapısı paylaşımlı hostingde (cPanel, Plesk) doğrudan çalışır.

Metin
site/
├── index.php
├── iletisim/index.php
├── api/iletisim.php
├── inc/
│   ├── config.php
│   ├── header.php
│   └── footer.php
└── .htaccess
PHPinc/config.php
<?php
declare(strict_types=1);
session_start();
if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(32));

function e(?string $s): string { return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8'); }
function csrf_field(): string { return '<input type="hidden" name="csrf" value="' . e($_SESSION['csrf']) . '">'; }
function flash(string $msg, string $type = 'success'): void { $_SESSION['flash'] = [$msg, $type]; }
PHPinc/header.php
<?php require __DIR__ . '/config.php'; ?>
<!DOCTYPE html>
<html lang="tr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title><?= e($title ?? 'Sitem') ?></title>
  <meta name="description" content="<?= e($description ?? '') ?>">
  <link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500&family=Geist:wght@300..700&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="/assets/fkdev/fkdev-ui.css">
  <script src="/assets/fkdev/fkdev-ui.js" defer></script>
</head>
<body class="fk-body fk-atmosphere">
  <header class="fk-navbar">
    <div class="fk-navbar-in fk-glass">
      <a class="fk-brand" href="/"><b>Site <i>Adı</i></b></a>
      <nav class="fk-tabsbar">
        <span class="fk-tabsbar-ind"></span>
        <a href="/" <?= ($page ?? '') === 'home' ? 'aria-current="page"' : '' ?>>Ana sayfa</a>
        <a href="/iletisim/" <?= ($page ?? '') === 'iletisim' ? 'aria-current="page"' : '' ?>>İletişim</a>
      </nav>
      <div class="fk-navbar-tools"><button class="fk-btn fk-btn-icon" data-fk-theme-toggle aria-label="Tema">◐</button></div>
    </div>
  </header>
  <main class="fk-wrap">
PHPinc/footer.php
  </main>
  <footer class="fk-footer">
    <div class="fk-footer-card">
      <a class="fk-footer-brand" href="/">Site <i>Adı</i></a>
      <p class="fk-footer-bottom">© <?= date('Y') ?></p>
    </div>
  </footer>
  <?php if (!empty($_SESSION['flash'])): [$m, $t] = $_SESSION['flash']; unset($_SESSION['flash']); ?>
    <script>addEventListener('DOMContentLoaded', () => FK.toast(<?= json_encode($m) ?>, <?= json_encode($t) ?>));</script>
  <?php endif; ?>
</body>
</html>
PHPiletisim/index.php
<?php
$title = 'İletişim — Sitem';
$description = 'Bize ulaşın.';
$page = 'iletisim';
require __DIR__ . '/../inc/header.php';
?>
<section class="fk-center" style="padding:120px 0 40px">
  <h1 class="fk-h1">Konuşa<em>lım.</em></h1>
</section>
<form class="fk-form fk-card" method="post" action="/api/iletisim.php" data-fk-validate style="max-width:560px;margin:0 auto">
  <?= csrf_field() ?>
  <div class="fk-field">
    <label class="fk-label" for="ad">Ad</label>
    <input class="fk-input" id="ad" name="ad" required minlength="2">
    <span class="fk-error"></span>
  </div>
  <div class="fk-field">
    <label class="fk-label" for="mail">E-posta</label>
    <input class="fk-input" id="mail" name="mail" type="email" required>
    <span class="fk-error"></span>
  </div>
  <div class="fk-field">
    <label class="fk-label" for="mesaj">Mesaj</label>
    <textarea class="fk-textarea" id="mesaj" name="mesaj" required minlength="10"></textarea>
    <span class="fk-error"></span>
  </div>
  <button class="fk-btn fk-btn-primary" type="submit">Gönder</button>
</form>
<?php require __DIR__ . '/../inc/footer.php'; ?>

03PHP: form işleme#

PHPapi/iletisim.php
<?php
require __DIR__ . '/../inc/config.php';
require __DIR__ . '/../config/db.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
  http_response_code(403);
  exit('Geçersiz istek.');
}

$ad    = trim($_POST['ad'] ?? '');
$mail  = trim($_POST['mail'] ?? '');
$mesaj = trim($_POST['mesaj'] ?? '');

if (mb_strlen($ad) < 2 || !filter_var($mail, FILTER_VALIDATE_EMAIL) || mb_strlen($mesaj) < 10) {
  flash('Lütfen alanları kontrol edin.', 'danger');
  header('Location: /iletisim/');
  exit;
}

$stmt = db()->prepare('INSERT INTO messages (channel, name, email, body, ip) VALUES (?, ?, ?, ?, ?)');
$stmt->execute(['contact', $ad, $mail, $mesaj, $_SERVER['REMOTE_ADDR'] ?? '']);

flash('Mesajın iletildi, teşekkürler!');
header('Location: /iletisim/');
PHPDosya yükleme (özet)
<?php
$f = $_FILES['dosya'] ?? null;
if (!$f || $f['error'] !== UPLOAD_ERR_OK || $f['size'] > 150 * 1024 * 1024) exit('Geçersiz dosya.');
$ext  = strtolower(pathinfo($f['name'], PATHINFO_EXTENSION));
$izin = ['pdf', 'zip', 'png', 'jpg', 'jpeg', 'docx', 'xlsx'];
if (!in_array($ext, $izin, true)) exit('Bu dosya türü kabul edilmiyor.');
$hedef = __DIR__ . '/../storage/' . bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($f['tmp_name'], $hedef);

storage klasörünü dışarı kapatYüklenen dosyaların durduğu klasör web’den doğrudan erişilebilir olmamalı. Mümkünse public klasörün dışına koy ya da içine Require all denied içeren bir .htaccess yerleştir.

04Node.js / Express#

Terminal
npm init -y
npm install express
JavaScriptindex.js
const express = require('express');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
app.use(express.json());

app.post('/api/iletisim', (req, res) => {
  const { ad, mail, mesaj } = req.body || {};
  if (!ad || !mail || !mesaj) return res.status(400).json({ ok: false, error: 'Eksik alan' });
  res.json({ ok: true });
});

app.use((req, res) => res.status(404).sendFile(path.join(__dirname, 'public', '404.html')));

if (require.main === module) app.listen(process.env.PORT || 3000);
module.exports = app;

05Vercel#

Statik bir fkdev UI sitesi Vercel’e ek ayar olmadan yüklenir. Express uygulaması için:

JSONvercel.json
{
  "version": 2,
  "builds": [{ "src": "index.js", "use": "@vercel/node" }],
  "routes": [{ "src": "/(.*)", "dest": "/index.js" }]
}

Vercel sınırlarıVercel fonksiyonlarında istek gövdesi 4,5 MB ile sınırlıdır; büyük dosya yüklemeleri doğrudan bir depolamaya (ör. Vercel Blob) yapılmalı. Bellekte tutulan oturumlar da kopyalar arasında paylaşılmaz; oturumu veritabanında ya da imzalı çerezde tut.

06React ve Next.js#

CSS’i bir kez global olarak içe aktar, class yerine className kullan. Sayfa değiştikçe yeni öğeleri başlatmak için FK.init() çağır.

JavaScriptapp/layout.jsx
import Script from 'next/script';
import './fkdev-ui.css';

export default function RootLayout({ children }) {
  return (
    <html lang="tr">
      <body className="fk-body fk-atmosphere">
        {children}
        <Script src="/fkdev-ui.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}
JavaScriptcomponents/Kart.jsx
import { useEffect, useRef } from 'react';

export function Kart({ baslik, children }) {
  const ref = useRef(null);
  useEffect(() => { window.FK && window.FK.init(ref.current); }, []);
  return (
    <article ref={ref} className="fk-card fk-reveal">
      <h3 className="fk-card-title">{baslik}</h3>
      <p>{children}</p>
    </article>
  );
}

07Vue#

JavaScriptBileşen içinde
import { onMounted, ref } from 'vue';
const kok = ref(null);
onMounted(() => window.FK && window.FK.init(kok.value));

08Laravel (Blade)#

PHPresources/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="tr">
<head>
  <meta charset="UTF-8">
  <title>@yield('title', 'Sitem')</title>
  <link rel="stylesheet" href="{{ asset('fkdev/fkdev-ui.css') }}">
  <script src="{{ asset('fkdev/fkdev-ui.js') }}" defer></script>
</head>
<body class="fk-body fk-atmosphere">
  @include('partials.navbar')
  <main class="fk-wrap">@yield('content')</main>
  @if (session('ok'))
    <script>addEventListener('DOMContentLoaded', () => FK.toast(@json(session('ok')), 'success'));</script>
  @endif
</body>
</html>

09WordPress#

PHPfunctions.php
<?php
add_action('wp_enqueue_scripts', function () {
  wp_enqueue_style('fkdev-fonts', 'https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500&family=Geist:wght@300..700&family=Instrument+Serif:ital@0;1&display=swap', [], null);
  wp_enqueue_style('fkdev-ui', 'https://ui.fkdev.sbs/v2.4.1/fkdev-ui.min.css', [], '2.4.1');
  wp_enqueue_script('fkdev-ui', 'https://ui.fkdev.sbs/v2.4.1/fkdev-ui.min.js', [], '2.4.1', ['strategy' => 'defer']);
});
add_filter('body_class', fn($c) => array_merge($c, ['fk-body', 'fk-atmosphere']));

10Tailwind ve Bootstrap ile birlikte#

Sınıflar fk- önekli olduğu için çakışmaz. Tailwind’in renklerini fkdev değişkenlerine bağlayarak tek bir tasarım dili kullanabilirsin:

JavaScripttailwind.config.js
export default {
  theme: {
    extend: {
      colors: { ink: 'var(--fk-ink)', soft: 'var(--fk-soft)', surface: 'var(--fk-surface)' },
      fontFamily: { serif: ['Instrument Serif', 'serif'], sans: ['Geist', 'sans-serif'] }
    }
  }
};

Dokümantasyonda ara

Esc
↑ ↓ gezinEnter aç