# uikit Shared library for BI next.js projects. --- ## Install ```bash yarn add git+https://git.d.aiengines.ir/bi/uikit.git ``` --- ## Update ```bash yarn upgrade uikit ``` or ```bash rm -rf .next rm -rf node_modules/.vite rm -rf node_modules/.cache rm -rf node_modules/uikit yarn upgrade uikit ``` --- # imports | مسیر | کاربرد | dependency اضافی | | ------------------ | ------------------------------------- | --------------------------------------- | | `uikit` | utility functions | - | | `uikit/utils` | utility functions | - | | `uikit/pagination` | pagination | Chakra + React | | `uikit/layout` | Header, Sidebar | Chakra + React | | `uikit/core` | API، BiProvider، Keycloak، Permission | Chakra + React Query + Axios + Keycloak + Query Params | | `uikit/table` | DataTable | Chakra + TanStack Table + Pagination | | `uikit/remote/ui` | Remote UI components مثل `GBadge` | Chakra + React + remote static file | --- # utils ```js import { toFaDigits, toEnDigits, toFaNumber } from "uikit/utils"; toFaDigits(123456); toEnDigits("۱۲۳۴۵۶"); toFaNumber(123456); ``` or ```js import { toFaDigits } from "uikit"; ``` --- # pagination required dependencies ```bash yarn add @chakra-ui/react@2 yarn add @emotion/react@11 yarn add @emotion/styled@11 yarn add chakra-react-select@5 yarn add react-select@5 yarn add framer-motion@10 ``` using ```jsx import { Pagination, LightPagination, SimplePagination } from "uikit/pagination"; function Page() { return ( console.log(page)} /> ); } ``` --- # Header required dependencies: ```bash yarn add @chakra-ui/react@2 yarn add @emotion/react@11 yarn add @emotion/styled@11 yarn add framer-motion@10 ``` using: ```jsx import { Header } from "uikit/layout"; function AppHeader() { return (
عنوان سامانه توضیح کوتاه سامانه
); } ``` --- # Sidebar required dependencies: ```bash yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion yarn add @tanstack/react-query axios keycloak-js yarn add next-query-params@^5.1.0 use-query-params@^2.2.2 ``` `Sidebar` همان UI ناوبری سامانه را برای دسکتاپ و موبایل ارائه می‌دهد. در دسکتاپ به صورت نوار آیکون کناری و در موبایل به صورت bottom navigation + drawer «بیشتر» نمایش داده می‌شود. آیتم‌ها به صورت config به کامپوننت داده می‌شوند: ```jsx import { SlUserFollowing } from "react-icons/sl"; const navigationItems = [ { title: "آنالیز شبکه متعارف - فالوور/فالووینگ", mobileTitle: "ارتباط کاربران", icon: SlUserFollowing, path: "/user-connections/analyze", perm: ["analyze"], }, ]; ``` نمونه استفاده در Next.js Pages Router: ```jsx import NextLink from "next/link"; import { useRouter } from "next/router"; import { Sidebar } from "uikit/layout"; function AppSidebar() { const router = useRouter(); return ( ); } ``` `Sidebar` باید داخل `BiProvider` استفاده شود. Permission هر آیتم از `perm` توسط خود UI Kit بررسی می‌شود و خروج با Keycloak توسط خود UI Kit انجام می‌شود. سامانه مقصد نیازی به `usePermission` یا `logoutKeycloak` برای Sidebar ندارد. کنترل دسترسی کلی `panel_access` عمداً داخل Sidebar انجام نمی‌شود تا navigation هنگام render باعث redirect و لغو route در Next.js نشود. اگر سامانه به این guard نیاز دارد، از `PanelAccessGuard` موجود در `uikit/core` در سطح Layout/App استفاده کنید. `aliases` نیز پشتیبانی می‌شود و برای تشخیص آیتم active استفاده می‌شود: ```jsx { title: "آنالیز شبکه های اجتماعی خارجی", mobileTitle: "شبکه خارجی", icon: TbAnalyze, path: "/posts/final_analyze", aliases: ["/posts/analyze"], perm: ["analyze"], } ``` اگر `perm` آرایه باشد، همه permissionهای داخل آرایه برای فعال بودن آیتم لازم هستند. آیتم بدون `perm` محدودیت permission ندارد. --- # Core required dependencies: ```bash yarn add @chakra-ui/react@^2 yarn add @emotion/react@^11 yarn add @emotion/styled@^11 yarn add framer-motion@^12 yarn add @tanstack/react-query@^5 yarn add axios@^1 yarn add keycloak-js yarn add next-query-params@^5.1.0 yarn add use-query-params@^2.2.2 ``` --- ## BiProvider ```jsx import { BiProvider } from "uikit/core"; export default function App() { return ( ); } ``` --- ## BiProvider props | Prop | | ----------------------------- | | `apiBaseUrl` | | `keycloakClientId` | | `permissionClientId` | | `loading` | | `updateChecker` | | `updateCheckerProps` | | `keycloakUrl` | | `keycloakRealm` | | `permissionUrl` | | `keycloakEnabled` | | `permissionEnabled` | | `remoteUiEnabled` | | `remoteUiUrl` | | `remoteUiChakraProviderProps` | --- ## BiProvider defaults ```js keycloakUrl = "https://auth.ibagher.ir"; keycloakRealm = "bi"; permissionUrl = "https://api.d.aiengines.ir/user_api/v1/permissions/list"; updateChecker = false; remoteUiEnabled = true; remoteUiUrl = "https://uikit.d.aiengines.ir/GBadge.js"; ``` --- ## BiProvider with Remote UI `BiProvider` به صورت خودکار dependencyهای لازم برای کامپوننت‌های `remote/ui` را setup می‌کند. پس اگر سامانه مقصد از `BiProvider` استفاده می‌کند، برای استفاده از `GBadge` کار اضافه‌ای لازم نیست. ```jsx import { BiProvider } from "uikit/core"; import { GBadge } from "uikit/remote/ui"; export default function App() { return ( فعال ); } ``` در این حالت `GBadge` همچنان از Chakra استفاده می‌کند، ولی فایل remote آن، یعنی `GBadge.js`، خودش Chakra و React و font را داخل bundle نمی‌آورد. dependencyهای لازم از خود سامانه مقصد گرفته می‌شوند. --- ## Change Remote UI URL اگر فایل remote روی آدرس دیگری deploy شده باشد: ```jsx import { BiProvider } from "uikit/core"; export default function App() { return ( ); } ``` --- ## Disable Remote UI setup in BiProvider اگر نمی‌خواهید `BiProvider` به صورت خودکار remote ui را setup کند: ```jsx import { BiProvider } from "uikit/core"; export default function App() { return ( ); } ``` --- To enable `updateChecker` you should have a script `write-version.js` in the root of the project and in the `package.json` of the target project, run before the build: ```json { "scripts": { "prebuild": "node write-version.js", "build": "next build" } } ``` --- # API ```js import { api, fetcher, configureApi, getApi } from "uikit/core"; ``` ```js import { api } from "uikit/core"; const response = await api.get("/users"); ``` or ```js import { fetcher } from "uikit/core"; const data = await fetcher("/users"); ``` --- # createApi ```js import { createApi } from "uikit/core"; export const sampleApi = createApi({ key: "sample", url: "/...", actions: { create: { method: "post", url: "/...", }, update: { method: "patch", url: (id) => `/.../${id}`, }, delete: { method: "delete", url: (id) => `/.../${id}`, }, confirm: { method: "post", url: "/...", }, updateImage: { method: "post", url: (id) => `/.../${id}/image`, body: ({ formData }) => formData, }, }, }); ``` --- # Keycloak ```js import { getKeycloakToken, loginKeycloak, logoutKeycloak, } from "uikit/core"; const token = getKeycloakToken(); loginKeycloak(); logoutKeycloak({ redirectUri: window.location.origin, }); ``` --- # Permission ```jsx import { Can, usePermission } from "uikit/core"; function Page() { const { can, cannot, permissions } = usePermission(); return ( <> {can("users:list") &&
لیست کاربران
} ); } ``` --- # Remote UI `remote/ui` برای کامپوننت‌هایی است که باید بدون build مجدد سامانه مقصد، از طریق فایل remote به‌روزرسانی شوند. مثلاً `GBadge` از این مسیر استفاده می‌شود: ```jsx import { GBadge } from "uikit/remote/ui"; function Page() { return ( فعال ); } ``` --- ## GBadge required dependencies: ```bash yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion ``` using: ```jsx import { GBadge } from "uikit/remote/ui"; function Page({ item }) { return ( {item} ); } ``` `GBadge` از Chakra `Badge` استفاده می‌کند. اما برای کم شدن حجم فایل remote، این موارد داخل `GBadge.js` bundle نمی‌شوند: ```txt React ReactDOM Chakra Emotion theme Fonts font files ``` این موارد باید در سامانه مقصد وجود داشته باشند. --- ## GBadge with BiProvider اگر سامانه مقصد از `BiProvider` استفاده می‌کند، نیازی به setup دستی نیست: ```jsx import { BiProvider } from "uikit/core"; import { GBadge } from "uikit/remote/ui"; function App() { return ( فعال ); } ``` --- ## GBadge without BiProvider اگر سامانه مقصد از `BiProvider` استفاده نمی‌کند، دو حالت وجود دارد. ### حالت ساده اگر theme خاصی ندارید، فقط استفاده از `GBadge` کافی است: ```jsx import { GBadge } from "uikit/remote/ui"; function Page() { return ( فعال ); } ``` در این حالت wrapper مربوط به `GBadge` خودش dependencyهای لازم را setup می‌کند. --- ### حالت با theme اختصاصی اگر سامانه مقصد theme اختصاصی Chakra دارد، بهتر است در entry اصلی پروژه یک بار setup انجام شود. مثلاً در `main.jsx`، `App.jsx` یا `_app.jsx`: ```jsx import React from "react"; import { createRoot } from "react-dom/client"; import { ChakraProvider } from "@chakra-ui/react"; import { setupUikitRemoteDeps } from "uikit/remote/ui"; import { theme } from "./theme"; import App from "./App"; setupUikitRemoteDeps({ theme, }); createRoot(document.getElementById("root")).render( , ); ``` برای Next.js: ```jsx import { ChakraProvider } from "@chakra-ui/react"; import { setupUikitRemoteDeps } from "uikit/remote/ui"; import { theme } from "@/theme"; setupUikitRemoteDeps({ theme, }); export default function App({ Component, pageProps }) { return ( ); } ``` --- ## Remote URL without BiProvider اگر سامانه مقصد از `BiProvider` استفاده نمی‌کند و آدرس فایل remote متفاوت است: ```jsx import { setupUikitRemoteDeps } from "uikit/remote/ui"; import { theme } from "./theme"; setupUikitRemoteDeps({ theme, remoteUrl: "https://static.example.com/uikit/GBadge.js", }); ``` یا مستقیم روی خود کامپوننت: ```jsx import { GBadge } from "uikit/remote/ui"; function Page() { return ( فعال ); } ``` --- ## Font فونت داخل فایل remote باندل نمی‌شود. اگر سامانه مقصد از `BiProvider` استفاده می‌کند، فونت و theme اصلی از همان `BiProvider` می‌آید. اگر سامانه مقصد `BiProvider` ندارد، فونت را در خود سامانه مقصد ست کنید: ```css @font-face { font-family: Estedad; src: url("/fonts/estedad.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; } :root { --uikit-font-family: Estedad, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } body { font-family: var(--uikit-font-family); } ``` `GBadge` از این مقدار استفاده می‌کند: ```css var(--uikit-font-family, inherit) ``` --- ## Build Remote UI برای ساخت فایل remote: ```bash yarn build:remote ``` خروجی remote: ```txt dist/remote/GBadge.js ``` این فایل باید روی static server یا CDN داخلی deploy شود. مثلاً: ```txt https://uikit.d.aiengines.ir/GBadge.js ``` --- ## Deploy Remote UI بعد از تغییر در `GBadge.remote.jsx` یا کامپوننت‌های remote: ```bash yarn build:remote ``` سپس فایل زیر را deploy کنید: ```txt dist/remote/GBadge.js ``` سامانه‌های مقصد بعد از reload صفحه، نسخه جدید remote را دریافت می‌کنند. اگر cache سمت browser یا CDN فعال است، باید cache فایل remote کنترل شود. --- ## Important Notes اگر از مسیر زیر استفاده شود: ```js import { GBadge } from "uikit/remote/ui"; ``` خود کامپوننت از فایل remote استفاده می‌کند. اما اگر کامپوننتی از مسیر معمولی package import شود، مثل: ```js import { SomeComponent } from "uikit"; ``` یا: ```js import { SomeComponent } from "uikit/table"; ``` آن کامپوننت داخل build سامانه مقصد bundle می‌شود و برای آپدیت شدن نیاز به rebuild یا `yarn upgrade` دارد. پس: ```txt uikit/remote/ui => update by remote file uikit/table => update by package upgrade uikit/core => update by package upgrade uikit/layout => update by package upgrade ``` --- # DataTable required dependencies: ```bash yarn add @tanstack/react-table ``` using: ```jsx import { DataTable } from "uikit/table"; function UsersTable({ data, columns }) { return ( console.log(page)} getRowId={(row) => String(row.id)} enableSelection showIndex > لیست کاربران ); } ``` ## Sidebar.Exit `Sidebar` به‌صورت پیش‌فرض گزینه خروج را نمایش نمی‌دهد. برای فعال‌کردن خروج Keycloak داخلی UI Kit، آن را به‌صورت compound component اضافه کنید: ```jsx ``` عنوان و آیکون خروج قابل تغییر هستند: ```jsx ``` `Sidebar.Exit` از `logoutKeycloak` داخلی UI Kit استفاده می‌کند و callback خروج از سامانه مقصد نیاز ندارد. ### Sidebar active route notes `Sidebar` compares `currentPath` with each item's `path` and `aliases`, ignoring query strings, hashes, and trailing slashes. Use the actual application route as `path`; use `aliases` only for alternate routes that should keep the same item active. Active sidebar items are rendered as non-links to avoid redundant same-route navigations. This is especially useful on pages that synchronize URL query params. --- # Remote AppHeader (self-contained) `AppHeader` یک remote مستقل است و برای فایل remote خودش از `setupUikitRemoteDeps` یا `window.__UIKIT_REMOTE_DEPS__` استفاده نمی‌کند. ```jsx import { AppHeader } from "uikit/remote/ui"; function ExampleAppHeader() { return ( عنوان سامانه توضیحات سامانه ); } ``` `AppHeader` در flow عادی صفحه باقی می‌ماند و عرض کامل می‌گیرد؛ بنابراین اگر داخل `VStack`/`Stack` قرار بگیرد، محتوای بعدی زیر Header قرار نمی‌گیرد. `bg` اختیاری است و رنگ پس‌زمینه پیش‌فرض Header را override می‌کند. `AppHeader.Extra` نیز اختیاری است و لوگو + متن دلخواه می‌گیرد. `AppHeader.Extra` و `AppHeader.Bi` مستقل از هم هستند و می‌توان هرکدام را حذف کرد. اگر هر دو وجود داشته باشند، در انتهای Header ترتیب نمایشی `Extra` سپس `Bi` است. فایل remote از این آدرس بارگذاری می‌شود: ```text https://uikit.d.aiengines.ir/AppHeader.js ``` برای `AppHeader.js` هیچ dependency از host تزریق نمی‌شود. خود remote به صورت Web Component ایزوله با Shadow DOM اجرا می‌شود. تنها `font-family` از سامانه مقصد inherit می‌شود تا AppHeader از همان فونت برنامه استفاده کند؛ سایر استایل‌ها همچنان ایزوله هستند. مسیر React wrapper فقط `react` خود برنامه را برای mount کردن custom element استفاده می‌کند و هیچ وابستگی به Chakra ندارد. ## AppHeader local preview برای دیدن `remote/ui/AppHeader` قبل از انتشار: ```bash yarn install yarn dev ``` سپس آدرس Vite که در ترمینال نمایش داده می‌شود (معمولاً `http://localhost:5173`) را باز کنید. در حالت dev، همان سورس `src/remote/ui/AppHeader/AppHeader.remote.jsx` به‌صورت remote لوکال لود می‌شود. ## Remote AppSidebar `AppSidebar` is an isolated desktop sidebar. Its visual layer lives inside Shadow DOM and does not depend on Chakra, Tailwind, router, permission or authentication packages from the host. The host supplies the menu data and application logic. Prefer the dedicated entry so projects that only need `AppSidebar` do not load the Chakra-oriented remote setup used by older components: ```jsx import { AppSidebar } from "uikit/remote/ui/AppSidebar"; , active: true, onClick: () => navigate("/home"), }, ]} > ``` Permission and routing decisions belong to the host. Filter inaccessible items before passing them, or pass `disabled: true` when the disabled state should remain visible. متن `AppSidebar.Exit` داخل خود UI Kit ثابت و همیشه «خروج» است؛ Host فقط callback و در صورت نیاز `disabled` را پاس می‌دهد. ### AppSidebar responsive behavior On widths below the desktop breakpoint (`62em`), `AppSidebar` renders a fixed bottom navigation bar: - Up to 4 visible items: all items are shown directly; when `AppSidebar.Exit` is enabled, exit is the final direct item and there is no **More** button. - More than 4 visible items: the first 4 items stay in the bottom bar; **More** is added as the fifth slot; item 5 onward plus `AppSidebar.Exit` are rendered in the bottom drawer. - `hidden` items are excluded before this count is calculated. The host remains responsible for route, permission and logout logic. ## Pagination `remote/ui/Pagination` چهار حالت نمایشی دارد و قرارداد props در همه آن‌ها یکسان است: ```txt full compact simple minimal ``` حالت `minimal` فقط نحوه نمایش را ساده‌تر می‌کند. همه variantها API یکسان دارند و `loading` نیز یک prop اختیاری است: ```jsx import { Pagination } from "uikit/remote/ui"; ``` اگر `loading` پاس داده نشود، رفتار Pagination مانند قبل است. - `loading=true` و `totalPages`/`totalCount` هنوز در دسترس نیستند: صفحه جاری `1` است و جای مقادیر نامشخص loader نمایش داده می‌شود. - `loading=true` و totals قبلاً موجودند: totals ثابت می‌مانند و فقط صفحه جاری تا رسیدن پاسخ جدید loader نمایش می‌دهد. - در جابه‌جایی بین صفحات، اگر loader داخل دکمه صفحه جاری نمایش داده شود، spinner سفید است تا روی پس‌زمینه active خوانا بماند. ## Deployed remote demo The production build now also creates a static preview at `dist/remote/index.html`. Because the Docker server already serves `dist/remote` as its document root, opening `https://uikit.d.aiengines.ir/` shows the UI Kit preview instead of a directory listing. The remote module URLs stay unchanged, for example `/AppHeader.js`, `/AppSidebar.js`, `/Pagination.js`, and `/GBadge.js`. ### AppHeader.Extra بدون متن اگر `text` پاس داده نشود، فقط لوگو نمایش داده می‌شود و تصویر تمام ارتفاع قابل‌استفاده‌ی `AppHeader.Extra` را می‌گیرد.