├── .changeset ├── README.md └── config.json ├── .github └── workflows │ ├── pages.yml │ └── test.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── crowdin.yml ├── docs ├── .vitepress │ └── config.ts ├── README.md ├── guide │ ├── apis │ │ ├── app-hooks.md │ │ ├── directives.md │ │ └── util-funcs.md │ ├── essentials │ │ ├── app-state.md │ │ ├── application.md │ │ ├── component.md │ │ ├── conditional.md │ │ ├── context.md │ │ ├── event.md │ │ ├── input.md │ │ ├── list.md │ │ ├── lowlevel.md │ │ ├── rendering-basics.md │ │ └── view.md │ ├── introduction.md │ ├── quick-start.md │ └── why.md ├── helpers │ ├── ReplLoading.vue │ ├── chip.vue │ ├── kind.vue │ ├── loader.ts │ ├── optional.vue │ └── utils.ts ├── index.md ├── misc │ ├── contact.md │ └── playground.md ├── package.json ├── public │ ├── favicon.ico │ ├── logo.svg │ └── media │ │ ├── app-states.png │ │ └── create-refina.png ├── snippets │ ├── async-fetch.vue │ ├── basic-input.vue │ ├── conditional-rendering.vue │ ├── counter.vue │ ├── event-handling.vue │ ├── for-times.vue │ ├── input-event.vue │ ├── list-rendering.vue │ ├── lowlevel.vue │ ├── now.vue │ ├── run-refina.vue │ └── static-page.vue ├── std-comps │ ├── button.md │ ├── checkbox.md │ ├── dialog.md │ ├── introduction.md │ ├── list.md │ ├── nav-rail.md │ ├── radio-group.md │ ├── slider.md │ ├── switch.md │ ├── table.md │ ├── tabs.md │ └── text-field.md ├── tutorial │ ├── TutorialRepl.vue │ ├── index.md │ ├── src │ │ ├── step-1 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ └── description.md │ │ ├── step-10 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ ├── description.md │ │ │ └── import-map.json │ │ ├── step-2 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ ├── _hint │ │ │ │ └── App │ │ │ │ │ └── App.ts │ │ │ └── description.md │ │ ├── step-3 │ │ │ ├── App │ │ │ │ ├── App.ts │ │ │ │ └── styles.css │ │ │ ├── _hint │ │ │ │ └── App │ │ │ │ │ ├── App.ts │ │ │ │ │ └── styles.css │ │ │ └── description.md │ │ ├── step-4 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ ├── _hint │ │ │ │ └── App │ │ │ │ │ └── App.ts │ │ │ └── description.md │ │ ├── step-5 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ ├── _hint │ │ │ │ └── App │ │ │ │ │ └── App.ts │ │ │ └── description.md │ │ ├── step-6 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ ├── _hint │ │ │ │ └── App │ │ │ │ │ └── App.ts │ │ │ └── description.md │ │ ├── step-7 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ ├── _hint │ │ │ │ └── App │ │ │ │ │ └── App.ts │ │ │ └── description.md │ │ ├── step-8 │ │ │ ├── App │ │ │ │ └── App.ts │ │ │ ├── _hint │ │ │ │ └── App │ │ │ │ │ ├── App.ts │ │ │ │ │ └── PartView.ts │ │ │ └── description.md │ │ └── step-9 │ │ │ ├── App │ │ │ └── App.ts │ │ │ ├── _hint │ │ │ └── App │ │ │ │ └── App.ts │ │ │ └── description.md │ └── tutorial.data.ts └── zh │ ├── README.md │ ├── guide │ ├── apis │ │ ├── app-hooks.md │ │ ├── directives.md │ │ └── util-funcs.md │ ├── essentials │ │ ├── app-state.md │ │ ├── application.md │ │ ├── component.md │ │ ├── conditional.md │ │ ├── context.md │ │ ├── event.md │ │ ├── input.md │ │ ├── list.md │ │ ├── lowlevel.md │ │ ├── rendering-basics.md │ │ └── view.md │ ├── introduction.md │ ├── quick-start.md │ └── why.md │ ├── index.md │ ├── misc │ ├── contact.md │ └── playground.md │ ├── std-comps │ ├── button.md │ ├── checkbox.md │ ├── dialog.md │ ├── introduction.md │ ├── list.md │ ├── nav-rail.md │ ├── radio-group.md │ ├── slider.md │ ├── switch.md │ ├── table.md │ ├── tabs.md │ └── text-field.md │ └── tutorial │ ├── TutorialRepl.vue │ ├── index.md │ ├── src │ ├── step-1 │ │ ├── App │ │ │ └── App.ts │ │ └── description.md │ ├── step-10 │ │ ├── App │ │ │ └── App.ts │ │ ├── description.md │ │ └── import-map.json │ ├── step-2 │ │ ├── App │ │ │ └── App.ts │ │ ├── _hint │ │ │ └── App │ │ │ │ └── App.ts │ │ └── description.md │ ├── step-3 │ │ ├── App │ │ │ ├── App.ts │ │ │ └── styles.css │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── App.ts │ │ │ │ └── styles.css │ │ └── description.md │ ├── step-4 │ │ ├── App │ │ │ └── App.ts │ │ ├── _hint │ │ │ └── App │ │ │ │ └── App.ts │ │ └── description.md │ ├── step-5 │ │ ├── App │ │ │ └── App.ts │ │ ├── _hint │ │ │ └── App │ │ │ │ └── App.ts │ │ └── description.md │ ├── step-6 │ │ ├── App │ │ │ └── App.ts │ │ ├── _hint │ │ │ └── App │ │ │ │ └── App.ts │ │ └── description.md │ ├── step-7 │ │ ├── App │ │ │ └── App.ts │ │ ├── _hint │ │ │ └── App │ │ │ │ └── App.ts │ │ └── description.md │ ├── step-8 │ │ ├── App │ │ │ └── App.ts │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── App.ts │ │ │ │ └── PartView.ts │ │ └── description.md │ └── step-9 │ │ ├── App │ │ └── App.ts │ │ ├── _hint │ │ └── App │ │ │ └── App.ts │ │ └── description.md │ └── tutorial.data.ts ├── package.json ├── packages ├── basic-components │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── a.ts │ │ │ ├── br.ts │ │ │ ├── button.ts │ │ │ ├── checkbox.ts │ │ │ ├── div.ts │ │ │ ├── h.ts │ │ │ ├── img.ts │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── label.ts │ │ │ ├── li.ts │ │ │ ├── ol.ts │ │ │ ├── p.ts │ │ │ ├── span.ts │ │ │ ├── table.ts │ │ │ ├── textarea.ts │ │ │ └── ul.ts │ │ └── index.ts │ ├── tsconfig.json │ └── vite.config.ts ├── bundles │ ├── README.md │ ├── package.json │ └── vite.config.ts ├── core │ ├── README.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── app.ts │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ └── plugin.ts │ │ ├── component │ │ │ ├── component.ts │ │ │ ├── index.ts │ │ │ └── trigger.ts │ │ ├── constants.ts │ │ ├── context │ │ │ ├── base.ts │ │ │ ├── index.ts │ │ │ ├── recv.ts │ │ │ └── update.ts │ │ ├── data │ │ │ ├── index.ts │ │ │ ├── model.ts │ │ │ ├── propModel.ts │ │ │ └── ref.ts │ │ ├── dom │ │ │ ├── body.ts │ │ │ ├── content.ts │ │ │ ├── element.ts │ │ │ ├── index.ts │ │ │ ├── node.ts │ │ │ ├── portal.ts │ │ │ ├── root.ts │ │ │ ├── text.ts │ │ │ ├── view.ts │ │ │ └── window.ts │ │ ├── index.ts │ │ ├── patch │ │ │ ├── index.ts │ │ │ ├── parser.ts │ │ │ └── patch.ts │ │ └── prelude │ │ │ ├── await.ts │ │ │ ├── documentTitle.ts │ │ │ ├── embed.ts │ │ │ ├── index.ts │ │ │ ├── loop.ts │ │ │ ├── portal.ts │ │ │ ├── provide.ts │ │ │ ├── timing.ts │ │ │ └── useModel.ts │ ├── tsconfig.json │ └── vite.config.ts ├── creator │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── templates │ │ │ ├── app │ │ │ │ ├── basics+mdui.ts │ │ │ │ ├── basics.ts │ │ │ │ ├── intrinsic.ts │ │ │ │ └── mdui.ts │ │ │ ├── extensions.ts │ │ │ ├── gitignore.ts │ │ │ ├── html │ │ │ │ ├── base.ts │ │ │ │ └── mdui.ts │ │ │ ├── postcssConfig.ts │ │ │ ├── prettierrc.ts │ │ │ ├── readme.ts │ │ │ ├── settings.ts │ │ │ ├── styles.ts │ │ │ ├── tailwindConfig.ts │ │ │ ├── tsconfig.ts │ │ │ └── viteConfig.ts │ │ └── utils │ │ │ ├── banner.ts │ │ │ ├── pkgManager.ts │ │ │ └── pkgName.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── examples │ ├── fluentui │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ └── app.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── gallery │ │ ├── index.html │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src │ │ │ ├── app.ts │ │ │ └── tailwind.css │ │ ├── tailwind.config.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── gh-login │ │ ├── assets │ │ │ └── github-mark-white.svg │ │ ├── index.html │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src │ │ │ ├── app.ts │ │ │ └── styles.css │ │ ├── tailwind.config.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── mdui │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ ├── app.ts │ │ └── styles.css │ │ ├── tsconfig.json │ │ └── vite.config.ts ├── fluentui-icons │ ├── README.md │ ├── generator.ts │ ├── package.json │ └── tsconfig.json ├── fluentui │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── accordion │ │ │ │ ├── accordion │ │ │ │ │ ├── header.styles.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── item.styles.ts │ │ │ │ ├── accordionPanel │ │ │ │ │ ├── index.ts │ │ │ │ │ └── styles.ts │ │ │ │ └── index.ts │ │ │ ├── avatar │ │ │ │ ├── colors.ts │ │ │ │ ├── getInitials.ts │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ ├── breadcrumb │ │ │ │ ├── button.styles.ts │ │ │ │ ├── divider.styles.ts │ │ │ │ ├── index.ts │ │ │ │ ├── item.styles.ts │ │ │ │ └── styles.ts │ │ │ ├── button │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── dialog │ │ │ │ ├── constants.ts │ │ │ │ ├── dialog │ │ │ │ │ └── index.ts │ │ │ │ ├── dialogBody │ │ │ │ │ ├── actions.styles.ts │ │ │ │ │ ├── body.styles.ts │ │ │ │ │ ├── content.styles.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── title.styles.ts │ │ │ │ ├── dialogSurface │ │ │ │ │ ├── index.ts │ │ │ │ │ └── styles.ts │ │ │ │ └── index.ts │ │ │ ├── divider │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ ├── dropdown │ │ │ │ ├── index.ts │ │ │ │ ├── listbox.styles.ts │ │ │ │ ├── option.styles.ts │ │ │ │ ├── styles.ts │ │ │ │ ├── tokens.ts │ │ │ │ └── types.ts │ │ │ ├── field │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ ├── label │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── popover │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── portal │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── progressBar │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ ├── slider │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── tab │ │ │ │ ├── index.ts │ │ │ │ ├── tab │ │ │ │ │ ├── index.ts │ │ │ │ │ └── styles.ts │ │ │ │ ├── tabList │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── indicator.styles.ts │ │ │ │ │ └── styles.ts │ │ │ │ └── tabs │ │ │ │ │ ├── index.ts │ │ │ │ │ └── styles.ts │ │ │ ├── textarea │ │ │ │ ├── index.ts │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ └── tooltip │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ ├── focus │ │ │ ├── constants.ts │ │ │ ├── createCustomFocusIndicatorStyle.ts │ │ │ ├── createFocusOutlineStyle.ts │ │ │ ├── focusVisiblePolyfill.ts │ │ │ ├── focusWithinPolyfill.ts │ │ │ ├── index.ts │ │ │ └── utils │ │ │ │ └── isHTMLElement.ts │ │ ├── index.ts │ │ └── positioning │ │ │ ├── constants.ts │ │ │ ├── createArrowStyles.ts │ │ │ ├── createSlideStyles.ts │ │ │ ├── index.ts │ │ │ ├── middleware │ │ │ ├── flip.ts │ │ │ ├── index.ts │ │ │ └── offset.ts │ │ │ ├── types.ts │ │ │ ├── usePositioning.ts │ │ │ └── utils │ │ │ ├── fromFloatingUIPlacement.ts │ │ │ ├── getFloatingUIOffset.ts │ │ │ ├── index.ts │ │ │ ├── mergeArrowOffset.ts │ │ │ ├── parseFloatingUIPlacement.ts │ │ │ ├── resolvePositioningShorthand.ts │ │ │ └── toFloatingUIPlacement.ts │ ├── tsconfig.json │ └── vite.config.ts ├── griffel │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── hmr │ ├── package.json │ ├── src │ │ ├── compile.ts │ │ ├── constants.ts │ │ ├── cutSrc.ts │ │ ├── getDecls.ts │ │ ├── getDeps.ts │ │ ├── index.ts │ │ ├── parser.ts │ │ ├── wrapLocals.ts │ │ └── wrapMain.ts │ └── tsconfig.json ├── mdui │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── avatar.ts │ │ │ ├── badge.ts │ │ │ ├── bottomAppBar.ts │ │ │ ├── button.ts │ │ │ ├── checkbox.ts │ │ │ ├── chip.ts │ │ │ ├── circularProgress.ts │ │ │ ├── collapse.ts │ │ │ ├── dialog.ts │ │ │ ├── divider.ts │ │ │ ├── fab.ts │ │ │ ├── icon.ts │ │ │ ├── iconButton.ts │ │ │ ├── index.ts │ │ │ ├── layout.ts │ │ │ ├── layoutMain.ts │ │ │ ├── linearProgress.ts │ │ │ ├── list.ts │ │ │ ├── navBar.ts │ │ │ ├── navDrawer.ts │ │ │ ├── navRail.ts │ │ │ ├── prose.ts │ │ │ ├── radioGroup.ts │ │ │ ├── rangeSlider.ts │ │ │ ├── segmentedButton.ts │ │ │ ├── select.ts │ │ │ ├── slider.ts │ │ │ ├── switch.ts │ │ │ ├── table.ts │ │ │ ├── tabs.ts │ │ │ ├── textField.ts │ │ │ ├── tooltip.ts │ │ │ └── topAppBar.ts │ │ ├── index.ts │ │ ├── metadata │ │ │ ├── events.ts │ │ │ └── tags.ts │ │ └── theme │ │ │ ├── index.ts │ │ │ ├── useColorScheme.ts │ │ │ └── useTheme.ts │ ├── styles.css │ ├── tsconfig.json │ └── vite.config.ts ├── router │ ├── package.json │ ├── src │ │ ├── beforeRoute.ts │ │ ├── index.ts │ │ ├── route.ts │ │ ├── router.ts │ │ └── utils.ts │ └── tsconfig.json ├── tests │ ├── core │ │ ├── __snapshots__ │ │ │ └── app.r.test.ts.snap │ │ └── app.r.test.ts │ ├── package.json │ ├── transformer │ │ ├── __snapshots__ │ │ │ └── transformer.test.ts.snap │ │ ├── fragment.test.ts │ │ └── transformer.test.ts │ ├── tsconfig.json │ └── utils │ │ └── index.ts ├── transformer │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── patterns.ts │ └── tsconfig.json ├── tsconfig │ ├── package.json │ └── tsconfig.json └── vite-plugin │ ├── README.md │ ├── package.json │ ├── src │ ├── app.ts │ ├── hmr.ts │ ├── index.ts │ ├── lib.ts │ ├── options.ts │ └── transformer.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── vitest.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | - next 9 | 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Setup Node.js environment 20 | uses: actions/setup-node@v4.0.0 21 | with: 22 | node-version: 20 23 | - name: Setup pnpm 24 | uses: pnpm/action-setup@v2.4.0 25 | with: 26 | version: 8 27 | - name: Install and run tests 28 | run: pnpm it 29 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | docs/zh 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "zixuanchen.vitest-explorer", 4 | "ryanluker.vscode-coverage-gutters", 5 | "esbenp.prettier-vscode", 6 | "bradlc.vscode-tailwindcss" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.quickSuggestions": { 3 | "strings": "on" 4 | }, 5 | "tailwindCSS.experimental.classRegex": [ 6 | ["\\$cls`([\\s\\S]*?)`", "(\\S+)"], 7 | ["\\$cls\\(([^\\)]*)\\)", "\\\"(.*?)\\\""], 8 | ["\\$cls\\(([^\\)]*)\\)", "`(.*?)`"], 9 | ["addCls\\(([^\\)]*)\\)", "\\\"(.*?)\\\""], 10 | ["addCls\\(([^\\)]*)\\)", "`(.*?)`"], 11 | ["\\$clsFunc`([\\s\\S]*?)`", "(\\S+)"], 12 | ["\\$clsFunc\\(([^\\)]*)\\)", "\\\"(.*?)\\\""], 13 | ["\\$clsStr`([\\s\\S]*?)`", "(\\S+)"], 14 | ["\\$clsStr\\(([^\\)]*)\\)", "\\\"(.*?)\\\""] 15 | ], 16 | "editor.guides.bracketPairs": "active", 17 | "vitest.commandLine": "pnpm exec vitest --coverage", 18 | "editor.defaultFormatter": "esbenp.prettier-vscode", 19 | "grammarly.selectors": [ 20 | { 21 | "language": "markdown", 22 | "scheme": "file", 23 | "pattern": "docs/tutorial/src/*" 24 | }, 25 | { 26 | "language": "markdown", 27 | "scheme": "file" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 _Kerman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /docs/**/*.md 3 | ignore: 4 | - /docs/zh 5 | - /docs/index.md 6 | - /docs/tutorial 7 | - /docs/README.md 8 | translation: /docs/%two_letters_code%/**/%original_file_name% 9 | - source: /docs/tutorial/**/* 10 | translation: /docs/%two_letters_code%/tutorial/**/%original_file_name% 11 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Refina.js Docs 2 | 3 | Visit the docs at https://refina.vercel.app/ and https://refinajs.github.io/refina. 4 | 5 | Some of the code and content are borrowed from [Vue.js docs](https://github.com/vuejs/docs). Thanks to their great work! 6 | -------------------------------------------------------------------------------- /docs/guide/essentials/conditional.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Conditional Rendering 6 | 7 | Just like JSX, you can use the `if-else` statement to conditionally render components. 8 | 9 | ```ts 10 | let count = 0; 11 | 12 | $app([Basics], _ => { 13 | _.p(`Count is: ${count}`); 14 | 15 | _.button(`Add`) && count++; 16 | 17 | if (count > 0) { 18 | _.button("Reset") && (count = 0); 19 | } 20 | }); 21 | ``` 22 | 23 | **Result** 24 | 25 | 26 | 27 | :::tip 28 | 29 | You can also use the `&&` operator to conditionally render components: 30 | 31 | ```ts 32 | input.length > 0 && _.button("Clear"); 33 | ``` 34 | 35 | ::: 36 | -------------------------------------------------------------------------------- /docs/helpers/chip.vue: -------------------------------------------------------------------------------- 1 | 6 | 16 | 25 | -------------------------------------------------------------------------------- /docs/helpers/kind.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /docs/helpers/optional.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /docs/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import { onBeforeUnmount } from "vue"; 2 | 3 | export type ExampleData = { 4 | [key: string]: string | Record; 5 | } & { 6 | "import-map.json"?: string; 7 | _hint?: ExampleData; 8 | }; 9 | 10 | function forEachComponent( 11 | raw: ExampleData, 12 | files: Record, 13 | cb: (filename: string, file: Record) => void, 14 | ) { 15 | for (const filename in raw) { 16 | const content = raw[filename]; 17 | if ( 18 | filename === "description.txt" || 19 | filename === "description.md" || 20 | filename === "_hint" 21 | ) { 22 | continue; 23 | } else if (typeof content === "string") { 24 | files[filename] = content; 25 | } else { 26 | cb(filename, content); 27 | } 28 | } 29 | } 30 | 31 | export function resolveSFCExample(raw: ExampleData) { 32 | const files: Record = {}; 33 | forEachComponent(raw, files, (filename, content) => { 34 | Object.assign(files, content); 35 | }); 36 | return files; 37 | } 38 | 39 | export function onHashChange(cb: () => void) { 40 | window.addEventListener("hashchange", cb); 41 | onBeforeUnmount(() => { 42 | window.removeEventListener("hashchange", cb); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /docs/misc/contact.md: -------------------------------------------------------------------------------- 1 | # Contact Us 2 | 3 | ## The Author 4 | 5 | - **Email**: [kermanx@qq.com](mailto:kermanx@qq.com) 6 | - **GitHub**: [KermanX](https://github.com/KermanX) 7 | 8 | ## The QQ Group 9 | 10 | - **Group Number**: 488240549 11 | 12 | ## The Discord Server 13 | 14 | - **Server Link**: [Join Refina.js](https://discord.gg/2hjDhfpgzK) 15 | 16 | > The Discord server isn't very active. If you have any questions, please contact via email or QQ group. 17 | -------------------------------------------------------------------------------- /docs/misc/playground.md: -------------------------------------------------------------------------------- 1 | 3 | 4 | 13 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/docs", 3 | "version": "0.0.0", 4 | "description": "The documentation site for Refina.", 5 | "keywords": [ 6 | "refina", 7 | "docs" 8 | ], 9 | "type": "module", 10 | "scripts": { 11 | "dev": "vitepress dev", 12 | "build": "vitepress build", 13 | "preview": "vitepress preview" 14 | }, 15 | "author": "_Kerman", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/KermanX/refina" 19 | }, 20 | "readme": "https://github.com/KermanX/refina#readme", 21 | "bugs": "https://github.com/KermanX/refina/issues", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@refina/tsconfig": "workspace:^", 25 | "js-confetti": "^0.12.0", 26 | "vite-plugin-refina": "workspace:^", 27 | "vitepress": "1.0.0-rc.31" 28 | }, 29 | "dependencies": { 30 | "@refina/basic-components": "workspace:^", 31 | "@refina/repl": "^0.0.9", 32 | "@vue/theme": "^2.2.5", 33 | "refina": "workspace:^", 34 | "vue": "^3.4.21" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/refinajs/refina/e537f25ee5c6cd272be0ed5253a8ac4bbab511d1/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/media/app-states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/refinajs/refina/e537f25ee5c6cd272be0ed5253a8ac4bbab511d1/docs/public/media/app-states.png -------------------------------------------------------------------------------- /docs/public/media/create-refina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/refinajs/refina/e537f25ee5c6cd272be0ed5253a8ac4bbab511d1/docs/public/media/create-refina.png -------------------------------------------------------------------------------- /docs/snippets/basic-input.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | -------------------------------------------------------------------------------- /docs/snippets/conditional-rendering.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | -------------------------------------------------------------------------------- /docs/snippets/counter.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /docs/snippets/event-handling.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | -------------------------------------------------------------------------------- /docs/snippets/for-times.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /docs/snippets/input-event.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /docs/snippets/list-rendering.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 21 | -------------------------------------------------------------------------------- /docs/snippets/lowlevel.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 46 | -------------------------------------------------------------------------------- /docs/snippets/now.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /docs/snippets/static-page.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | -------------------------------------------------------------------------------- /docs/std-comps/button.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Button` Component 7 | 8 | The main function of the `Button` component is to trigger an action when clicked. 9 | 10 | TriggerComponent 11 | 12 | **Example** 13 | 14 | ```ts 15 | if (_.xButton("Click me", disabled)) { 16 | alert("Clicked!"); 17 | } 18 | ``` 19 | 20 | ## Param: `children` 21 | 22 | **type**: `Content` 23 | 24 | The content of the button. 25 | 26 | ## Param: `disabled` 27 | 28 | = `false` 29 | 30 | **type**: `boolean` 31 | 32 | Whether the button is disabled. 33 | 34 | ## Triggered when: `click` 35 | 36 | **data type**: `void` _(Can be `MouseEvent`)_ 37 | 38 | When the button is clicked, the event will be emitted. 39 | -------------------------------------------------------------------------------- /docs/std-comps/checkbox.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Checkbox` Component 7 | 8 | The main function of the `Checkbox` component is to toggle a boolean value. 9 | 10 | TriggerComponent 11 | 12 | **Example** 13 | 14 | ```ts 15 | if (_.xCheckbox(state, "Check me", disabled)) { 16 | alert(_.$ev ? "Checked!" : "Unchecked!"); 17 | } 18 | ``` 19 | 20 | ## Param: `state` 21 | 22 | **type**: `Model` 23 | 24 | > ↑ `Model` if indeterminate state is supported 25 | 26 | The state of the checkbox. 27 | 28 | ## Param: `label` 29 | 30 | 31 | 32 | **type**: `Content` 33 | 34 | The label of the checkbox. 35 | 36 | ## Param: `disabled` 37 | 38 | = `false` 39 | 40 | **type**: `boolean` 41 | 42 | Whether the checkbox is disabled. 43 | 44 | ## Triggered when: `change` 45 | 46 | **data type**: `boolean` 47 | 48 | > ↑ `boolean|undefined` if indeterminate state is supported 49 | 50 | When the state of the checkbox is changed, the new state will be emitted. 51 | -------------------------------------------------------------------------------- /docs/std-comps/list.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `List` Component 7 | 8 | Render a list of items. 9 | 10 | OutputComponent 11 | 12 | **Example** 13 | 14 | ```ts 15 | _.xList(data, bySelf, item => { 16 | _.span(item); 17 | _.xButton("Remove"); 18 | }); 19 | ``` 20 | 21 | ## Generic: `T` 22 | 23 | The type of the data of one row. 24 | 25 | ## Param: `data` 26 | 27 | **type**: `Iterable` 28 | 29 | The data to render. 30 | 31 | ## Param: `key` 32 | 33 | **type**: `LoopKey` 34 | 35 | The key generator of the list. See [The Key Generator](../guide/essentials/list.md#key-generator). 36 | 37 | ## Param: `body` 38 | 39 | **type**: `(item: T, index: number) => void` 40 | 41 | The body of rows of the list. 42 | -------------------------------------------------------------------------------- /docs/std-comps/nav-rail.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `NavRail` Component 7 | 8 | The `NavRail` component is a navigation rail. It is usually used to navigate between pages. 9 | 10 | It is displayed as a vertical list of buttons on the left side of the screen. 11 | 12 | StatusComponent 13 | 14 | **Example** 15 | 16 | ```ts 17 | const currentPage = _.mdNavRail([ 18 | ["Lobby", "home"], 19 | ["About", "info"], 20 | ]); 21 | _.embed(pages[currentPage]); 22 | ``` 23 | 24 | ## Generic: `Value` 25 | 26 | **extends**: `string` 27 | 28 | The item value type. 29 | 30 | ## Param: `items` 31 | 32 | **type**: `readonly [value: Value, iconName?: string][]` 33 | 34 | The item' values (which is displayed as texts by default) and icon names. 35 | 36 | ## Param: `contentOverride` 37 | 38 | = `{}` 39 | 40 | **type**: `Partial>` 41 | 42 | By default, the text of each item is the value itself. You can override it by passing a record mapping each value to its content. 43 | 44 | ## Status: Active item 45 | 46 | **type**: `Value` 47 | 48 | The value of the active item. 49 | -------------------------------------------------------------------------------- /docs/std-comps/slider.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Slider` Component 7 | 8 | Use the `Slider` component to select a value from a range. 9 | 10 | TriggerComponent 11 | 12 | **Example** 13 | 14 | ```ts 15 | if (_.xSlider(value, disabled, step, min, max)) { 16 | alert(`New value: ${_.$ev}`); 17 | } 18 | ``` 19 | 20 | ## Param: `value` 21 | 22 | **type**: `number` 23 | 24 | The percentage of the slider, from 0 to 100. 25 | 26 | ## Param: `disabled` 27 | 28 | = `false` 29 | 30 | **type**: `boolean` 31 | 32 | Whether the slider is disabled. 33 | 34 | ## Param: `step` 35 | 36 | = `1` 37 | 38 | **type**: `number` 39 | 40 | The step of the slider. 41 | 42 | ## Param: `min` 43 | 44 | = `0` 45 | 46 | **type**: `number` 47 | 48 | The minimum value of the slider. 49 | 50 | ## Param: `max` 51 | 52 | = `100` 53 | 54 | **type**: `number` 55 | 56 | The maximum value of the slider. 57 | 58 | ## Triggered when: `change` 59 | 60 | **data type**: `number` 61 | 62 | When the value of the slider is changed, the new value will be emitted. 63 | -------------------------------------------------------------------------------- /docs/std-comps/switch.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Switch` Component 7 | 8 | The main function of the `Switch` component is to toggle a boolean value. 9 | 10 | TriggerComponent 11 | 12 | **Example** 13 | 14 | ```ts 15 | if (_.xSwitch(checked, "Switch me", disabled)) { 16 | alert(_.$ev ? "On!" : "Off!"); 17 | } 18 | ``` 19 | 20 | ## Param: `checked` 21 | 22 | **type**: `Model` 23 | 24 | The state of the switch. 25 | 26 | ## Param: `label` 27 | 28 | 29 | 30 | **type**: `Content` 31 | 32 | The label of the switch. 33 | 34 | ## Param: `disabled` 35 | 36 | = `false` 37 | 38 | **type**: `boolean` 39 | 40 | Whether the switch is disabled. 41 | 42 | ## Triggered when: `change` 43 | 44 | **data type**: `boolean` 45 | 46 | When the state of the switch is changed, the new state will be emitted. 47 | -------------------------------------------------------------------------------- /docs/std-comps/table.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Table` Component 7 | 8 | Render a table. 9 | 10 | OutputComponent 11 | 12 | **Example** 13 | 14 | ```ts 15 | _.xTable( 16 | data, 17 | [_ => _.xIcon("person"), "Age", "Action"], 18 | "name", 19 | ({ name, age }) => { 20 | _.xTableCell(name); 21 | _.xTableCell(age); 22 | _.xTableCell(_ => _.xButton("Open") && alert(name)); 23 | }, 24 | ); 25 | ``` 26 | 27 | ## Generic: `T` 28 | 29 | The type of the data of one row. 30 | 31 | ## Param: `data` 32 | 33 | **type**: `Iterable` 34 | 35 | The data to render. 36 | 37 | ## Param `head` 38 | 39 | **type**: `Content[] | Content` 40 | 41 | The head of the table. 42 | 43 | If it is an array, each item will be rendered as a ``. Otherwise, it is the content of the ``. 44 | 45 | ## Param: `key` 46 | 47 | **type**: `LoopKey` 48 | 49 | The key generator of the table. See [The Key Generator](../guide/essentials/list.md#key-generator). 50 | 51 | ## Param: `row` 52 | 53 | **type**: `(item: T, index: number) => void` 54 | 55 | The body of rows of the table. 56 | -------------------------------------------------------------------------------- /docs/std-comps/tabs.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Tabs` Component 7 | 8 | Displays a set of tabs, and only one tab is visible at a time. 9 | 10 | TriggerComponent 11 | 12 | **Example** 13 | 14 | ```ts 15 | _.xTabs( 16 | "Tab 1", 17 | _ => _._p({}, "Content 1"), 18 | "Tab 2", 19 | _ => _._p({}, "Content 2"), 20 | "Tab 3", 21 | _ => _._p({}, "Content 3"), 22 | ); 23 | ``` 24 | 25 | ## Params: `...tabs` 26 | 27 | **type**: `RepeatedTuple<[name: string, content: Content]>` (An alternating list of `string` and `Content`). 28 | 29 | The tab names and contents. 30 | 31 | For the odd indices, the value is the tab name. 32 | 33 | For the even indices, the value is the tab content. 34 | 35 | ## Triggered when: `change` 36 | 37 | **data type**: `string` 38 | 39 | When the active tab changes, the new tab name will be emitted. 40 | -------------------------------------------------------------------------------- /docs/std-comps/text-field.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `TextField` Component 7 | 8 | Use the `TextField` component to input text. 9 | 10 | Different from the `Textarea` component, the `TextField` component is a single-line input. 11 | 12 | TriggerComponent 13 | 14 | **Example** 15 | 16 | ```ts 17 | if (_.xTextField(value, "Username", disabled)) { 18 | console.log(`New value: ${_.$ev}`); 19 | } 20 | ``` 21 | 22 | ## Param: `value` 23 | 24 | **type**: `Model` 25 | 26 | The value of the text field. 27 | 28 | ## Param: `label` 29 | 30 | 31 | 32 | **type**: `string` 33 | 34 | > ↑ `Content` if HTML label is supported. 35 | 36 | The label of the text field. 37 | 38 | ## Param: `placeholder` 39 | 40 | 41 | 42 | **type**: `string` 43 | 44 | The placeholder of the text field. 45 | 46 | > May be combined with `label`. 47 | 48 | ## Param: `disabled` 49 | 50 | = `false` 51 | 52 | **type**: `boolean` 53 | 54 | Whether the text field is disabled. 55 | 56 | ## Triggered when: `input` 57 | 58 | **data type**: `string` 59 | 60 | When the value of the text field is changed, the new value will be emitted. 61 | -------------------------------------------------------------------------------- /docs/tutorial/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | title: Tutorial 4 | sidebar: false 5 | aside: false 6 | footer: false 7 | returnToTop: false 8 | --- 9 | 10 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-1/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | $app([Basics], _ => { 5 | _.h1("👋 Hello, Refina!"); 6 | }); 7 | 8 | declare module "refina" { 9 | interface Plugins { 10 | Basics: typeof Basics; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-10/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | import JSConfetti from "js-confetti"; 4 | 5 | const confetti = new JSConfetti(); 6 | confetti.addConfetti(); 7 | 8 | $app([Basics], _ => { 9 | _.$css` 10 | all: unset; 11 | font-size: xx-large; 12 | font-weight: bold; 13 | text-align: center; 14 | margin: 3em 30%;`; 15 | _.button("🎉 Congratulations!") && confetti.addConfetti(); 16 | }); 17 | 18 | declare module "refina" { 19 | interface Plugins { 20 | Basics: typeof Basics; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-10/description.md: -------------------------------------------------------------------------------- 1 | # You Did It! 2 | 3 | You have finished the tutorial! 4 | 5 | At this point, you should have a good idea of what it's like to work with Refina. However, we covered a lot of things really fast and glossed over the details, so definitely keep learning! As a next step, you can: 6 | 7 | - Set up a real Refina project on your machine by following the [Quick Start](../../../guide/quick-start). 8 | - Go through the [Main Guide](../../../guide/essentials/application), which covers all the topics we learned so far in greater details, and much more. 9 | 10 | We can't wait to see what you build next! 11 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-10/import-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "js-confetti": "https://cdn.jsdelivr.net/npm/js-confetti/+esm" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-2/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | // Build your app here 5 | 6 | declare module "refina" { 7 | interface Plugins { 8 | // Add your plugins here 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-2/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | $app([Basics], _ => { 5 | _.h1("Hello, world!"); 6 | _.p(_ => { 7 | _.t`This is a `; 8 | _.a("link", "https://refina.dev"); 9 | _.t`.`; 10 | }); 11 | }); 12 | 13 | declare module "refina" { 14 | interface Plugins { 15 | Basics: typeof Basics; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-3/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | $app([Basics], _ => { 5 | _.h1("Make me styled!"); 6 | }); 7 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-3/App/styles.css: -------------------------------------------------------------------------------- 1 | .my-class { 2 | /* Add your CSS here */ 3 | } 4 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-3/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | $app([Basics], _ => { 5 | _.$cls`my-class`; 6 | _.$css`color:red;font-size:2rem`; 7 | _.h1("Make me styled!"); 8 | }); 9 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-3/_hint/App/styles.css: -------------------------------------------------------------------------------- 1 | .my-class { 2 | text-transform: uppercase; 3 | } 4 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-3/description.md: -------------------------------------------------------------------------------- 1 | # Add Styles and Class Names 2 | 3 | In the previous step, we learned how to render components using some functions in the context object, which are called **component functions**. There are also **directives** in the context object. In this step, we will learn how to use two directives: `_.$cls` and `_.$css`. 4 | 5 | `_.$cls` is used to add class names to the next component. It can be used both as a function and a tag function: 6 | 7 | ```ts 8 | _.$cls`foo`; 9 | _.div(); 10 | //
11 | ``` 12 | 13 | `_.$css` is used to add styles to the next component. It works the same as `_.$cls`: 14 | 15 | ```ts 16 | _.$css`color:red`; 17 | _.div(); 18 | //
19 | ``` 20 | 21 | Also, you can use `_.$cls` and `_.$css` together, and for multiple times: 22 | 23 | ```ts 24 | _.$cls`foo`; 25 | _.$cls`bar`; 26 | _.$css`color:red`; 27 | _.div(); 28 | //
29 | ``` 30 | 31 | Now let's try to add some styles and class names to the `

` element on the right. 32 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-4/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | // Should declare states here? 5 | 6 | $app([Basics], _ => { 7 | // Or here? 8 | 9 | _.p("Count is 0!"); 10 | }); 11 | 12 | declare module "refina" { 13 | interface Plugins { 14 | Basics: typeof Basics; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-4/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | let count = 0; 5 | 6 | $app([Basics], _ => { 7 | // Not here! 8 | 9 | _.p(`Count is ${count}!`); 10 | }); 11 | 12 | declare module "refina" { 13 | interface Plugins { 14 | Basics: typeof Basics; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-4/description.md: -------------------------------------------------------------------------------- 1 | # Using States 2 | 3 | States in Refina are just plain JavaScript values. You can use them to store data and render dynamic content. 4 | 5 | ```ts 6 | let message = "Hello World!"; 7 | let person = { 8 | id: 1, 9 | name: "John Doe", 10 | }; 11 | 12 | $app([Basics], _ => { 13 | _.h1(message); 14 | _.p(`Hello ${person.name}!`); 15 | }); 16 | ``` 17 | 18 | :::warning States should be declared outside the main function. 19 | 20 | Variables declared inside the main function are not states. They are just local temporary variables, which are re-created every time the main function is called. 21 | 22 | ::: 23 | 24 | Just like in JSX, you can render components conditionally using `if` statements or other operators: 25 | 26 | ```ts 27 | let cond = true; 28 | let value: number | null | undefined; 29 | 30 | $app([Basics], _ => { 31 | if (cond) { 32 | _.h1("Hello World!"); 33 | } else { 34 | _.h1("Hello Refina!"); 35 | } 36 | 37 | cond && _.p("cond is truthy."); 38 | value === 1 && _.p("value is 1."); 39 | value ?? _.p("value is null or undefined."); 40 | }); 41 | ``` 42 | 43 | Now, try to create a "count" state yourself, and use it to render the content of `_.p`. 44 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-5/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | $app([Basics], _ => { 5 | _.h1(`You have clicked the button 0 times.`); 6 | _.button("Click me!"); 7 | }); 8 | 9 | declare module "refina" { 10 | interface Plugins { 11 | Basics: typeof Basics; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-5/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | let count = 0; 5 | 6 | $app([Basics], _ => { 7 | _.h1(`You have clicked the button ${count} times.`); 8 | if (_.button("Click me!")) { 9 | count++; 10 | } 11 | }); 12 | 13 | declare module "refina" { 14 | interface Plugins { 15 | Basics: typeof Basics; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-6/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | let name = ""; 5 | 6 | $app([Basics], _ => { 7 | if (_.textInput(name)) { 8 | name = _.$ev; 9 | } 10 | _.p(`Hello ${name}!`); 11 | }); 12 | 13 | declare module "refina" { 14 | interface Plugins { 15 | Basics: typeof Basics; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-6/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app, model } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | const name = model(""); 5 | 6 | $app([Basics], _ => { 7 | _.textInput(name); 8 | if (name.value.length > 0) { 9 | _.button("Clear") && (name.value = ""); 10 | } 11 | _.p(`Hello ${name}!`); 12 | }); 13 | 14 | declare module "refina" { 15 | interface Plugins { 16 | Basics: typeof Basics; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-7/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app, model } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | let id = 0; 5 | let newTodo = model(""); 6 | 7 | let todos = [ 8 | { id: id++, text: "Learn HTML" }, 9 | { id: id++, text: "Learn JavaScript" }, 10 | { id: id++, text: "Learn Refina" }, 11 | ]; 12 | 13 | function remove(id: number) { 14 | todos = todos.filter(todo => todo.id !== id); 15 | } 16 | 17 | $app([Basics], _ => { 18 | _.textInput(newTodo); 19 | _.button("Add Todo") && todos.push({ id: id++, text: newTodo.value }); 20 | 21 | // Finish the following code to render the list of todos. 22 | // _.ul(todos, ...); 23 | }); 24 | 25 | declare module "refina" { 26 | interface Plugins { 27 | Basics: typeof Basics; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-7/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app, model } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | let id = 0; 5 | let newTodo = model(""); 6 | 7 | let todos = [ 8 | { id: id++, text: "Learn HTML" }, 9 | { id: id++, text: "Learn JavaScript" }, 10 | { id: id++, text: "Learn Refina" }, 11 | ]; 12 | 13 | function remove(id: number) { 14 | todos = todos.filter(todo => todo.id !== id); 15 | } 16 | 17 | $app([Basics], _ => { 18 | _.textInput(newTodo); 19 | _.button("Add Todo") && todos.push({ id: id++, text: newTodo.value }); 20 | 21 | _.ul(todos, "id", item => 22 | _.li(_ => { 23 | _.span(item.text); 24 | _.button("❌") && remove(item.id); 25 | }), 26 | ); 27 | }); 28 | 29 | declare module "refina" { 30 | interface Plugins { 31 | Basics: typeof Basics; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-7/description.md: -------------------------------------------------------------------------------- 1 | # List Rendering 2 | 3 | We can use the `_.for` component to render a list of elements based on a source array: 4 | 5 | ```ts 6 | const todos = [ 7 | { id: 1, text: "Buy milk" }, 8 | { id: 2, text: "Do homework" }, 9 | { id: 3, text: "Read a book" }, 10 | ]; 11 | 12 | $app([Basics], _ => { 13 | _.for(todos, "id", item => { 14 | _.p(item.text); 15 | }); 16 | }); 17 | ``` 18 | 19 | Notice how we are also giving each todo object a unique key via the second parameter of `_.for`. The key allows Refina to accurately move each `

` to match the position of its corresponding object in the array. 20 | 21 | The key can be an object key of each item in the list, or a function that returns a unique key for each item. `bySelf` and `byIndex` are two built-in functions that can be used as the key. 22 | 23 | Some components have the same effect as `_.for`, e.g. `_.ul` and `_.ol`. 24 | 25 | Now, let's finish the todo manager. 26 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-8/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | $app([Basics], _ => { 5 | _.h1("Title"); 6 | _.div(_ => { 7 | _.h2("Part 1"); 8 | _.p("Content 1"); 9 | _.a("share", "#"); 10 | }); 11 | _.div(_ => { 12 | _.h2("Part 2"); 13 | _.p("Content 2"); 14 | _.a("share", "#"); 15 | }); 16 | _.div(_ => { 17 | _.h2("Part 3"); 18 | _.p("Content 3"); 19 | _.a("share", "#"); 20 | }); 21 | _.div(_ => { 22 | _.h2("Part 4"); 23 | _.p("Content 4"); 24 | _.a("share", "#"); 25 | }); 26 | }); 27 | 28 | declare module "refina" { 29 | interface Plugins { 30 | Basics: typeof Basics; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-8/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | import PartView from "./PartView"; 4 | 5 | $app([Basics], _ => { 6 | _.h1("Title"); 7 | _(PartView)("Part 1", "Content 1"); 8 | _(PartView)("Part 2", "Content 2"); 9 | _(PartView)("Part 3", "Content 3"); 10 | _(PartView)("Part 4", "Content 4"); 11 | }); 12 | 13 | declare module "refina" { 14 | interface Plugins { 15 | Basics: typeof Basics; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-8/_hint/App/PartView.ts: -------------------------------------------------------------------------------- 1 | import { $view, Content, _ } from "refina"; 2 | 3 | export default $view((title: string, content: Content) => { 4 | _.h1(title); 5 | _.p(content); 6 | }); 7 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-8/description.md: -------------------------------------------------------------------------------- 1 | # Views 2 | 3 | A view is a function that renders a part of the page. 4 | 5 | It is used to split the page into multiple parts, which can be rendered separately, and reused. 6 | 7 | To define a view in Refina, you can use the `$view` function: 8 | 9 | ```ts 10 | import { $view, _ } from "refina"; 11 | 12 | export default $view((id: number) => { 13 | _.h1(`Card ${id}`); 14 | }); 15 | ``` 16 | 17 | To use a view, just call the context object: 18 | 19 | ```ts 20 | import { $app } from "refina"; 21 | import CardView from "./CardView"; 22 | 23 | $app([], _ => { 24 | _(CardView)("1"); 25 | _(CardView)("2"); 26 | _(CardView)("3"); 27 | }); 28 | ``` 29 | 30 | Now, let's try to use views to extract the duplicated code into a view, and use the view to render the content. 31 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-9/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | 3 | /* 4 | Rewrite the following HTML using low-level rendering functions. 5 | 6 | 7 | 8 | 9 | 10 | */ 11 | 12 | $app([], _ => {}); 13 | -------------------------------------------------------------------------------- /docs/tutorial/src/step-9/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | 3 | /* 4 | Rewrite the following HTML using low-level rendering functions. 5 | 6 | 7 | 8 | 9 | 10 | */ 11 | 12 | $app([], _ => { 13 | _.$css`position:fixed;`; 14 | _._svgSvg( 15 | { 16 | id: "svg1", 17 | width: 100, 18 | height: 100, 19 | }, 20 | _ => { 21 | _._svgPath({ 22 | d: "M 10 10 H 90 V 90 H 10 Z", 23 | fill: "red", 24 | }); 25 | _._svgCircle({ 26 | cx: 50, 27 | cy: 50, 28 | r: 40, 29 | stroke: "blue", 30 | fill: "none", 31 | }); 32 | }, 33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /docs/tutorial/tutorial.data.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { createMarkdownRenderer } from "vitepress"; 3 | import { readExamples } from "../helpers/loader"; 4 | import type { ExampleData } from "../helpers/utils"; 5 | 6 | export declare const data: Record; 7 | 8 | export default { 9 | watch: "./src/**", 10 | async load() { 11 | const md = await createMarkdownRenderer(process.cwd(), undefined, "/"); 12 | const files = readExamples(path.resolve(__dirname, "./src")); 13 | for (const step in files) { 14 | const stepFiles = files[step]; 15 | const desc = stepFiles["description.md"] as string; 16 | if (desc) { 17 | stepFiles["description.md"] = md.render(desc); 18 | } 19 | } 20 | return files; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | # Refina.js Docs 2 | 3 | Visit the docs at https\://refina.vercel.app/ and https\://refinajs.github.io/refina. 4 | 5 | Some of the code and content are borrowed from [Vue.js docs](https://github.com/vuejs/docs). Thanks to their great work! 6 | -------------------------------------------------------------------------------- /docs/zh/guide/apis/app-hooks.md: -------------------------------------------------------------------------------- 1 | # 钩子 2 | 3 | 钩子是在特定时机被调用的回调函数。 4 | 5 | 钩子可由插件添加,也可以编程式地注册。 6 | 7 | ## 一次性钩子 8 | 9 | 一次性钩子在调用一次后会被移除。 10 | 11 | 调用 `_.$app.pushOnetimeHook` 以添加一次性钩子。 12 | 13 | 一次性钩子可以通过 `_.$app.onetimeHooks` 访问。 14 | 15 | ## 永久钩子 16 | 17 | 永久钩子在被调用后不会被移除。 18 | 19 | 调用 `_.$app.pushPermanentHook` 以添加永久钩子。 20 | 21 | 永久钩子可以通过 `_.$app.permanentHooks` 访问。 22 | 23 | ## `initContext` 钩子 24 | 25 | 在该钩子中初始化上下文对象。 26 | 27 | ## `beforeMain` 钩子 28 | 29 | 在应用的主函数被调用之前调用,无论处于 `UPDATE` 或 `RECV` 状态下。 30 | 31 | :::tip 32 | 33 | `app.runtimeData` 在此钩子中可用。 34 | 35 | ::: 36 | 37 | ## `afterMain` 钩子 38 | 39 | 在应用的主函数被调用之后调用,无论处于 `UPDATE` 或 `RECV` 状态下。 40 | 41 | :::tip 42 | 43 | `app.runtimeData` 在此钩子中可用。 44 | 45 | ::: 46 | 47 | ## `beforeModifyDOM` 钩子 48 | 49 | 在应用主函数在 `UPDATE` 状态下的调用完成后、在更新 DOM 的树结构、设置类名与样式之前调用。 50 | 51 | :::tip 52 | 53 | `app.runtimeData` 在此钩子中可用。 54 | 55 | ::: 56 | 57 | ## `afterModifyDOM` 钩子 58 | 59 | 在应用主函数在 `UPDATE` 状态下的调用完成并且完成更新 DOM 的树结构、设置类名与样式之后调用。 60 | 61 | :::tip 62 | 63 | `app.runtimeData` 在此钩子中可用。 64 | 65 | ::: 66 | 67 | ## `onError` 钩子 68 | 69 | 当主函数运行时抛出未捕获的错误时调用。 70 | 71 | :::info 72 | 73 | 一个将错误输出至控制台的钩子已经由 `Prelude` 插件自动添加。 74 | 75 | ::: 76 | -------------------------------------------------------------------------------- /docs/zh/guide/essentials/app-state.md: -------------------------------------------------------------------------------- 1 | # 应用状态 2 | 3 | 应用实例总是处于以下3个状态中的一个: 4 | 5 | - `IDLE`: 主函数不在运行。 6 | - `UPDATE`: 渲染并更新页面中。 7 | - `RECV`: 接收并处理事件中。 8 | 9 | ![应用状态流程图](/media/app-states.png) 10 | 11 | ## `IDLE` 状态 12 | 13 | 当 `UPDATE` 状态结束后,应用进入 `IDLE` 状态。 14 | 15 | ## `UPDATE` 状态 16 | 17 | 有三种方式会使得应用进入 `UPDATE` 状态: 18 | 19 | - 应用被挂载(初次创建)。 20 | - 应用实例的 `update` 方法被调用。 21 | - 事件队列被清空。 22 | 23 | 在 `UPDATE` 状态下,应用将生成 DOM tree 并更新页面。 24 | 25 | :::tip 26 | 27 | 多个 `UPDATE` 调用请求将被合并成一个。 28 | 29 | ::: 30 | 31 | :::danger 32 | 33 | 不应当在 `UPDATE` 状态下改变变量。 34 | 35 | 以下代码是错误的。它会造成未定义行为。 36 | 37 | ```ts 38 | let count = 0; 39 | $app([Basics], _ => { 40 | _.p(`Count is: ${count}`); 41 | count++; // count 在 UPDATE 状态下也会被改变 42 | }); 43 | ``` 44 | 45 | ::: 46 | 47 | ## `RECV` 状态 48 | 49 | 当侦听到事件时,应用进入 `RECV` 状态。 50 | 51 | 只有在这个状态下,事件型组件的返回值可能是真值(当它就是事件的接收者时)。 52 | 53 | 当 `RECV` 状态结束后,应用会进入 `UPDATE` 状态以更新页面。 54 | 55 | :::danger 56 | 57 | 不能在 `RECV` 状态下修改 DOM。 58 | 59 | 以下代码是错误的。它会造成未定义行为。 60 | 61 | ```ts 62 | $app([Basics], _ => { 63 | if (_.button("Click me")) { 64 | _.p("Hello"); 65 | } 66 | }); 67 | ``` 68 | 69 | ::: 70 | -------------------------------------------------------------------------------- /docs/zh/guide/essentials/application.md: -------------------------------------------------------------------------------- 1 | # 创建一个 Refina 应用 2 | 3 | ## ​应用实例 4 | 5 | 每个 Refina 应用通过调用 `$app` 函数创建: 6 | 7 | ```ts 8 | import { $app } from "refina"; 9 | 10 | $app([], _ => { 11 | // 应用主体 (主函数) 12 | // ... 13 | }); 14 | ``` 15 | 16 | ## 使用插件 17 | 18 | 所有组件和工具函数都通过插件提供。所以插件是必不可少的一部分。 19 | 20 | `$app` 的第一个参数可以是插件列表。 21 | 22 | ```ts 23 | $app([Plugin1, Plugin2(param1, param2), Plugin3], _ => { 24 | // ... 25 | }); 26 | ``` 27 | 28 | 但是,TypeScript 并不知道有哪些插件被使用,除非显式地声明它们 : 29 | 30 | ```ts 31 | declare module "refina" { 32 | interface Plugins { 33 | Plugin1: typeof Plugin1; 34 | Plugin2: typeof Plugin2; 35 | Plugin3: typeof Plugin3; 36 | } 37 | } 38 | ``` 39 | 40 | 事实上,属于Refina核心的组件和工具函数由名为 `Prelude` 插件的提供。这个插件在创建应用时会被自动添加。 41 | 42 | ## 主函数 43 | 44 | 主函数是页面的主体,负责构建页面和处理事件。 45 | 46 | 不但应用(`App`)有主函数,每个视图(`View`)和组件(`Component`)也有主函数。 47 | 48 | 主函数的第一个参数是上下文对象。通过上下文对象可以进行渲染组件、处理事件等等操作。 49 | 50 | :::warning 51 | 52 | 必须将上下文对象(即第一个参数)命名为 `_`。 53 | 54 | 否则,编译时转换将不会工作,并将产生运行时错误。 55 | 56 | ::: 57 | 58 | ## 根元素 59 | 60 | 根元素是应用挂载在 DOM 中的容器元素。 61 | 62 | 根元素默认是 `"#app"`。 63 | 64 | 你可以用 `root` 选项自定义根元素。 65 | 66 | ```ts 67 | $app( 68 | { plugins: [], root: "#my-root" }, 69 | _ => { 70 | // ... 71 | }, 72 | "my-root", 73 | ); 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/zh/guide/essentials/conditional.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # 条件渲染 6 | 7 | 就像 JSX 中一样,你可以直接使用普通的 `if-else` 语句来根据条件决定是否渲染元素。 8 | 9 | ```ts 10 | let count = 0; 11 | 12 | $app([Basics], _ => { 13 | _.p(`Count is: ${count}`); 14 | 15 | _.button(`Add`) && count++; 16 | 17 | if (count > 0) { 18 | _.button("Reset") && (count = 0); 19 | } 20 | }); 21 | ``` 22 | 23 | **运行结果** 24 | 25 | 26 | 27 | :::tip 28 | 29 | 可以使用 `&&` 运算符来根据条件渲染组件: 30 | 31 | ```ts 32 | input.length > 0 && _.button("Clear"); 33 | ``` 34 | 35 | ::: 36 | -------------------------------------------------------------------------------- /docs/zh/guide/essentials/context.md: -------------------------------------------------------------------------------- 1 | # 上下文对象 2 | 3 | Refina 的很多 API 通过上下文对象提供。 4 | 5 | 上下文对象被用于渲染组件、处理事件,等等。 6 | 7 | 上下文对象有3类成员: 8 | 9 | - **组件函数**:调用它们以渲染组件/元素。 10 | - **工具函数**:一些实用工具,并不渲染组件/元素。 11 | - **指令**:一些特殊的方法与属性。 12 | 13 | :::warning 14 | 15 | 如果你想使用除了指令外的上下文对象的属性,你必须将上下文对象命名为 `_`。 16 | 17 | 否则,编译时转换将不会工作,并将产生运行时错误。 18 | 19 | ::: 20 | 21 | ## 组件函数 22 | 23 | 渲染组件的唯一方法是调用其组件函数。 24 | 25 | 有3种组件函数: 26 | 27 | 1. **文本节点**:即`_.t`。 28 | 2. **底层元素**:原始的 DOM 元素,名称有 `_` 作为前缀,如 `_._div` 和 `_._svgPath`。 29 | 3. **插件提供的组件函数**:比如由 `Basics` 插件提供的 `_.button` 、由 `MdUI` 插件提供的 `_.mdButton`。 它们的名称不含有 `_` 前缀。 它们的名称不含有 `_` 前缀。 30 | 31 | ## 工具函数 32 | 33 | 这些函数作为工具被使用,比如用来控制渲染顺序、设置定时器等等。 34 | 35 | 它们的名称也不含有 `_` 前缀。 36 | 37 | 由 Refina 核心提供的使用函数参见 [Utility Context Functions](/guide/apis/util-funcs.md)。 38 | 39 | ## 指令 40 | 41 | 指令是上下文对象上一些特殊的属性与方法。 42 | 43 | 它们的名称都有 `$` 前缀。并且它们不会经过编译时转换。 44 | 45 | 由 Refina 核心提供的指令参见 [Directives](/guide/apis/directives.md)。 46 | -------------------------------------------------------------------------------- /docs/zh/guide/essentials/list.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # 列表渲染 7 | 8 | 就像在 Vue.js 中一样,在渲染列表时需要为每个元素指定唯一的 `key`。 9 | 10 | 因此,不可以使用普通的循环来渲染列表。因为它们不接收 `key`。 11 | 12 | 需要使用 `_.for` 或 `_.forTimes` 等上下文你函数来渲染列表。 13 | 14 | ```ts 15 | import { bySelf } from "refina"; 16 | 17 | const items = ["Apple", "Banana", "Orange"]; 18 | 19 | $app([Basics], _ => { 20 | _.for(items, bySelf, item => { 21 | _.p(item); 22 | }); 23 | }); 24 | ``` 25 | 26 | **运行结果** 27 | 28 | 29 | 30 | ## key 生成器 {#key-generator} 31 | 32 | `_.for` 的第二个参数是一个 key 生成器,用于为每个元素提供一个唯一的 key。 33 | 34 | key 生成器可以是一个形式为 `(item, index) => key` 的函数,也可以是被作为 key 的元素属性的属性名。 35 | 36 | Refina 提供了两个 key 生成器: 37 | 38 | - `bySelf`: 将元素本身作为 key。 39 | - `byIndex`: 将元素的索引作为 key。 40 | 41 | ## 重复一定次数 {#for-times} 42 | 43 | 可以使用 `_.forTimes` 来将渲染重复数次。 44 | 45 | `_.forTimes` 没有 key 生成器。它将索引作为 key。 46 | 47 | ```ts 48 | $app([Basics], _ => { 49 | _.forTimes(5, index => { 50 | _.p(`This is the ${index + 1}th paragraph.`); 51 | }); 52 | }); 53 | ``` 54 | 55 | **运行结果** 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/zh/guide/why.md: -------------------------------------------------------------------------------- 1 | # 为什么选 Refina 2 | 3 | ## 前端的困境 4 | 5 | 对于大部分前端页面,长什么样、要做什么,往往只需要三言两语就能说明白。 但是若要通过传统的前端框架实现它,往往需要写数百行代码。 6 | 7 | 传统的前端框架,比如 Vue,配合组件库,比如 Vuetify,允许你编写出细节完善、视觉美观的应用。 但是即使是一些“大厂”,也没能总是产出那么高质量的应用,因为开发效率的原因。 这即是说,即使开发者有能力,往往也会由于没有时间而无法做出相当完美的应用。 结果就是,许多传统前端框架的能力几乎得不到发挥,而为拥有这些能力所作的一些设计反而在很多情况下称为了负担。 8 | 9 | 除了传统前端框架,我们其实需要一个**首先关注开发效率**,其次是功能的完整性,再其次是运行时性能的前端框架。 10 | 11 | ## 解决方案 12 | 13 | - **类 ImGUI 的状态管理机制** 14 | 15 | 省去状态管理带来的一切心智负担。 就像 [Svelte](https://svelte.dev/) 那样无需手动为数据添加响应性,并且不需要复杂的编译器。 16 | 17 | - **无需写结束标签** 18 | 19 | 结束标签(如``)对开发效率造成了很大的影响,而它们事实上并不必要。 20 | 21 | - **按位置传入参数,而非按名称** 22 | 23 | 这样,开发者就不需要写出参数的名称。这节省了很多时间与代码量。 24 | 25 | - **通过返回值监听事件** 26 | 27 | 通过检查返回值来判断事件是否发生,就不需要使用回调函数,减少了代码复杂度。 28 | 29 | - **纯 TypeScript 构建页面** 30 | 31 | 无需像 JSX 或 Vue SFC 那样的 DSL。 渲染和逻辑使用同样的、普通的 TypeScript 语法。相关的工具链也都完全兼容。 32 | 33 | ## 效果 34 | 35 | Refina 仍在积极开发中,但它已经在开发效率上体现了非常大的优势。 36 | 37 | 基于数个真实项目,Refina 相比其他前端框架**减少了大约 `30%~40%` 的代码量**,**开发效率提升至约 `1.4~1.6` 倍**。 38 | -------------------------------------------------------------------------------- /docs/zh/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | title: Refina 4 | titleTemplate: 高度提炼的前端框架 5 | hero: 6 | name: Refina 7 | text: 高度提炼的前端框架 8 | tagline: 帮助你极大地减少工作量。 9 | image: 10 | src: /logo.svg 11 | alt: Refina Logo 12 | actions: 13 | - theme: brand 14 | text: 快速上手 15 | link: /zh/guide/introduction 16 | - theme: alt 17 | text: English Version 18 | link: / 19 | - theme: alt 20 | text: 为什么选 Refina? 21 | link: /zh/guide/why 22 | - theme: alt 23 | text: 在 GitHub 上查看 24 | link: https://github.com/refinajs/refina 25 | features: 26 | - icon: 💡 27 | title: 类 ImGUI 的状态管理机制 28 | details: 省去状态管理带来的一切心智负担。 29 | - icon: ⚡️ 30 | title: 大大减少代码量 31 | details: 无需写元素的结束标签,并按位置传入参数而非按名称传入参数。 32 | - icon: 🧩 33 | title: 纯 TypeScript 34 | details: 无需使用 DSL。 所有的 TypeScript 功能和开发工具都可用。 35 | - icon: 🔥 36 | title: 热模块替换 37 | details: 热模块替换(HMR)运行您在开发时更新页面内容而无需刷新页面,不丢失页面的状态。 38 | - icon: 🔩 39 | title: 插件系统 40 | details: 可以通过插件扩充 Refina 的功能。 包括组件、工具函数、指令等等。 41 | - icon: 🔑 42 | title: 完整的 TS 类型 43 | details: 所有 API 都有良好的类型。甚至一些动态的状态也有类型提示。 44 | --- 45 | -------------------------------------------------------------------------------- /docs/zh/misc/contact.md: -------------------------------------------------------------------------------- 1 | # 关于我们 2 | 3 | ## 关于作者 4 | 5 | - **邮箱**: [kermanx@qq.com](mailto:kermanx@qq.com) 6 | - **GitHub**: [KermanX](https://github.com/KermanX) 7 | 8 | ## QQ 群 9 | 10 | - **群号**: 488240549 11 | 12 | ## Discord 服务器 13 | 14 | - **邀请链接**: [Join Refina.js](https://discord.gg/2hjDhfpgzK) 15 | 16 | > 该服务器可能并不活跃。 如果你有任何问题,欢迎通过邮箱或QQ群联系。 17 | -------------------------------------------------------------------------------- /docs/zh/misc/playground.md: -------------------------------------------------------------------------------- 1 | 3 | 4 | 13 | -------------------------------------------------------------------------------- /docs/zh/std-comps/button.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Button` Component 7 | 8 | The main function of the `Button` component is to trigger an action when clicked. 9 | 10 | TriggerComponent 11 | 12 | **例子** 13 | 14 | ```ts 15 | if (_.xButton("Click me", disabled)) { 16 | alert("Clicked!"); 17 | } 18 | ``` 19 | 20 | ## Param: `children` 21 | 22 | **type**: `D` 23 | 24 | The content of the button. 25 | 26 | ## Param: `disabled` 27 | 28 | = `false` 29 | 30 | **type**: `D` 31 | 32 | Whether the button is disabled. 33 | 34 | ## Triggered when: `click` 35 | 36 | **data type**: `void` _(Can be `MouseEvent`)_ 37 | 38 | When the button is clicked, the event will be emitted. 39 | -------------------------------------------------------------------------------- /docs/zh/std-comps/checkbox.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Checkbox` Component 7 | 8 | The main function of the `Checkbox` component is to toggle a boolean value. 9 | 10 | TriggerComponent 11 | 12 | **例子** 13 | 14 | ```ts 15 | if (_.xCheckbox(state, "Check me", disabled)) { 16 | alert(_.$ev ? "Checked!" : "Unchecked!"); 17 | } 18 | ``` 19 | 20 | ## Param: `state` 21 | 22 | **type**: `D` 23 | 24 | > ↑ `D` if indeterminate state is supported 25 | 26 | The state of the checkbox. 27 | 28 | ## Param: `label` 29 | 30 | 31 | 32 | **type**: `D` 33 | 34 | The label of the checkbox. 35 | 36 | ## Param: `disabled` 37 | 38 | = `false` 39 | 40 | **type**: `D` 41 | 42 | Whether the checkbox is disabled. 43 | 44 | ## Triggered when: `change` 45 | 46 | **data type**: `boolean` 47 | 48 | > ↑ `boolean|undefined` if indeterminate state is supported 49 | 50 | When the state of the checkbox is changed, the new state will be emitted. 51 | -------------------------------------------------------------------------------- /docs/zh/std-comps/introduction.md: -------------------------------------------------------------------------------- 1 | # 标准组件 2 | 3 | 本章节描述了 Refina 定义的标准组件。 4 | 5 | Refina 的标准组件是常用的,且_有一定复杂度_的组件。 不能保证所有组件库都会包含所有这些标准组件。 但组件库应当尽可能地覆盖标准组件,并符合标准组件的接口。 6 | 7 | :::warning 8 | 9 | 标准组件在不同的 UI 库中可能有不同的实现。 10 | 11 | 一些特性在有些 UI 风格中不可用,而有些 UI 风格会提供一些专有的特性。 12 | 13 | ::: 14 | 15 | :::tip 16 | 17 | 在本文档的示例中,我们使用 `x` 前缀来表示标准组件。 比如,`_.xButton`。 比如,`_.xButton`。 18 | 19 | 但是,在实际开发中,需要加上你所使用的组件库的前缀。 但是,在实际开发中,需要加上你所使用的组件库的前缀。 比如,MdUI 中的按钮是 `_.mdButton`,而在 FluentUI 中是 `_.fButton`。 20 | 21 | 由 `@refina/basic-components` 提供的组件没有前缀。 比如 `_.button`。 比如 `_.button`。 22 | 23 | ::: 24 | 25 | :::info 26 | 27 | 一些过于简单和显然的不属于标准组件的范畴。 比如,`_.span` 与 `_.mdIcon`。 28 | 29 | 一些只在少数组件库中包含的组件也不属于标准组件的范畴。 比如,`_.mdAppBarTitle`。 30 | 31 | ::: 32 | 33 | ## 目前可用的组件库 34 | 35 | 以下组件库目前已经可用: 36 | 37 | [![@refina/basic-components](https://img.shields.io/npm/v/%40refina%2Fbasic-components?label=%40refina%2Fbasic-components\&color=green)](https://www.npmjs.com/package/@refina/basic-components) 38 | 39 | [![@refina/mdui](https://img.shields.io/npm/v/%40refina%2Fmdui?label=%40refina%2Fmdui\&color=green)](https://www.npmjs.com/package/@refina/mdui) 40 | -------------------------------------------------------------------------------- /docs/zh/std-comps/list.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `List` Component 7 | 8 | Render a list of items. 9 | 10 | OutputComponent 11 | 12 | **例子** 13 | 14 | ```ts 15 | _.xList(data, bySelf, item => { 16 | _.span(item); 17 | _.xButton("Remove"); 18 | }); 19 | ``` 20 | 21 | ## Generic: `T` 22 | 23 | The type of the data of one row. 24 | 25 | ## Param: `data` 26 | 27 | **type**: `D>` 28 | 29 | The data to render. 30 | 31 | ## Param: `key` 32 | 33 | **type**: `LoopKey` 34 | 35 | The key generator of the list. See [The Key Generator](../guide/essentials/list.md#key-generator). 36 | 37 | ## Param: `body` 38 | 39 | **type**: `(item: T, index: number) => void` 40 | 41 | The body of rows of the list. 42 | -------------------------------------------------------------------------------- /docs/zh/std-comps/nav-rail.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `NavRail` Component 7 | 8 | The `NavRail` component is a navigation rail. It is usually used to navigate between pages. 9 | 10 | It is displayed as a vertical list of buttons on the left side of the screen. 11 | 12 | StatusComponent 13 | 14 | **例子** 15 | 16 | ```ts 17 | const currentPage = _.mdNavRail([ 18 | ["Lobby", "home"], 19 | ["About", "info"], 20 | ]); 21 | _.embed(pages[currentPage]); 22 | ``` 23 | 24 | ## Generic: `Value` 25 | 26 | **extends**: `string` 27 | 28 | The item value type. 29 | 30 | ## Param: `items` 31 | 32 | **type**: `DReadonlyArray<[value: Value, iconName?: string]><[value: Value, iconName?: string]>` 33 | 34 | The item' values (which is displayed as texts by default) and icon names. 35 | 36 | ## Param: `contentOverride` 37 | 38 | = `{}` 39 | 40 | **type**: `DPartialRecord` 41 | 42 | By default, the text of each item is the value itself. You can override it by passing a record mapping each value to its content. 43 | 44 | ## Status: Active item 45 | 46 | **type**: `Value` 47 | 48 | The value of the active item. 49 | -------------------------------------------------------------------------------- /docs/zh/std-comps/slider.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Slider` Component 7 | 8 | Use the `Slider` component to select a value from a range. 9 | 10 | TriggerComponent 11 | 12 | **例子** 13 | 14 | ```ts 15 | if (_.xSlider(value, disabled, step, min, max)) { 16 | alert(`New value: ${_.$ev}`); 17 | } 18 | ``` 19 | 20 | ## Param: `value` 21 | 22 | **type**: `D` 23 | 24 | The percentage of the slider, from 0 to 100. 25 | 26 | ## Param: `disabled` 27 | 28 | = `false` 29 | 30 | **type**: `D` 31 | 32 | Whether the slider is disabled. 33 | 34 | ## Param: `step` 35 | 36 | = `1` 37 | 38 | **type**: `D` 39 | 40 | The step of the slider. 41 | 42 | ## Param: `min` 43 | 44 | = `0` 45 | 46 | **type**: `D` 47 | 48 | The minimum value of the slider. 49 | 50 | ## Param: `max` 51 | 52 | = `100` 53 | 54 | **type**: `D` 55 | 56 | The maximum value of the slider. 57 | 58 | ## Triggered when: `change` 59 | 60 | **data type**: `number` 61 | 62 | When the value of the slider is changed, the new value will be emitted. 63 | -------------------------------------------------------------------------------- /docs/zh/std-comps/switch.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Switch` Component 7 | 8 | The main function of the `Switch` component is to toggle a boolean value. 9 | 10 | TriggerComponent 11 | 12 | **例子** 13 | 14 | ```ts 15 | if (_.xSwitch(checked, "Switch me", disabled)) { 16 | alert(_.$ev ? "On!" : "Off!"); 17 | } 18 | ``` 19 | 20 | ## Param: `checked` 21 | 22 | **type**: `D` 23 | 24 | The state of the switch. 25 | 26 | ## Param: `label` 27 | 28 | 29 | 30 | **type**: `D` 31 | 32 | The label of the switch. 33 | 34 | ## Param: `disabled` 35 | 36 | = `false` 37 | 38 | **type**: `D` 39 | 40 | Whether the switch is disabled. 41 | 42 | ## Triggered when: `change` 43 | 44 | **data type**: `boolean` 45 | 46 | When the state of the switch is changed, the new state will be emitted. 47 | -------------------------------------------------------------------------------- /docs/zh/std-comps/table.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Table` Component 7 | 8 | Render a table. 9 | 10 | OutputComponent 11 | 12 | **例子** 13 | 14 | ```ts 15 | _.mdTable( 16 | data, 17 | [_ => _.mdIcon("person"), "Age", "Action"], 18 | "name", 19 | ({ name, age }) => { 20 | _.mdTableCell(name); 21 | _.mdTableCell(age); 22 | _.mdTableCell(_ => _.mdButton("Open") && alert(name)); 23 | }, 24 | ); 25 | ``` 26 | 27 | ## Generic: `T` 28 | 29 | The type of the data of one row. 30 | 31 | ## Param: `data` 32 | 33 | **type**: `D>` 34 | 35 | The data to render. 36 | 37 | ## Param `head` 38 | 39 | **type**: `DArray | D` 40 | 41 | The head of the table. 42 | 43 | If it is an array, each item will be rendered as a ``. Otherwise, it is the content of the ``. 44 | 45 | ## Param: `key` 46 | 47 | **type**: `LoopKey` 48 | 49 | The key generator of the table. See [The Key Generator](../guide/essentials/list.md#key-generator). 50 | 51 | ## Param: `row` 52 | 53 | **type**: `(item: T, index: number) => void` 54 | 55 | The body of rows of the table. 56 | -------------------------------------------------------------------------------- /docs/zh/std-comps/tabs.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `Tabs` Component 7 | 8 | Displays a set of tabs, and only one tab is visible at a time. 9 | 10 | TriggerComponent 11 | 12 | **例子** 13 | 14 | ```ts 15 | _.xTabs( 16 | "Tab 1", 17 | _ => _._p({}, "Content 1"), 18 | "Tab 2", 19 | _ => _._p({}, "Content 2"), 20 | "Tab 3", 21 | _ => _._p({}, "Content 3"), 22 | ); 23 | ``` 24 | 25 | ## Params: `...tabs` 26 | 27 | **type**: `RepeatedTuple<[name: D, content: D]>` (An alternating list of `D` and `D`). 28 | 29 | The tab names and contents. 30 | 31 | For the odd indices, the value is the tab name. 32 | 33 | For the even indices, the value is the tab content. 34 | 35 | ## Triggered when: `change` 36 | 37 | **data type**: `string` 38 | 39 | When the active tab changes, the new tab name will be emitted. 40 | -------------------------------------------------------------------------------- /docs/zh/std-comps/text-field.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # The `TextField` Component 7 | 8 | Use the `TextField` component to input text. 9 | 10 | Different from the `Textarea` component, the `TextField` component is a single-line input. 11 | 12 | TriggerComponent 13 | 14 | **例子** 15 | 16 | ```ts 17 | if (_.xTextField(value, "Username", disabled)) { 18 | console.log(`New value: ${_.$ev}`); 19 | } 20 | ``` 21 | 22 | ## Param: `value` 23 | 24 | **type**: `D` 25 | 26 | The value of the text field. 27 | 28 | ## Param: `label` 29 | 30 | 31 | 32 | **type**: `D` 33 | 34 | > ↑ `D` if HTML label is supported. 35 | 36 | The label of the text field. 37 | 38 | ## Param: `placeholder` 39 | 40 | 41 | 42 | **type**: `D` 43 | 44 | The placeholder of the text field. 45 | 46 | > May be combined with `label`. 47 | 48 | ## Param: `disabled` 49 | 50 | = `false` 51 | 52 | **type**: `D` 53 | 54 | Whether the text field is disabled. 55 | 56 | ## Triggered when: `input` 57 | 58 | **data type**: `string` 59 | 60 | When the value of the text field is changed, the new value will be emitted. 61 | -------------------------------------------------------------------------------- /docs/zh/tutorial/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | title: 教程 4 | sidebar: false 5 | aside: false 6 | footer: false 7 | returnToTop: false 8 | --- 9 | 10 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-1/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | $app([Basics], _ => { 4 | _.h1("👋 Hello, Refina!"); 5 | }); 6 | declare module "refina" { 7 | interface Plugins { 8 | Basics: typeof Basics; 9 | } 10 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-1/description.md: -------------------------------------------------------------------------------- 1 | # 开始 {#getting-started} 2 | 3 | 欢迎来到 Refina 互动教程! 4 | 5 | 本教程的目标是让你在浏览器中快速体验使用 Refina 是什么感受, 因此它不会太过深入解释所有细节,如果有些东西你一时无法完全理解,也不必担心。 但是,在完成本教程之后,请务必阅读指南,以确保你对涉及的话题有更深入、完整的理解。 6 | 7 | ## 前置要求 {#prerequisites} 8 | 9 | 本教程假定你基本熟悉 HTML、CSS 和 JavaScript。 对于前端开发来说,一个完全的新手也许并不适合上手就学习框架——最好是掌握了基础知识再回来。 如果之前有其他框架的经验会很有帮助,但也不是必须的。 10 | 11 | ## 如何使用本教程 {#how-to-use-this-tutorial} 12 | 13 | 你可以编辑右侧下方的代码,并立即看到结果更新。 教程每一步都会介绍一个 Refina 的核心功能,并期望你能够补全代码,让 demo 运行起来。 如果你卡住了,会有一个“看答案!”按钮,点击它,会为你揭晓能够运行的代码。 试着不要太依赖该按钮——自己解决会学得更快。 14 | 15 | 准备好了吗? 点击“下一步”按钮开始吧。 16 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-10/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | import JSConfetti from "js-confetti"; 4 | const confetti = new JSConfetti(); 5 | confetti.addConfetti(); 6 | $app([Basics], _ => { 7 | _.$css` 8 | all: unset; 9 | font-size: xx-large; 10 | font-weight: bold; 11 | text-align: center; 12 | margin: 3em 30%;`; 13 | _.button("🎉 Congratulations!") && confetti.addConfetti(); 14 | }); 15 | declare module "refina" { 16 | interface Plugins { 17 | Basics: typeof Basics; 18 | } 19 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-10/description.md: -------------------------------------------------------------------------------- 1 | # 你做到了! 2 | 3 | 你已经完成了整个教程! 4 | 5 | 现在,你应该大致明白使用 Refina 开发应用是怎样的感觉了。 虽然我们飞快地介绍了许多东西,但也因此忽略了大量的细节,所以千万别这样就满足了! 接下来,你可以: 6 | 7 | - 参考[快速上手](../../../guide/quick-start),在你的机器上创建一个真实的 Refina 项目。 8 | - 阅读[指南](../../../guide/essentials/application)——它更详细地探讨了我们目前学过的所有话题,并且还有许多其他更深入的内容。 9 | 10 | 我们很期待看到你能用 Refina 打造出怎样的作品! 11 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-10/import-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "js-confetti": "https://cdn.jsdelivr.net/npm/js-confetti/+esm" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-2/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | // Build your app here 5 | 6 | declare module "refina" { 7 | interface Plugins { 8 | // Add your plugins here 9 | } 10 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-2/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | $app([Basics], _ => { 4 | _.h1("Hello, world!"); 5 | _.p(_ => { 6 | _.t`This is a `; 7 | _.a("link", "https://refina.dev"); 8 | _.t`.`; 9 | }); 10 | }); 11 | declare module "refina" { 12 | interface Plugins { 13 | Basics: typeof Basics; 14 | } 15 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-3/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | $app([Basics], _ => { 4 | _.h1("Make me styled!"); 5 | }); -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-3/App/styles.css: -------------------------------------------------------------------------------- 1 | .my-class { 2 | /* 在此处编写 CSS */ 3 | } 4 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-3/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | $app([Basics], _ => { 4 | _.$cls`my-class`; 5 | _.$css`color:red;font-size:2rem`; 6 | _.h1("Make me styled!"); 7 | }); -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-3/_hint/App/styles.css: -------------------------------------------------------------------------------- 1 | .my-class { 2 | text-transform: uppercase; 3 | } 4 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-3/description.md: -------------------------------------------------------------------------------- 1 | # 添加样式与类名 2 | 3 | 在上一步中,我没学习了如何使用上下文对象中的**组件函数**来渲染组件。 上下文对象中还有**指令**。 在这一步中,我们将学习使用两个指令:`_.$cls` 和 `_.$css`。 4 | 5 | `_.$cls` 用于向下一个渲染的组件添加类名。 它既可以是普通函数也可以是标签函数: 6 | 7 | ```ts 8 | _.$cls`foo`; 9 | _.div(); 10 | //

11 | ``` 12 | 13 | `_.$css` 用于向下一个渲染的组件添加样式(相当于元素的 `style` 属性)。 它和 `_.$cls` 用法一致: 14 | 15 | ```ts 16 | _.$css`color:red`; 17 | _.div(); 18 | //
19 | ``` 20 | 21 | 你还可以一起使用 `_.$cls` 和 `_.$css`,并可以连续使用多次: 22 | 23 | ```ts 24 | _.$cls`foo`; 25 | _.$cls`bar`; 26 | _.$css`color:red`; 27 | _.div(); 28 | //
29 | ``` 30 | 31 | 现在,试着给编辑器中的 `

` 元素添加一些样式和类名。 32 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-4/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | 4 | // Should declare states here? 5 | 6 | $app([Basics], _ => { 7 | // Or here? 8 | 9 | _.p("Count is 0!"); 10 | }); 11 | declare module "refina" { 12 | interface Plugins { 13 | Basics: typeof Basics; 14 | } 15 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-4/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | let count = 0; 4 | $app([Basics], _ => { 5 | // Not here! 6 | 7 | _.p(`Count is ${count}!`); 8 | }); 9 | declare module "refina" { 10 | interface Plugins { 11 | Basics: typeof Basics; 12 | } 13 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-4/description.md: -------------------------------------------------------------------------------- 1 | # 状态 2 | 3 | Refina 中的状态是普通的 JavaScript 变量。 你可以使用状态来存储数据,并渲染动态的内容。 4 | 5 | ```ts 6 | let message = "Hello World!"; 7 | let person = { 8 | id: 1, 9 | name: "John Doe", 10 | }; 11 | 12 | $app([Basics], _ => { 13 | _.h1(message); 14 | _.p(`Hello ${person.name}!`); 15 | }); 16 | ``` 17 | 18 | :::warning States should be declared outside the main function. 19 | 20 | Variables declared inside the main function are not states. They are just local temporary variables, which are re-created every time the main function is called. 21 | 22 | ::: 23 | 24 | 就像在 JSX 中那样,你可以使用 `if` 语句或者其他运算符来根据条件渲染组件: 25 | 26 | ```ts 27 | let cond = true; 28 | let value: number | null | undefined; 29 | 30 | $app([Basics], _ => { 31 | if (cond) { 32 | _.h1("Hello World!"); 33 | } else { 34 | _.h1("Hello Refina!"); 35 | } 36 | 37 | cond && _.p("cond is truthy."); 38 | value === 1 && _.p("value is 1."); 39 | value ?? _.p("value is null or undefined."); 40 | }); 41 | ``` 42 | 43 | 现在,请尝试自己创建一个 "count" 状态,并用它来渲染 `_.p` 组件的内容。 44 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-5/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | $app([Basics], _ => { 4 | _.h1(`You have clicked the button 0 times.`); 5 | _.button("Click me!"); 6 | }); 7 | declare module "refina" { 8 | interface Plugins { 9 | Basics: typeof Basics; 10 | } 11 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-5/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | let count = 0; 4 | $app([Basics], _ => { 5 | _.h1(`You have clicked the button ${count} times.`); 6 | if (_.button("Click me!")) { 7 | count++; 8 | } 9 | }); 10 | declare module "refina" { 11 | interface Plugins { 12 | Basics: typeof Basics; 13 | } 14 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-5/description.md: -------------------------------------------------------------------------------- 1 | # 事件处理 2 | 3 | 我们可以通过检查组件函数的返回值来接收事件: 4 | 5 | ```ts {2-4} 6 | $app([Basics], _ => { 7 | if (_.button("Click me!")) { 8 | console.log("Clicked!", _.$ev); 9 | } 10 | }); 11 | ``` 12 | 13 | 只有在按钮被按下后,对应的 `_.button` 函数调用会返回 `true`,然后 `if` 内部的语句会被执行。 这些语句就是处理事件的代码。 14 | 15 | `_.$ev` 是事件所携带的数据。 对于 `_.button`,它是一个 `MouseEvent` 对象。 `_.$ev` 只在事件处理部分可用。 16 | 17 | 你可以在事件处理代码中更新状态。 18 | 19 | ```ts {4} 20 | let click = false; 21 | 22 | $app([Basics], _ => { 23 | _.button("Click me!") && (clicked = true); 24 | clicked && _.p("Clicked!"); 25 | }); 26 | ``` 27 | 28 | 46 | 47 | 在所有事件被处理之后,Refina 会自动更新页面。 48 | 49 | 现在,试着自己实现一个显示点击按钮的次数的计数器。 50 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-6/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | let name = ""; 4 | $app([Basics], _ => { 5 | if (_.textInput(name)) { 6 | name = _.$ev; 7 | } 8 | _.p(`Hello ${name}!`); 9 | }); 10 | declare module "refina" { 11 | interface Plugins { 12 | Basics: typeof Basics; 13 | } 14 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-6/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app, model } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | const name = model(""); 4 | $app([Basics], _ => { 5 | _.textInput(name); 6 | if (name.value.length > 0) { 7 | _.button("Clear") && (name.value = ""); 8 | } 9 | _.p(`Hello ${name}!`); 10 | }); 11 | declare module "refina" { 12 | interface Plugins { 13 | Basics: typeof Basics; 14 | } 15 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-6/description.md: -------------------------------------------------------------------------------- 1 | # 获取用户输入 2 | 3 | 通过接收事件,我们可用获取用户输入并据此更新状态: 4 | 5 | ```ts 6 | let name = ""; 7 | 8 | $app([Basics], _ => { 9 | if (_.textInput(name)) { 10 | name = _.$ev; 11 | } 12 | _.p(`Hello ${name}!`); 13 | }); 14 | ``` 15 | 16 | 试着在 "PREVIEW" 栏中的输入框中输入字符,你会看见 `

` 中的文字同步更新。 17 | 18 | 还有一个更加简单的方式可以将用户输入直接保存到一个状态,而无需手动处理事件以保存输入: 19 | 20 | ```ts 21 | import { $app, model } from "refina"; 22 | 23 | const name = model(""); 24 | 25 | $app([Basics], _ => { 26 | _.textInput(name); 27 | 28 | _.p(`Hello ${name}!`); 29 | // equivalent to: 30 | // _.p(`Hello ${name.value}!`); 31 | }); 32 | ``` 33 | 34 | `model` 函数创建了一个 `JustModel` 对象。它简单地把原始值包裹为对象,于是组件可以“引用”到值并直接更新它。 35 | 36 | `textInput` 组件(以及一些类似的组件)接收 类型为 `Model` 的值作为它的第一个参数,即,既可以是原始值也可以是 model。 当用户在输入框中输入时,组件会更新 model 的值。 37 | 38 | 现在,试着使用 `model` 重构编辑器中的代码。 并且在当输入框不为空时显示一个可以清除输入内容的按钮。 39 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-7/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app, model } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | let id = 0; 4 | let newTodo = model(""); 5 | let todos = [{ 6 | id: id++, 7 | text: "Learn HTML" 8 | }, { 9 | id: id++, 10 | text: "Learn JavaScript" 11 | }, { 12 | id: id++, 13 | text: "Learn Refina" 14 | }]; 15 | function remove(id: number) { 16 | todos = todos.filter(todo => todo.id !== id); 17 | } 18 | $app([Basics], _ => { 19 | _.textInput(newTodo); 20 | _.button("Add Todo") && todos.push({ 21 | id: id++, 22 | text: newTodo.value 23 | }); 24 | 25 | // Finish the following code to render the list of todos. 26 | // _.ul(todos, ...); 27 | }); 28 | declare module "refina" { 29 | interface Plugins { 30 | Basics: typeof Basics; 31 | } 32 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-7/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app, model } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | let id = 0; 4 | let newTodo = model(""); 5 | let todos = [{ 6 | id: id++, 7 | text: "Learn HTML" 8 | }, { 9 | id: id++, 10 | text: "Learn JavaScript" 11 | }, { 12 | id: id++, 13 | text: "Learn Refina" 14 | }]; 15 | function remove(id: number) { 16 | todos = todos.filter(todo => todo.id !== id); 17 | } 18 | $app([Basics], _ => { 19 | _.textInput(newTodo); 20 | _.button("Add Todo") && todos.push({ 21 | id: id++, 22 | text: newTodo.value 23 | }); 24 | _.ul(todos, "id", item => _.li(_ => { 25 | _.span(item.text); 26 | _.button("❌") && remove(item.id); 27 | })); 28 | }); 29 | declare module "refina" { 30 | interface Plugins { 31 | Basics: typeof Basics; 32 | } 33 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-7/description.md: -------------------------------------------------------------------------------- 1 | # 列表渲染 2 | 3 | 使用 `_.for` 组件来渲染列表一个基于数组的列表: 4 | 5 | ```ts 6 | const todos = [ 7 | { id: 1, text: "Buy milk" }, 8 | { id: 2, text: "Do homework" }, 9 | { id: 3, text: "Read a book" }, 10 | ]; 11 | 12 | $app([Basics], _ => { 13 | _.for(todos, "id", item => { 14 | _.p(item.text); 15 | }); 16 | }); 17 | ``` 18 | 19 | 注意我们还通过 `_.for` 的第二个参数给每个 todo 对象设置了唯一的 key。 这允许 Refina 能够精确的移动每个 `

`,以匹配对应的对象在数组中的位置。 20 | 21 | 这个 key 可以是列表元素的一个键名,或者一个返回 key 的函数。 Refina 还提供了 `bySelf` 和 `byIndex` 这两个可以作为 key 的函数。 22 | 23 | 一些组件自身也具备 `_.for` 的用法和功能,比如 `_.ul` 与 `_.ol`。 24 | 25 | 现在,尝试完成这个 todo 列表。 26 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-8/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | $app([Basics], _ => { 4 | _.h1("Title"); 5 | _.div(_ => { 6 | _.h2("Part 1"); 7 | _.p("Content 1"); 8 | _.a("share", "#"); 9 | }); 10 | _.div(_ => { 11 | _.h2("Part 2"); 12 | _.p("Content 2"); 13 | _.a("share", "#"); 14 | }); 15 | _.div(_ => { 16 | _.h2("Part 3"); 17 | _.p("Content 3"); 18 | _.a("share", "#"); 19 | }); 20 | _.div(_ => { 21 | _.h2("Part 4"); 22 | _.p("Content 4"); 23 | _.a("share", "#"); 24 | }); 25 | }); 26 | declare module "refina" { 27 | interface Plugins { 28 | Basics: typeof Basics; 29 | } 30 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-8/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import Basics from "@refina/basic-components"; 3 | import PartView from "./PartView"; 4 | $app([Basics], _ => { 5 | _.h1("Title"); 6 | _(PartView)("Part 1", "Content 1"); 7 | _(PartView)("Part 2", "Content 2"); 8 | _(PartView)("Part 3", "Content 3"); 9 | _(PartView)("Part 4", "Content 4"); 10 | }); 11 | declare module "refina" { 12 | interface Plugins { 13 | Basics: typeof Basics; 14 | } 15 | } -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-8/_hint/App/PartView.ts: -------------------------------------------------------------------------------- 1 | import { $view, Content, _ } from "refina"; 2 | export default $view((title: string, content: Content) => { 3 | _.h1(title); 4 | _.p(content); 5 | }); -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-8/description.md: -------------------------------------------------------------------------------- 1 | # 视图 2 | 3 | 视图是一个函数,它渲染一部分页面。 4 | 5 | 他被用来将页面分割为相对独立的几个部分,并且可以复用。 6 | 7 | 使用 `$view` 函数来定义视图: 8 | 9 | ```ts 10 | import { $view, _ } from "refina"; 11 | 12 | export default $view((id: number) => { 13 | _.h1(`Card ${id}`); 14 | }); 15 | ``` 16 | 17 | To use a view, just call the context object: 18 | 19 | ```ts 20 | import { $app } from "refina"; 21 | import CardView from "./CardView"; 22 | 23 | $app([], _ => { 24 | _(CardView)("1"); 25 | _(CardView)("2"); 26 | _(CardView)("3"); 27 | }); 28 | ``` 29 | 30 | 现在,试着将编辑器中重复的代码提炼为一个视图,并使用该视图来渲染页面。 31 | -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-9/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | 3 | /* 4 | Rewrite the following HTML using low-level rendering functions. 5 | 6 | 7 | 8 | 9 | 10 | */ 11 | 12 | $app([], _ => {}); -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-9/_hint/App/App.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | 3 | /* 4 | Rewrite the following HTML using low-level rendering functions. 5 | 6 | 7 | 8 | 9 | 10 | */ 11 | 12 | $app([], _ => { 13 | _.$css`position:fixed;`; 14 | _._svgSvg({ 15 | id: "svg1", 16 | width: 100, 17 | height: 100 18 | }, _ => { 19 | _._svgPath({ 20 | d: "M 10 10 H 90 V 90 H 10 Z", 21 | fill: "red" 22 | }); 23 | _._svgCircle({ 24 | cx: 50, 25 | cy: 50, 26 | r: 40, 27 | stroke: "blue", 28 | fill: "none" 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /docs/zh/tutorial/src/step-9/description.md: -------------------------------------------------------------------------------- 1 | # 底层渲染 2 | 3 | 在之前的步骤中,我们使用组件来渲染页面。 但是,有时你想要渲染的东西并没有对应的组件。 4 | 5 | 底层渲染函数允许你渲染任何的 DOM 元素。 以及,组件归根结底也是通过这些底层渲染函数实现的。 6 | 7 | 下面是一个示例: 8 | 9 | ```ts 10 | let count = 0; 11 | 12 | const app = $app([], _ => { 13 | _.$cls`my-button`; // _.$cls and _.$css are also available 14 | _._button( 15 | // Attributes (optional) 16 | { 17 | id: "my-div", 18 | onclick() { 19 | count++; 20 | app.update(); // Update the application 21 | }, 22 | }, 23 | // Content (optional) 24 | _ => _.span(`Count is ${count}`), 25 | // Event listeners (optional) 26 | { 27 | hover: { 28 | mousemove(ev) { 29 | console.log(ev.clientX, ev.clientY); 30 | }, 31 | capture: true, 32 | }, 33 | }, 34 | ); 35 | }); 36 | ``` 37 | 38 | 这比使用组件要不那么方便。 当你觉得需要使用底层渲染函数,而不是使用组件是,请考虑这是否是必须的:底层渲染函数提供的额外功能真的值得花时间事先吗? 往往一些细枝末节且特别的功能会耗费开发者大量的时间,却并没有成比例的用处。 39 | 40 | 底层渲染函数的名字遵循这样的规律: 41 | 42 | - **HTML**: `_._div` 是 `

` 43 | - **Web components**: `_._custom_element` 是 `` 44 | - **SVG**: `_._svgCircle` 是 `` 45 | 46 | 现在,试着将注释中的 HTML 代码通过底层渲染函数重写。 47 | -------------------------------------------------------------------------------- /docs/zh/tutorial/tutorial.data.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { createMarkdownRenderer } from "vitepress"; 3 | import { readExamples } from "../../helpers/loader"; 4 | import type { ExampleData } from "../../helpers/utils"; 5 | export declare const data: Record; 6 | export default { 7 | watch: "./src/**", 8 | async load() { 9 | const md = await createMarkdownRenderer(process.cwd(), undefined, "/"); 10 | const files = readExamples(path.resolve(__dirname, "./src")); 11 | for (const step in files) { 12 | const stepFiles = files[step]; 13 | const desc = (stepFiles["description.md"] as string); 14 | if (desc) { 15 | stepFiles["description.md"] = md.render(desc); 16 | } 17 | } 18 | return files; 19 | } 20 | }; -------------------------------------------------------------------------------- /packages/basic-components/README.md: -------------------------------------------------------------------------------- 1 | # `@refina/basic-components` 2 | 3 | [![npm](https://img.shields.io/npm/v/%40refina%2Fbasic-components?color=green)](https://www.npmjs.com/package/@refina/basic-components) 4 | 5 | The basic components of Refina. 6 | 7 | To learn more about Refina, please visit: 8 | 9 | - [Documentation](https://refina.vercel.app). 10 | - [Getting Started](https://refina.vercel.app/guide/introduction.html) 11 | - [GitHub Repository](https://github.com/refinajs/refina). 12 | - [Examples](https://gallery.refina.vercel.app). 13 | 14 | ## Usage 15 | 16 | ### Install the Plugin to App 17 | 18 | ```ts 19 | import Basics from "@refina/basic-components"; 20 | 21 | $app([Basics], _ => { 22 | // ... 23 | }); 24 | ``` 25 | 26 | ### Use the Components 27 | 28 | All the components of this package doesn't have a prefix. 29 | 30 | ```ts 31 | if (_.button("button")) { 32 | console.log("Button clicked"); 33 | } 34 | ``` 35 | 36 | ## Included Components 37 | 38 | - `a` 39 | - `br` 40 | - `button` 41 | - `div` 42 | - `h1` 43 | - `h2` 44 | - `h3` 45 | - `h4` 46 | - `h5` 47 | - `h6` 48 | - `img` 49 | - `input` 50 | - `textInput` 51 | - `passwordInput` 52 | - `checkbox` 53 | - `label` 54 | - `li` 55 | - `ol` 56 | - `p` 57 | - `span` 58 | - `table` 59 | - `th` 60 | - `td` 61 | - `textarea` 62 | - `ul` 63 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/a.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class BasicA extends Component { 4 | $main(children: Content, href: string, target?: string): void { 5 | _._a({ href, target }, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/br.ts: -------------------------------------------------------------------------------- 1 | import { Component, _ } from "refina"; 2 | 3 | export class BasicBr extends Component { 4 | $main(): void { 5 | _._br(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/button.ts: -------------------------------------------------------------------------------- 1 | import { Content, TriggerComponent, _ } from "refina"; 2 | 3 | export class BasicButton extends TriggerComponent { 4 | $main( 5 | children: Content, 6 | disabled?: boolean, 7 | ): this is { 8 | $ev: MouseEvent; 9 | } { 10 | _._button( 11 | { 12 | onclick: this.$fire, 13 | disabled, 14 | type: "button", 15 | }, 16 | children, 17 | ); 18 | return this.$fired; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/checkbox.ts: -------------------------------------------------------------------------------- 1 | import { Model, TriggerComponent, _, elementRef, unwrap } from "refina"; 2 | 3 | export class BasicCheckbox extends TriggerComponent { 4 | inputRef = elementRef<"input">(); 5 | $main(checked: Model): this is { 6 | $ev: boolean; 7 | } { 8 | _.$ref(this.inputRef); 9 | _._input({ 10 | type: "checkbox", 11 | checked: unwrap(checked), 12 | onchange: () => { 13 | const newChecked = this.inputRef.current!.node.checked; 14 | this.$updateModel(checked, newChecked); 15 | this.$fire(newChecked); 16 | }, 17 | }); 18 | return this.$fired; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/div.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class BasicDiv extends Component { 4 | $main(children?: Content): void { 5 | _._div({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/h.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class BasicH1 extends Component { 4 | $main(children: Content): void { 5 | _._h1({}, children); 6 | } 7 | } 8 | 9 | export class BasicH2 extends Component { 10 | $main(children: Content): void { 11 | _._h2({}, children); 12 | } 13 | } 14 | 15 | export class BasicH3 extends Component { 16 | $main(children: Content): void { 17 | _._h3({}, children); 18 | } 19 | } 20 | 21 | export class BasicH4 extends Component { 22 | $main(children: Content): void { 23 | _._h4({}, children); 24 | } 25 | } 26 | 27 | export class BasicH5 extends Component { 28 | $main(children: Content): void { 29 | _._h5({}, children); 30 | } 31 | } 32 | 33 | export class BasicH6 extends Component { 34 | $main(children: Content): void { 35 | _._h6({}, children); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/img.ts: -------------------------------------------------------------------------------- 1 | import { Component, _ } from "refina"; 2 | 3 | export class BasicImg extends Component { 4 | $main(src: string, alt?: string): void { 5 | _._img({ 6 | src, 7 | alt, 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./a"; 2 | export * from "./br"; 3 | export * from "./button"; 4 | export * from "./checkbox"; 5 | export * from "./div"; 6 | export * from "./h"; 7 | export * from "./img"; 8 | export * from "./input"; 9 | export * from "./label"; 10 | export * from "./li"; 11 | export * from "./ol"; 12 | export * from "./p"; 13 | export * from "./span"; 14 | export * from "./table"; 15 | export * from "./textarea"; 16 | export * from "./ul"; 17 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/input.ts: -------------------------------------------------------------------------------- 1 | import { Model, TriggerComponent, _, elementRef, unwrap } from "refina"; 2 | 3 | export class BasicInput extends TriggerComponent { 4 | inputRef = elementRef<"input">(); 5 | type = "text"; 6 | $main( 7 | value: Model, 8 | disabled?: boolean, 9 | placeholder?: string, 10 | ): this is { 11 | $ev: string; 12 | } { 13 | _.$ref(this.inputRef); 14 | _._input({ 15 | type: this.type, 16 | disabled, 17 | placeholder, 18 | value: unwrap(value), 19 | oninput: () => { 20 | const newValue = this.inputRef.current!.node.value; 21 | this.$updateModel(value, newValue); 22 | this.$fire(newValue); 23 | }, 24 | }); 25 | return this.$fired; 26 | } 27 | } 28 | 29 | export class BasicTextInput extends BasicInput { 30 | type = "text"; 31 | } 32 | 33 | export class BasicPasswordInput extends BasicInput { 34 | type = "password"; 35 | } 36 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/label.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class BasicLabel extends Component { 4 | $main(children: Content): void { 5 | _._label({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/li.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class BasicLi extends Component { 4 | $main(children: Content): void { 5 | _._li({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/ol.ts: -------------------------------------------------------------------------------- 1 | import { Component, LoopKey, _ } from "refina"; 2 | 3 | export class BasicOl extends Component { 4 | $main( 5 | data: Iterable, 6 | key: LoopKey, 7 | itemView: (item: T, index: number) => void, 8 | ): void { 9 | _._ol({}, _ => 10 | _.for(data, key, (item, index) => { 11 | itemView(item, index); 12 | }), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/p.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class BasicP extends Component { 4 | $main(children: Content): void { 5 | _._p({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/span.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class BasicSpan extends Component { 4 | $main(children?: Content): void { 5 | _._span({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/table.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, LoopKey, _, byIndex } from "refina"; 2 | 3 | export class BasicTable extends Component { 4 | $main( 5 | data: Iterable, 6 | head: Content[] | Content, 7 | key: LoopKey, 8 | row: (item: T, index: number) => void, 9 | ): void { 10 | _._table({}, _ => { 11 | _._thead({}, _ => { 12 | if (Array.isArray(head)) { 13 | _._tr({}, _ => 14 | _.for(head, byIndex, item => { 15 | _._th({}, item); 16 | }), 17 | ); 18 | } else { 19 | _.embed(head); 20 | } 21 | }); 22 | _._tbody({}, _ => { 23 | _.for(data, key, (item, index) => { 24 | _._tr({}, _ => { 25 | row(item, index); 26 | }); 27 | }); 28 | }); 29 | }); 30 | } 31 | } 32 | 33 | export class BasicTh extends Component { 34 | $main(children: Content): void { 35 | _._th({}, children); 36 | } 37 | } 38 | 39 | export class BasicTd extends Component { 40 | $main(children: Content): void { 41 | _._td({}, children); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/textarea.ts: -------------------------------------------------------------------------------- 1 | import { Model, TriggerComponent, _, elementRef, unwrap } from "refina"; 2 | 3 | export class BasicTextarea extends TriggerComponent { 4 | inputRef = elementRef<"textarea">(); 5 | $main( 6 | value: Model, 7 | disabled?: boolean, 8 | placeholder?: string, 9 | ): this is { 10 | $ev: string; 11 | } { 12 | _.$ref(this.inputRef); 13 | _._textarea({ 14 | disabled, 15 | placeholder, 16 | value: unwrap(value), 17 | oninput: () => { 18 | const newValue = this.inputRef.current!.node.value; 19 | this.$updateModel(value, newValue); 20 | this.$fire(newValue); 21 | }, 22 | }); 23 | return this.$fired; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/basic-components/src/components/ul.ts: -------------------------------------------------------------------------------- 1 | import { Component, LoopKey, _ } from "refina"; 2 | 3 | export class BasicUl extends Component { 4 | $main( 5 | data: Iterable, 6 | key: LoopKey, 7 | itemView: (item: T, index: number) => void, 8 | ): void { 9 | _._ul({}, _ => 10 | _.for(data, key, (item, index) => { 11 | itemView(item, index); 12 | }), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/basic-components/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "refina"; 2 | import * as c from "./components"; 3 | 4 | export default { 5 | name: "basic-components", 6 | components: { 7 | a: c.BasicA, 8 | br: c.BasicBr, 9 | button: c.BasicButton, 10 | checkbox: c.BasicCheckbox, 11 | div: c.BasicDiv, 12 | h1: c.BasicH1, 13 | h2: c.BasicH2, 14 | h3: c.BasicH3, 15 | h4: c.BasicH4, 16 | h5: c.BasicH5, 17 | h6: c.BasicH6, 18 | img: c.BasicImg, 19 | input: c.BasicInput, 20 | textInput: c.BasicTextInput, 21 | passwordInput: c.BasicPasswordInput, 22 | label: c.BasicLabel, 23 | li: c.BasicLi, 24 | ol: c.BasicOl, 25 | p: c.BasicP, 26 | span: c.BasicSpan, 27 | table: c.BasicTable, 28 | td: c.BasicTd, 29 | th: c.BasicTh, 30 | textarea: c.BasicTextarea, 31 | ul: c.BasicUl, 32 | }, 33 | } satisfies Plugin; 34 | 35 | export * from "./components"; 36 | -------------------------------------------------------------------------------- /packages/basic-components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/basic-components/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { RefinaLib } from "vite-plugin-refina"; 3 | 4 | export default defineConfig({ 5 | plugins: [RefinaLib()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/bundles/README.md: -------------------------------------------------------------------------------- 1 | # `@refina/bundles` 2 | 3 | [![npm](https://img.shields.io/npm/v/%40refina%2Fbundles?color=green)](https://www.npmjs.com/package/@refina/bundles) 4 | 5 | The bundled version of Refina libs. 6 | 7 | To learn more about Refina, please visit: 8 | 9 | - [Documentation](https://refina.vercel.app). 10 | - [Getting Started](https://refina.vercel.app/guide/introduction.html) 11 | - [GitHub Repository](https://github.com/refinajs/refina). 12 | - [Examples](https://gallery.refina.vercel.app). 13 | 14 | ## Usage 15 | 16 | ### Via CDN 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | **Note:** Replace `` with the name of the lib you want to use. 23 | 24 | Available libs: 25 | 26 | - `core` 27 | - `basic-components` 28 | - `mdui` 29 | - `fluentui` 30 | - `transformer` 31 | -------------------------------------------------------------------------------- /packages/bundles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/bundles", 3 | "version": "0.6.1", 4 | "description": "The bundled version of Refina libs.", 5 | "keywords": [ 6 | "refina", 7 | "bundle" 8 | ], 9 | "files": [ 10 | "dist", 11 | "README.md" 12 | ], 13 | "type": "module", 14 | "exports": { 15 | "./*": "./dist/*" 16 | }, 17 | "scripts": { 18 | "dev": "vite", 19 | "build": "vite build", 20 | "prepublishOnly": "npm run build" 21 | }, 22 | "author": "_Kerman", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/KermanX/refina" 26 | }, 27 | "readme": "https://github.com/KermanX/refina#readme", 28 | "bugs": "https://github.com/KermanX/refina/issues", 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@types/node": "^20.11.26", 32 | "terser": "^5.29.1", 33 | "vite": "^4.5.2", 34 | "vite-plugin-refina": "workspace:^" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/bundles/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "node:path"; 2 | import { fileURLToPath } from "node:url"; 3 | import { defineConfig } from "vite"; 4 | import Refina, { RefinaTransformer } from "vite-plugin-refina"; 5 | 6 | const libs = { 7 | core: "core/dist/index.js", 8 | "basic-components": "basic-components/dist/index.js", 9 | mdui: "mdui/dist/index.js", 10 | fluentui: "fluentui/dist/index.js", 11 | transformer: "transformer/src/index.ts", 12 | }; 13 | 14 | const __dirname = dirname(fileURLToPath(import.meta.url)); 15 | 16 | const transformer = new RefinaTransformer(); 17 | transformer.ckeyPrefix = "b_"; // means "bundle" 18 | 19 | export default defineConfig({ 20 | plugins: [ 21 | Refina({ 22 | transformer, 23 | }) as any, 24 | ], 25 | build: { 26 | lib: { 27 | // Could also be a dictionary or array of multiple entry points 28 | entry: Object.fromEntries( 29 | Object.entries(libs).map(([k, v]) => [k, resolve(__dirname, "..", v)]), 30 | ), 31 | formats: ["es"], 32 | }, 33 | minify: "terser", 34 | outDir: "dist", 35 | emptyOutDir: true, 36 | sourcemap: true, 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # The Refina Core 2 | 3 | [![npm](https://img.shields.io/npm/v/refina?color=green)](https://www.npmjs.com/package/refina) 4 | 5 | This package contains the core of Refina. 6 | 7 | To learn more about Refina, please visit: 8 | 9 | - [Documentation](https://refina.vercel.app). 10 | - [GitHub Repository](https://github.com/refinajs/refina). 11 | - [Why Refina](https://refina.vercel.app/guide/why.html). 12 | - [Examples](https://gallery.refina.vercel.app). 13 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refina", 3 | "version": "0.6.1", 4 | "description": "An extremely refined web framework.", 5 | "keywords": [ 6 | "refina", 7 | "framework", 8 | "web", 9 | "typescript" 10 | ], 11 | "files": [ 12 | "src", 13 | "dist", 14 | "tsconfig.json", 15 | "README.md" 16 | ], 17 | "type": "module", 18 | "main": "./dist/index.js", 19 | "types": "./dist/index.d.ts", 20 | "exports": { 21 | ".": { 22 | "types": "./dist/index.d.ts", 23 | "import": "./dist/index.js", 24 | "default": "./dist/index.js" 25 | }, 26 | "./*": "./*" 27 | }, 28 | "scripts": { 29 | "check": "tsc --noEmit", 30 | "build": "vite build", 31 | "dev": "vite build --watch", 32 | "prepublishOnly": "npm run check && npm run build" 33 | }, 34 | "author": "_Kerman", 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/KermanX/refina" 38 | }, 39 | "readme": "https://github.com/KermanX/refina#readme", 40 | "bugs": "https://github.com/KermanX/refina/issues", 41 | "license": "MIT", 42 | "devDependencies": { 43 | "@refina/tsconfig": "workspace:^", 44 | "typescript": "^5.4.2", 45 | "vite": "^4.5.2", 46 | "vite-plugin-refina": "workspace:^" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/core/src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./app"; 2 | export * from "./hooks"; 3 | export * from "./plugin"; 4 | -------------------------------------------------------------------------------- /packages/core/src/component/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./component"; 2 | export * from "./trigger"; 3 | -------------------------------------------------------------------------------- /packages/core/src/component/trigger.ts: -------------------------------------------------------------------------------- 1 | import { _ } from "../context"; 2 | import { Component } from "./component"; 3 | 4 | /** 5 | * The base class of all trigger components. 6 | * 7 | * A trigger component is a component that can fire events with data. 8 | * The return value of the context function is whether the event is fired. 9 | */ 10 | export abstract class TriggerComponent extends Component { 11 | private $_fired = false; 12 | private $_firedData: Ev; 13 | 14 | /** 15 | * Fire an event with data. 16 | * 17 | * @param data The data of the event. 18 | */ 19 | protected readonly $fire = (data: Ev) => { 20 | this.$_fired = true; 21 | this.$_firedData = data; 22 | this.$app.recv(); 23 | }; 24 | 25 | /** 26 | * Create a function that fires an event with data. 27 | * 28 | * @param data The data of the event. 29 | * @returns A function that fires the event. 30 | */ 31 | protected readonly $fireWith = (data: Ev) => () => this.$fire(data); 32 | 33 | protected get $fired() { 34 | if (_.$recvContext && this.$_fired) { 35 | this.$_fired = false; 36 | (_ as any).$ev = this.$_firedData; 37 | return true; 38 | } else { 39 | return false; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The state of the app. 3 | */ 4 | export enum AppState { 5 | /** 6 | * The app is not running. 7 | */ 8 | IDLE = "idle", 9 | 10 | /** 11 | * The app is running to update DOM. 12 | */ 13 | UPDATE = "update", 14 | 15 | /** 16 | * The app is running to receive events. 17 | */ 18 | RECV = "recv", 19 | } 20 | 21 | // Add the `DEV` property to `import.meta.env`. 22 | // This property is used to check if the app is running in development mode. 23 | // See https://vitejs.dev/guide/env-and-mode.html#env-variables 24 | declare global { 25 | interface ImportMetaEnv { 26 | /** 27 | * Whether the app is running in development mode. 28 | * 29 | * In development mode, Refina will print detailed runtime information to the console, 30 | * and extra checks will be performed to ensure the app is running correctly. 31 | */ 32 | DEV: boolean; 33 | } 34 | 35 | interface ImportMeta { 36 | readonly env: ImportMetaEnv; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/context/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./base"; 2 | export * from "./recv"; 3 | export * from "./update"; 4 | -------------------------------------------------------------------------------- /packages/core/src/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./model"; 2 | export * from "./propModel"; 3 | export * from "./ref"; 4 | -------------------------------------------------------------------------------- /packages/core/src/data/propModel.ts: -------------------------------------------------------------------------------- 1 | import { toModelValue, JustModel } from "./model"; 2 | 3 | /** 4 | * Create a model that references a property of an object. 5 | * 6 | * @param obj The object of the property to reference. 7 | * @param key The key of the property to reference. 8 | * @returns The model. 9 | */ 10 | export function propModel(obj: T, key: K) { 11 | return { 12 | obj, 13 | key, 14 | 15 | get value() { 16 | return this.obj[this.key]; 17 | }, 18 | set value(v: T[K]) { 19 | this.obj[this.key] = v; 20 | }, 21 | 22 | [toModelValue]() { 23 | return this.value; 24 | }, 25 | [Symbol.toPrimitive]() { 26 | return this.value; 27 | }, 28 | } as JustModel & { obj: T; key: K }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/src/dom/body.ts: -------------------------------------------------------------------------------- 1 | import { HTMLElementComponent } from "./element"; 2 | 3 | /** 4 | * The body component that manages classes, styles and event listeners of the `` element. 5 | */ 6 | export class DOMBodyComponent extends HTMLElementComponent { 7 | updateDOM(): null { 8 | this.applyCls(); 9 | this.applyCss(); 10 | this.applyEventListeners(); 11 | return null; 12 | } 13 | 14 | insertAfter(_node: ChildNode): never { 15 | throw new Error("Cannot insert body component after another node."); 16 | } 17 | 18 | prependTo(_parent: Element): never { 19 | throw new Error("Cannot prepend body component to DOM tree."); 20 | } 21 | 22 | removeFrom(_parent: Element): never { 23 | throw new Error("Cannot remove body component from DOM tree."); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/dom/content.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../context"; 2 | import { View } from "./view"; 3 | 4 | /** 5 | * Allowed content types of DOM element components. 6 | */ 7 | export type Content = 8 | | string 9 | | number 10 | | View; 11 | 12 | /** 13 | * Bind arguments to content if the content is a view function. 14 | * 15 | * @example 16 | * ```ts 17 | * const content: Content<[string]> = ...; 18 | * const boundContent: Content = bindArgsToContent(content, "Hello, world!"); 19 | * _.div(boundContent); 20 | * ``` 21 | * 22 | * @param content The content to bind arguments to. 23 | * @param args The arguments to bind. 24 | * @returns The bound content. 25 | */ 26 | export function bindArgsToContent( 27 | content: Content, 28 | ...args: Args 29 | ): Content { 30 | if (typeof content === "function") { 31 | return () => content(...args); 32 | } 33 | return content; 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/dom/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./body"; 2 | export * from "./content"; 3 | export * from "./element"; 4 | export * from "./node"; 5 | export * from "./portal"; 6 | export * from "./root"; 7 | export * from "./text"; 8 | export * from "./view"; 9 | export * from "./window"; 10 | -------------------------------------------------------------------------------- /packages/core/src/dom/text.ts: -------------------------------------------------------------------------------- 1 | import { DOMNodeComponent } from "./node"; 2 | 3 | /** 4 | * Component that contains a text node. 5 | */ 6 | export class TextNodeComponent extends DOMNodeComponent { 7 | updateDOM(): null { 8 | return null; 9 | } 10 | } 11 | 12 | // Add text node function to context. 13 | declare module ".." { 14 | interface ContextFuncs { 15 | /** 16 | * Render a text node. 17 | * 18 | * @example 19 | * ```ts 20 | * _.t`Hello, world!`; 21 | * _.t(message); 22 | * ``` 23 | */ 24 | t(template: TemplateStringsArray, ...args: unknown[]): void; 25 | t(text: string): void; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/dom/view.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../context"; 2 | 3 | export type View = ( 4 | ...args: Args 5 | ) => void; 6 | 7 | export function $view( 8 | view: View, 9 | ) { 10 | return view; 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/dom/window.ts: -------------------------------------------------------------------------------- 1 | import { DOMElementComponent } from "./element"; 2 | 3 | /** 4 | * The window component that manages event listeners of Window. 5 | * 6 | * **Note**: Window is not a DOM element in fact, 7 | * but we just treat it as a DOM element to reuse the code to manage event listeners. 8 | */ 9 | export class DOMWindowComponent extends DOMElementComponent { 10 | updateDOM(): null { 11 | this.applyEventListeners(); 12 | return null; 13 | } 14 | 15 | addCls(_classes: string): void { 16 | throw new Error("Cannot add classes to window."); 17 | } 18 | 19 | addCss(_style: string) { 20 | throw new Error("Cannot add styles to window."); 21 | } 22 | 23 | addAttrs(_attrs: Partial): void { 24 | throw new Error("Cannot add attrs to window."); 25 | } 26 | 27 | insertAfter(_node: ChildNode): never { 28 | throw new Error("Cannot insert window component after another node."); 29 | } 30 | 31 | prependTo(_parent: Element): never { 32 | throw new Error("Cannot prepend window component to DOM tree."); 33 | } 34 | 35 | removeFrom(_parent: Element): never { 36 | throw new Error("Cannot remove window component from DOM tree."); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./app"; 2 | export * from "./component"; 3 | export * from "./constants"; 4 | export * from "./context"; 5 | export * from "./data"; 6 | export * from "./dom"; 7 | export * from "./patch"; 8 | export * from "./prelude"; 9 | -------------------------------------------------------------------------------- /packages/core/src/patch/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./patch"; 2 | export * from "./parser"; 3 | -------------------------------------------------------------------------------- /packages/core/src/patch/parser.ts: -------------------------------------------------------------------------------- 1 | import { PatchTarget } from "./patch"; 2 | 3 | export type SelectorNode = (name: string) => false | SelectorNode | PatchTarget; 4 | 5 | export function parseSelector( 6 | selector: string, 7 | patchTarget: PatchTarget, 8 | ): SelectorNode { 9 | if (!/^(>([a-z_][a-z0-9_]*|\*)(:(\d+|\*))?)+$/i.test(selector)) { 10 | throw new Error(`Invalid selector: ${selector}`); 11 | } 12 | const parts = selector.split(">").slice(1); 13 | let node: SelectorNode | undefined; 14 | for (let i = parts.length - 1; i >= 0; i--) { 15 | const part = parts[i]; 16 | const next = node ?? patchTarget; 17 | const [name, index] = part.split(":"); 18 | if (index === "*") { 19 | if (name === "*") { 20 | node = () => next; 21 | } else { 22 | node = v => v === name && next; 23 | } 24 | } else { 25 | let n = index ? parseInt(index) : 1; 26 | if (name === "*") { 27 | node = () => { 28 | n--; 29 | return n === 0 ? next : false; 30 | }; 31 | } else { 32 | node = v => { 33 | if (v !== name) return false; 34 | n--; 35 | return n === 0 ? next : false; 36 | }; 37 | } 38 | } 39 | } 40 | return () => node!; 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/prelude/documentTitle.ts: -------------------------------------------------------------------------------- 1 | import { $contextFunc, _ } from "../context"; 2 | 3 | export const documentTitle = $contextFunc(_ckey => 4 | /** 5 | * Set the document title. 6 | * 7 | * **Warning**: The document title will be overwritten by the last call to this function. 8 | */ 9 | (title: string) => { 10 | if (_.$updateContext) { 11 | document.title = title; 12 | } 13 | return true as const; 14 | }, 15 | ); 16 | -------------------------------------------------------------------------------- /packages/core/src/prelude/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "../app"; 2 | import { _await } from "./await"; 3 | import { documentTitle } from "./documentTitle"; 4 | import { AsyncEmbed, Embed } from "./embed"; 5 | import { _for, forTimes } from "./loop"; 6 | import { portal } from "./portal"; 7 | import { provide } from "./provide"; 8 | import { now, setInterval } from "./timing"; 9 | import { useModel } from "./useModel"; 10 | 11 | const components = { 12 | asyncEmbed: AsyncEmbed, 13 | embed: Embed, 14 | }; 15 | 16 | const contextFuncs = { 17 | await: _await, 18 | documentTitle, 19 | for: _for, 20 | forTimes, 21 | portal, 22 | provide, 23 | now, 24 | setInterval, 25 | useModel, 26 | }; 27 | 28 | /** 29 | * The plugin that is always installed on the app. 30 | */ 31 | export const Prelude = { 32 | name: "prelude", 33 | components, 34 | contextFuncs, 35 | onError(error: unknown) { 36 | console.error(error); 37 | }, 38 | } satisfies Plugin; 39 | 40 | declare module ".." { 41 | interface Plugins { 42 | prelude: typeof Prelude; 43 | } 44 | } 45 | 46 | export type { AsyncContentLoader } from "./embed"; 47 | export { byIndex, bySelf, type LoopKey } from "./loop"; 48 | -------------------------------------------------------------------------------- /packages/core/src/prelude/portal.ts: -------------------------------------------------------------------------------- 1 | import { $contextFunc, _ } from "../context"; 2 | import { Content, DOMPortalComponent } from "../dom"; 3 | 4 | export const portal = $contextFunc(ckey => 5 | /** 6 | * Render content to the end of the root element. 7 | * 8 | * This is usefull when you want to render a dialog or a tooltip 9 | * that should not be affected by the parent element's styles. 10 | */ 11 | (children: Content): void => { 12 | let portal = _.$lowlevel.$$currentRefNode[ckey] as 13 | | DOMPortalComponent 14 | | undefined; 15 | if (!portal) { 16 | portal = new DOMPortalComponent(_.$app.root.node); 17 | _.$lowlevel.$$currentRefNode[ckey] = portal; 18 | } 19 | 20 | const updateContext = _.$updateContext?.$lowlevel; 21 | if (updateContext) { 22 | updateContext.$$fulfillRef(portal); 23 | 24 | updateContext.$app.root.pendingPortals.push(portal); 25 | 26 | updateContext.$$updateDOMContent(portal, children); 27 | } else { 28 | const recvContext = _.$recvContext!.$lowlevel; 29 | 30 | recvContext.$$processDOMElement(ckey, children); 31 | } 32 | }, 33 | ); 34 | -------------------------------------------------------------------------------- /packages/core/src/prelude/timing.ts: -------------------------------------------------------------------------------- 1 | import { $contextFunc, _ } from "../context"; 2 | 3 | export const now = $contextFunc(ckey => 4 | /** 5 | * Get the current time in milliseconds. 6 | * 7 | * For every `precisionMs`, an `UPDATE` call will be scheduled to refresh the time. 8 | * 9 | * @param precisionMs The precision of the time in milliseconds. 10 | */ 11 | (precisionMs = 1000): number => { 12 | const refTreeNode = _.$lowlevel.$$currentRefNode; 13 | if (_.$updateContext && !refTreeNode[ckey]) { 14 | refTreeNode[ckey] = true; 15 | setTimeout(() => { 16 | delete refTreeNode[ckey]; 17 | _.$app.update(); 18 | }, precisionMs); 19 | } 20 | return Date.now(); 21 | }, 22 | ); 23 | 24 | export const setInterval = $contextFunc(ckey => 25 | /** 26 | * Schedule a callback to be called every `interval` milliseconds. 27 | */ 28 | (callback: () => void, interval: number): void => { 29 | const refTreeNode = _.$lowlevel.$$currentRefNode; 30 | if (_.$updateContext && !refTreeNode[ckey]) { 31 | refTreeNode[ckey] = true; 32 | setTimeout(() => { 33 | delete refTreeNode[ckey]; 34 | callback(); 35 | _.$app.update(); 36 | }, interval); 37 | } 38 | }, 39 | ); 40 | -------------------------------------------------------------------------------- /packages/core/src/prelude/useModel.ts: -------------------------------------------------------------------------------- 1 | import { $contextFunc, _ } from "../context"; 2 | import { JustModel, model } from "../data"; 3 | 4 | export const useModel = $contextFunc(ckey => 5 | /** 6 | * Create a model. 7 | * 8 | * @param init The initial value. 9 | */ 10 | (init: T): JustModel => { 11 | const refTreeNode = _.$lowlevel.$$currentRefNode; 12 | if (!refTreeNode[ckey]) { 13 | refTreeNode[ckey] = model(init); 14 | } 15 | return refTreeNode[ckey] as JustModel; 16 | }, 17 | ); 18 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { RefinaLib } from "vite-plugin-refina"; 3 | 4 | export default defineConfig({ 5 | plugins: [RefinaLib()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/creator/README.md: -------------------------------------------------------------------------------- 1 | # `@refina/creator` 2 | 3 | [![npm](https://img.shields.io/npm/v/create-refina?color=green)](https://www.npmjs.com/package/create-refina) 4 | 5 | The recommended way to start a Vite-powered Refina.js project 6 | 7 | To learn more about Refina, please visit: 8 | 9 | - [Documentation](https://refina.vercel.app). 10 | - [Getting Started](https://refina.vercel.app/guide/introduction.html) 11 | - [GitHub Repository](https://github.com/refinajs/refina). 12 | - [Examples](https://gallery.refina.vercel.app). 13 | 14 | ## Usage 15 | 16 | ```sh 17 | npm create refina@latest 18 | ``` 19 | -------------------------------------------------------------------------------- /packages/creator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-refina", 3 | "version": "0.6.0", 4 | "description": "An easy way to start a Refina project.", 5 | "keywords": [ 6 | "refina", 7 | "creator", 8 | "template" 9 | ], 10 | "bin": { 11 | "create-refina": "dist/index.cjs" 12 | }, 13 | "files": [ 14 | "dist", 15 | "README.md" 16 | ], 17 | "engines": { 18 | "node": ">=v18" 19 | }, 20 | "type": "module", 21 | "scripts": { 22 | "dev": "tsup --watch", 23 | "build": "tsup", 24 | "check": "tsc --noEmit", 25 | "prepublishOnly": "npm run build" 26 | }, 27 | "author": "_Kerman", 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/KermanX/refina" 31 | }, 32 | "readme": "https://github.com/KermanX/refina#readme", 33 | "bugs": "https://github.com/KermanX/refina/issues", 34 | "license": "MIT", 35 | "devDependencies": { 36 | "@types/node": "^20.11.26", 37 | "@types/prompts": "^2.4.9", 38 | "chalk": "^5.3.0", 39 | "latest-version": "^7.0.0", 40 | "prompts": "^2.4.2", 41 | "tsup": "^8.0.2", 42 | "typescript": "^5.4.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/creator/src/templates/app/basics+mdui.ts: -------------------------------------------------------------------------------- 1 | export default (tailwind: boolean) => `/** 2 | * Refina.js + MdUI + Basic-components 3 | */ 4 | 5 | import { $app, model } from "refina"; 6 | import Basics from "@refina/basic-components"; 7 | import MdUI from "@refina/mdui"; 8 | import "./styles.css"; 9 | 10 | let count = 0; 11 | const username = model("Refina"); 12 | 13 | $app([Basics, MdUI], _ => {${tailwind ? `\n _.$cls\`underline\`;` : ""} 14 | _.h1(\`Hello \${username}!\`); 15 | _.mdButton(\`Count is: \${count}\`) && count++; 16 | _.mdTextField(username, "Username"); 17 | }); 18 | 19 | declare module "refina" { 20 | interface Plugins { 21 | Basics: typeof Basics; 22 | MdUI: typeof MdUI; 23 | } 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /packages/creator/src/templates/app/basics.ts: -------------------------------------------------------------------------------- 1 | export default (tailwind: boolean) => `/** 2 | * Refina.js + Basic-components 3 | */ 4 | 5 | import { $app } from "refina"; 6 | import Basics from "@refina/basic-components"; 7 | import "./styles.css"; 8 | 9 | $app([Basics], _ => {${tailwind ? `\n _.$cls\`text-xl font-bold p-4\`;` : ""} 10 | _.h1("Hello, Refina!"); 11 | _.p(_ => {${tailwind ? `\n _.$cls\`block p-2 hover:bg-gray-200\`;` : ""} 12 | _.a("Visit Refina on GitHub", "https://github.com/refinajs/refina");${ 13 | tailwind 14 | ? `\n _.$cls\`block p-2 hover:bg-gray-200\`;` 15 | : "\n _._br();" 16 | } 17 | _.a("Visit Refina's documentation", "https://refina.vercel.app"); 18 | }); 19 | }); 20 | `; 21 | -------------------------------------------------------------------------------- /packages/creator/src/templates/app/intrinsic.ts: -------------------------------------------------------------------------------- 1 | export default (tailwind: boolean) => `/** 2 | * Refina.js with no component library 3 | */ 4 | 5 | import { $app } from "refina"; 6 | import "./styles.css"; 7 | 8 | $app([], _ => {${tailwind ? `\n _.$cls\`text-xl font-bold p-4\`;` : ""} 9 | _._h1({}, "Hello, Refina!"); 10 | _._p({}, _ => {${ 11 | tailwind ? `\n _.$cls\`block p-2 hover:bg-gray-200\`;` : "" 12 | } 13 | _._a( 14 | { 15 | href: "https://github.com/refinajs/refina", 16 | }, 17 | "Visit Refina on GitHub", 18 | );${ 19 | tailwind 20 | ? `\n _.$cls\`block p-2 hover:bg-gray-200\`;` 21 | : "\n _._br();" 22 | } 23 | _._a( 24 | { 25 | href: "https://refina.vercel.app", 26 | }, 27 | "Visit Refina's documentation", 28 | ); 29 | }); 30 | }); 31 | `; 32 | -------------------------------------------------------------------------------- /packages/creator/src/templates/app/mdui.ts: -------------------------------------------------------------------------------- 1 | export default (tailwind: boolean) => `/** 2 | * Refina.js + MdUI 3 | */ 4 | 5 | import { $app, model } from "refina"; 6 | import MdUI from "@refina/mdui"; 7 | import "./styles.css"; 8 | 9 | let count = 0; 10 | const username = model("Refina"); 11 | 12 | $app([MdUI], _ => {${tailwind ? `\n _.$cls\`underline\`;` : ""} 13 | _._h1({}, \`Hello \${username}!\`); 14 | _.mdButton(\`Count is: \${count}\`) && count++; 15 | _.mdTextField(username, "Username"); 16 | }); 17 | 18 | declare module "refina" { 19 | interface Plugins { 20 | MdUI: typeof MdUI; 21 | } 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /packages/creator/src/templates/extensions.ts: -------------------------------------------------------------------------------- 1 | export default (tailwind: boolean, prettier: boolean) => { 2 | if (!tailwind && !prettier) return null; 3 | 4 | const recommendations: string[] = []; 5 | 6 | if (tailwind) { 7 | recommendations.push("bradlc.vscode-tailwindcss"); 8 | } 9 | 10 | if (prettier) { 11 | recommendations.push("esbenp.prettier-vscode"); 12 | } 13 | 14 | return JSON.stringify( 15 | { 16 | recommendations, 17 | }, 18 | null, 19 | 2, 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/creator/src/templates/gitignore.ts: -------------------------------------------------------------------------------- 1 | export default `# Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .idea 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | 28 | *.tsbuildinfo`; 29 | -------------------------------------------------------------------------------- /packages/creator/src/templates/html/base.ts: -------------------------------------------------------------------------------- 1 | export default (head: string) => ` 2 | 3 | ${head} 4 | 5 | 6 | 7 |
8 | 9 | 10 | `; 11 | -------------------------------------------------------------------------------- /packages/creator/src/templates/html/mdui.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | `; 6 | -------------------------------------------------------------------------------- /packages/creator/src/templates/postcssConfig.ts: -------------------------------------------------------------------------------- 1 | export default `export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | `; 8 | -------------------------------------------------------------------------------- /packages/creator/src/templates/prettierrc.ts: -------------------------------------------------------------------------------- 1 | export default `{ 2 | "arrowParens": "avoid" 3 | } 4 | `; 5 | -------------------------------------------------------------------------------- /packages/creator/src/templates/settings.ts: -------------------------------------------------------------------------------- 1 | export default (tailwind: boolean, prettier: boolean) => 2 | JSON.stringify( 3 | { 4 | "editor.guides.bracketPairs": "active", 5 | "editor.quickSuggestions": tailwind 6 | ? { 7 | strings: "on", 8 | } 9 | : undefined, 10 | "tailwindCSS.experimental.classRegex": tailwind 11 | ? [ 12 | ["\\$cls`([\\s\\S]*?)`", "(\\S+)"], 13 | ["\\$cls\\(([^\\)]*)\\)", '\\"(.*?)\\"'], 14 | ["\\$cls\\(([^\\)]*)\\)", "`(.*?)`"], 15 | ["addCls\\(([^\\)]*)\\)", '\\"(.*?)\\"'], 16 | ["addCls\\(([^\\)]*)\\)", "`(.*?)`"], 17 | ["\\$clsFunc`([\\s\\S]*?)`", "(\\S+)"], 18 | ["\\$clsFunc\\(([^\\)]*)\\)", '\\"(.*?)\\"'], 19 | ["\\$clsStr`([\\s\\S]*?)`", "(\\S+)"], 20 | ["\\$clsStr\\(([^\\)]*)\\)", '\\"(.*?)\\"'], 21 | ] 22 | : undefined, 23 | "editor.defaultFormatter": prettier 24 | ? "esbenp.prettier-vscode" 25 | : undefined, 26 | }, 27 | null, 28 | 2, 29 | ); 30 | -------------------------------------------------------------------------------- /packages/creator/src/templates/styles.ts: -------------------------------------------------------------------------------- 1 | export default (tailwind: boolean, mdui: boolean) => 2 | (mdui ? `@import url(@refina/mdui/styles.css);\n` : ``) + 3 | (tailwind 4 | ? (mdui ? `` : `@tailwind base;\n`) + 5 | `@tailwind components;\n@tailwind utilities;\n` 6 | : `\n`); 7 | -------------------------------------------------------------------------------- /packages/creator/src/templates/tailwindConfig.ts: -------------------------------------------------------------------------------- 1 | export default `import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: ["./index.html", "./src/**/*.{js,ts}"], 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | } satisfies Config; 10 | `; 11 | -------------------------------------------------------------------------------- /packages/creator/src/templates/tsconfig.ts: -------------------------------------------------------------------------------- 1 | export default `{ 2 | "extends": "@refina/tsconfig", 3 | "include": ["src"] 4 | } 5 | `; 6 | -------------------------------------------------------------------------------- /packages/creator/src/templates/viteConfig.ts: -------------------------------------------------------------------------------- 1 | export default `import { defineConfig } from "vite"; 2 | import Refina from "vite-plugin-refina"; 3 | 4 | export default defineConfig({ 5 | plugins: [Refina()], 6 | }); 7 | `; 8 | -------------------------------------------------------------------------------- /packages/creator/src/utils/banner.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | 3 | const text = `Refina.js - An extremely refined web framework`; 4 | const startColor = [254, 204, 143]; 5 | const endColor = [255, 85, 0]; 6 | 7 | function getColor(index: number) { 8 | const percent = index / text.length; 9 | const color = startColor.map((start, i) => { 10 | const end = endColor[i]; 11 | const value = Math.round(start + (end - start) * percent); 12 | return value; 13 | }); 14 | return color as [number, number, number]; 15 | } 16 | 17 | function getColoredText() { 18 | return text 19 | .split("") 20 | .map((char, index) => { 21 | const color = getColor(index); 22 | return chalk.rgb(...color)(char); 23 | }) 24 | .join(""); 25 | } 26 | 27 | export const banner = getColoredText(); 28 | -------------------------------------------------------------------------------- /packages/creator/src/utils/pkgManager.ts: -------------------------------------------------------------------------------- 1 | const userAgent = process.env.npm_config_user_agent ?? ""; 2 | export const packageManager = /pnpm/.test(userAgent) 3 | ? "pnpm" 4 | : /yarn/.test(userAgent) 5 | ? "yarn" 6 | : "npm"; 7 | 8 | export const runCommand = packageManager === "npm" ? "npm run" : packageManager; 9 | -------------------------------------------------------------------------------- /packages/creator/src/utils/pkgName.ts: -------------------------------------------------------------------------------- 1 | // Copied from: https://github.com/vuejs/create-vue/blob/main/index.ts 2 | 3 | export function isValidPackageName(projectName: string) { 4 | return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test( 5 | projectName, 6 | ); 7 | } 8 | 9 | export function toValidPackageName(projectName: string) { 10 | return projectName 11 | .trim() 12 | .toLowerCase() 13 | .replace(/\s+/g, "-") 14 | .replace(/^[._]/, "") 15 | .replace(/[^a-z0-9-~]+/g, "-"); 16 | } 17 | -------------------------------------------------------------------------------- /packages/creator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "target": "ES2019", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "lib": ["ES2019"], 8 | "strict": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/creator/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from "tsup"; 2 | 3 | export const tsup: Options = { 4 | entry: ["./src/index.ts"], 5 | format: ["cjs"], 6 | noExternal: ["chalk", "prompts", "lastest-version"], 7 | target: "es2019", 8 | splitting: false, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/examples/fluentui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/examples/fluentui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/example-fluentui", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite --open", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "tsc --noEmit" 11 | }, 12 | "exports": { 13 | ".": "./src/app.ts" 14 | }, 15 | "devDependencies": { 16 | "@refina/tsconfig": "workspace:^", 17 | "typescript": "^5.4.2", 18 | "vite": "^4.5.2", 19 | "vite-plugin-inspect": "^0.7.42", 20 | "vite-plugin-refina": "workspace:^" 21 | }, 22 | "dependencies": { 23 | "@refina/fluentui": "workspace:^", 24 | "@refina/fluentui-icons": "workspace:^", 25 | "refina": "workspace:^" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/examples/fluentui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/examples/fluentui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, Plugin } from "vite"; 2 | import Inspect from "vite-plugin-inspect"; 3 | import Refina from "vite-plugin-refina"; 4 | 5 | export default defineConfig({ 6 | plugins: [Inspect(), Refina() as Plugin[]], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/examples/gallery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/examples/gallery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/gallery", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite --open", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "tsc --noEmit" 11 | }, 12 | "devDependencies": { 13 | "@refina/tsconfig": "workspace:^", 14 | "autoprefixer": "^10.4.18", 15 | "postcss": "^8.4.35", 16 | "tailwindcss": "^3.4.1", 17 | "typescript": "^5.4.2", 18 | "vite": "^4.5.2", 19 | "vite-plugin-ms-clarity": "^1.0.0", 20 | "vite-plugin-refina": "workspace:^" 21 | }, 22 | "dependencies": { 23 | "@refina/basic-components": "workspace:^", 24 | "@refina/example-fluentui": "workspace:^", 25 | "@refina/example-gh-login": "workspace:^", 26 | "@refina/example-mdui": "workspace:^", 27 | "refina": "workspace:^" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/examples/gallery/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/examples/gallery/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /packages/examples/gallery/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/examples/gallery/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, defineConfig } from "vite"; 2 | import Refina from "vite-plugin-refina"; 3 | import MsClarity from "vite-plugin-ms-clarity"; 4 | 5 | export default defineConfig({ 6 | plugins: [Refina() as Plugin[], MsClarity("k9vjx99oj1")], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/examples/gh-login/assets/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/examples/gh-login/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/examples/gh-login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/example-gh-login", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite --open", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "tsc --noEmit" 11 | }, 12 | "exports": { 13 | ".": "./src/app.ts" 14 | }, 15 | "devDependencies": { 16 | "@refina/tsconfig": "workspace:^", 17 | "autoprefixer": "^10.4.18", 18 | "postcss": "^8.4.35", 19 | "tailwindcss": "^3.4.1", 20 | "typescript": "^5.4.2", 21 | "vite": "^4.5.2", 22 | "vite-plugin-inspect": "^0.7.42", 23 | "vite-plugin-refina": "workspace:^" 24 | }, 25 | "dependencies": { 26 | "@refina/basic-components": "workspace:^", 27 | "refina": "workspace:^" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/examples/gh-login/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/examples/gh-login/src/styles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /packages/examples/gh-login/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/examples/gh-login/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import Inspect from "vite-plugin-inspect"; 3 | import Refina from "vite-plugin-refina"; 4 | 5 | export default defineConfig({ 6 | plugins: [Inspect(), Refina()], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/examples/mdui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/examples/mdui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/example-mdui", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite --open", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "tsc --noEmit" 11 | }, 12 | "exports": { 13 | ".": "./src/app.ts" 14 | }, 15 | "devDependencies": { 16 | "@refina/tsconfig": "workspace:^", 17 | "typescript": "^5.4.2", 18 | "vite": "^4.5.2", 19 | "vite-plugin-inspect": "^0.7.42", 20 | "vite-plugin-refina": "workspace:^" 21 | }, 22 | "dependencies": { 23 | "@refina/mdui": "workspace:^", 24 | "refina": "workspace:^" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/examples/mdui/src/styles.css: -------------------------------------------------------------------------------- 1 | @import url(@refina/mdui/styles.css); 2 | -------------------------------------------------------------------------------- /packages/examples/mdui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/examples/mdui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import Inspect from "vite-plugin-inspect"; 3 | import Refina from "vite-plugin-refina"; 4 | 5 | export default defineConfig({ 6 | plugins: [Inspect(), Refina()], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/fluentui-icons/README.md: -------------------------------------------------------------------------------- 1 | # `@refina/fluentui-icons` 2 | 3 | [![npm](https://img.shields.io/npm/v/%40refina%2Ffluentui-icons?color=green)](https://www.npmjs.com/package/@refina/fluentui-icons) 4 | 5 | The [FluentUI Icons](https://react.fluentui.dev/?path=/docs/icons-catalog--page) bundle for Refina. 6 | 7 | To learn more about Refina, please visit: 8 | 9 | - [Documentation](https://refina.vercel.app). 10 | - [Getting Started](https://refina.vercel.app/guide/introduction.html) 11 | - [GitHub Repository](https://github.com/refinajs/refina). 12 | - [Examples](https://gallery.refina.vercel.app). 13 | -------------------------------------------------------------------------------- /packages/fluentui-icons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./generator.ts"], 3 | "exclude": ["./dist"], 4 | "compilerOptions": { 5 | "strict": true, 6 | "moduleResolution": "NodeNext", 7 | "module": "NodeNext", 8 | "target": "ESNext", 9 | "lib": ["ESNext"], 10 | "composite": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/fluentui/README.md: -------------------------------------------------------------------------------- 1 | # `@refina/fluentui` 2 | 3 | [![npm](https://img.shields.io/npm/v/%40refina%2Fbasic-components?color=green)](https://www.npmjs.com/package/@refina/fluentui) 4 | 5 | The [FluentUI](https://react.fluentui.dev) components for Refina. 6 | 7 | To learn more about Refina, please visit: 8 | 9 | - [Documentation](https://refina.vercel.app). 10 | - [Getting Started](https://refina.vercel.app/guide/introduction.html) 11 | - [GitHub Repository](https://github.com/refinajs/refina). 12 | - [Examples](https://gallery.refina.vercel.app). 13 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/accordion/accordion/item.styles.ts: -------------------------------------------------------------------------------- 1 | import { defineStyles } from "@refina/griffel"; 2 | 3 | export const accordionItemClassNames = { 4 | root: "fui-AccordionItem", 5 | } as const; 6 | 7 | export default () => 8 | defineStyles({ 9 | root: [accordionItemClassNames.root], 10 | }); 11 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/accordion/accordionPanel/index.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | import useStyles from "./styles"; 3 | 4 | export class FAccordionPanel extends Component { 5 | $main(children: Content): void { 6 | const styles = useStyles(); 7 | 8 | styles.root; 9 | _._div({}, children); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/accordion/accordionPanel/styles.ts: -------------------------------------------------------------------------------- 1 | import { tokens } from "@fluentui/tokens"; 2 | import { defineStyles, makeStyles, shorthands } from "@refina/griffel"; 3 | 4 | export const accordionPanelClassNames = { 5 | root: "fui-AccordionPanel", 6 | } as const; 7 | 8 | /** 9 | * Styles for the root slot 10 | */ 11 | const styles = makeStyles({ 12 | root: { 13 | ...shorthands.margin(0, tokens.spacingHorizontalM), 14 | }, 15 | }); 16 | 17 | export default () => 18 | defineStyles({ 19 | root: [accordionPanelClassNames.root, styles.root], 20 | }); 21 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/accordion/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./accordion"; 2 | export * from "./accordionPanel"; 3 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/avatar/colors.ts: -------------------------------------------------------------------------------- 1 | import { FAvatarNamedColor } from "./types"; 2 | 3 | const avatarColors: FAvatarNamedColor[] = [ 4 | "dark-red", 5 | "cranberry", 6 | "red", 7 | "pumpkin", 8 | "peach", 9 | "marigold", 10 | "gold", 11 | "brass", 12 | "brown", 13 | "forest", 14 | "seafoam", 15 | "dark-green", 16 | "light-teal", 17 | "teal", 18 | "steel", 19 | "blue", 20 | "royal-blue", 21 | "cornflower", 22 | "navy", 23 | "lavender", 24 | "purple", 25 | "grape", 26 | "lilac", 27 | "pink", 28 | "magenta", 29 | "plum", 30 | "beige", 31 | "mink", 32 | "platinum", 33 | "anchor", 34 | ]; 35 | 36 | const getHashCode = (str: string): number => { 37 | let hashCode = 0; 38 | for (let len: number = str.length - 1; len >= 0; len--) { 39 | const ch = str.charCodeAt(len); 40 | const shift = len % 8; 41 | hashCode ^= (ch << shift) + (ch >> (8 - shift)); // eslint-disable-line no-bitwise 42 | } 43 | 44 | return hashCode; 45 | }; 46 | 47 | export function getColor(name: string) { 48 | return avatarColors[getHashCode(name) % avatarColors.length]; 49 | } 50 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/avatar/types.ts: -------------------------------------------------------------------------------- 1 | export type FAvatarShape = "circular" | "square"; 2 | export type FAvatarActive = "active" | "inactive" | "unset"; 3 | export type FAvatarNamedColor = 4 | | "dark-red" 5 | | "cranberry" 6 | | "red" 7 | | "pumpkin" 8 | | "peach" 9 | | "marigold" 10 | | "gold" 11 | | "brass" 12 | | "brown" 13 | | "forest" 14 | | "seafoam" 15 | | "dark-green" 16 | | "light-teal" 17 | | "teal" 18 | | "steel" 19 | | "blue" 20 | | "royal-blue" 21 | | "cornflower" 22 | | "navy" 23 | | "lavender" 24 | | "purple" 25 | | "grape" 26 | | "lilac" 27 | | "pink" 28 | | "magenta" 29 | | "plum" 30 | | "beige" 31 | | "mink" 32 | | "platinum" 33 | | "anchor"; 34 | export type FAvatarColor = "neutral" | "brand" | "colorful" | FAvatarNamedColor; 35 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/breadcrumb/divider.styles.ts: -------------------------------------------------------------------------------- 1 | import { defineStyles, makeStyles } from "@refina/griffel"; 2 | 3 | export const breadcrumbDividerClassNames = { 4 | root: "fui-BreadcrumbDivider", 5 | }; 6 | 7 | /** 8 | * Styles for the root slot 9 | */ 10 | const styles = makeStyles({ 11 | root: { 12 | display: "flex", 13 | }, 14 | }); 15 | 16 | export default () => 17 | defineStyles({ 18 | root: [breadcrumbDividerClassNames.root, styles.root], 19 | }); 20 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/breadcrumb/item.styles.ts: -------------------------------------------------------------------------------- 1 | import { tokens } from "@fluentui/tokens"; 2 | import { defineStyles, makeResetStyles } from "@refina/griffel"; 3 | 4 | export const breadcrumbItemClassNames = { 5 | root: "fui-BreadcrumbItem", 6 | }; 7 | 8 | const breadcrumbItemResetStyles = makeResetStyles({ 9 | display: "flex", 10 | alignItems: "center", 11 | color: tokens.colorNeutralForeground2, 12 | boxSizing: "border-box", 13 | textWrap: "nowrap", 14 | }); 15 | 16 | export default () => 17 | defineStyles({ 18 | root: [breadcrumbItemClassNames.root, breadcrumbItemResetStyles], 19 | }); 20 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/breadcrumb/styles.ts: -------------------------------------------------------------------------------- 1 | import { defineStyles, makeResetStyles } from "@refina/griffel"; 2 | 3 | export const breadcrumbClassNames = { 4 | root: "fui-Breadcrumb", 5 | list: "fui-Breadcrumb__list", 6 | }; 7 | 8 | const listClassName = makeResetStyles({ 9 | listStyleType: "none", 10 | display: "flex", 11 | alignItems: "center", 12 | margin: 0, 13 | padding: 0, 14 | }); 15 | 16 | export default () => 17 | defineStyles({ 18 | root: [breadcrumbClassNames.root], 19 | list: [listClassName, breadcrumbClassNames.list], 20 | }); 21 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/button/types.ts: -------------------------------------------------------------------------------- 1 | export type FButtonShape = "circular" | "rounded" | "square"; 2 | export type FButtonApperance = 3 | | "outline" 4 | | "primary" 5 | | "secondary" 6 | | "subtle" 7 | | "transparent"; 8 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/checkbox/types.ts: -------------------------------------------------------------------------------- 1 | export type FCheckboxState = true | false | "mixed"; 2 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/checkbox/utils.ts: -------------------------------------------------------------------------------- 1 | import type { FCheckboxState } from "./types"; 2 | 3 | /** 4 | * @returns the mixed state if any of the sources is mixed, true if any of the sources is true and none is false, false otherwise 5 | */ 6 | export function calcMixedCheckboxState( 7 | sources: (boolean | FCheckboxState)[], 8 | ): FCheckboxState { 9 | const sourcesSet = new Set(sources); 10 | return sourcesSet.has("mixed") 11 | ? "mixed" 12 | : sourcesSet.has(true) 13 | ? sourcesSet.has(false) 14 | ? "mixed" 15 | : true 16 | : false; 17 | } 18 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/dialog/constants.ts: -------------------------------------------------------------------------------- 1 | export const MEDIA_QUERY_BREAKPOINT_SELECTOR = 2 | "@media screen and (max-width: 480px)"; 3 | export const SURFACE_PADDING = "24px"; 4 | export const DIALOG_GAP = "8px"; 5 | export const SURFACE_BORDER_WIDTH = "1px"; 6 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/dialog/dialogBody/body.styles.ts: -------------------------------------------------------------------------------- 1 | import { defineStyles, makeResetStyles, shorthands } from "@refina/griffel"; 2 | import { 3 | DIALOG_GAP, 4 | MEDIA_QUERY_BREAKPOINT_SELECTOR, 5 | SURFACE_PADDING, 6 | } from "../constants"; 7 | 8 | export const dialogBodyClassNames = { 9 | root: "fui-DialogBody", 10 | } as const; 11 | 12 | /** 13 | * Styles for the root slot 14 | */ 15 | const resetStyles = makeResetStyles({ 16 | ...shorthands.overflow("unset"), 17 | ...shorthands.gap(DIALOG_GAP), 18 | display: "grid", 19 | maxHeight: `calc(100vh - 2 * ${SURFACE_PADDING})`, 20 | boxSizing: "border-box", 21 | gridTemplateRows: "auto 1fr", 22 | gridTemplateColumns: "1fr 1fr auto", 23 | 24 | [MEDIA_QUERY_BREAKPOINT_SELECTOR]: { 25 | maxWidth: "100vw", 26 | gridTemplateRows: "auto 1fr auto", 27 | }, 28 | }); 29 | 30 | export default () => 31 | defineStyles({ 32 | root: [dialogBodyClassNames.root, resetStyles], 33 | }); 34 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/dialog/dialogBody/content.styles.ts: -------------------------------------------------------------------------------- 1 | import { tokens, typographyStyles } from "@fluentui/tokens"; 2 | import { defineStyles, makeResetStyles, shorthands } from "@refina/griffel"; 3 | 4 | export const dialogContentClassNames = { 5 | root: "fui-DialogContent", 6 | } as const; 7 | 8 | /** 9 | * Styles for the root slot 10 | */ 11 | const styles = makeResetStyles({ 12 | ...shorthands.padding(tokens.strokeWidthThick), 13 | ...shorthands.margin(`calc(${tokens.strokeWidthThick} * -1)`), 14 | ...typographyStyles.body1, 15 | overflowY: "auto", 16 | minHeight: "32px", 17 | boxSizing: "border-box", 18 | gridRowStart: 2, 19 | gridRowEnd: 2, 20 | gridColumnStart: 1, 21 | gridColumnEnd: 4, 22 | }); 23 | 24 | export default () => 25 | defineStyles({ 26 | root: [dialogContentClassNames.root, styles], 27 | }); 28 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./dialog"; 2 | export * from "./dialogBody"; 3 | export * from "./dialogSurface"; 4 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/divider/index.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | import useStyles from "./styles"; 3 | import { DividerContentAlignment } from "./types"; 4 | 5 | export class FDivider extends Component { 6 | $main( 7 | children: Content | undefined, 8 | alignContent: DividerContentAlignment = "center", 9 | ): void { 10 | const styles = useStyles(alignContent, false, children == undefined); 11 | 12 | styles.root(); 13 | _._div({}, _ => styles.wrapper() && _._div({}, children)); 14 | } 15 | } 16 | 17 | export * from "./types"; 18 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/divider/types.ts: -------------------------------------------------------------------------------- 1 | export type DividerContentAlignment = "start" | "end" | "center"; 2 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/dropdown/listbox.styles.ts: -------------------------------------------------------------------------------- 1 | import { tokens } from "@fluentui/tokens"; 2 | import { defineStyles, makeStyles, shorthands } from "@refina/griffel"; 3 | 4 | export const listboxClassNames = { 5 | root: "fui-Listbox", 6 | } as const; 7 | 8 | const styles = makeStyles({ 9 | root: { 10 | backgroundColor: tokens.colorNeutralBackground1, 11 | boxSizing: "border-box", 12 | display: "flex", 13 | flexDirection: "column", 14 | minWidth: "160px", 15 | overflowY: "auto", 16 | ...shorthands.outline("1px", "solid", tokens.colorTransparentStroke), 17 | ...shorthands.padding(tokens.spacingHorizontalXS), 18 | rowGap: tokens.spacingHorizontalXXS, 19 | }, 20 | }); 21 | 22 | export default () => 23 | defineStyles({ 24 | root: [listboxClassNames.root, styles.root], 25 | }); 26 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/dropdown/tokens.ts: -------------------------------------------------------------------------------- 1 | export const iconSizes = { 2 | small: "16px", 3 | medium: "20px", 4 | large: "24px", 5 | } as const; 6 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/dropdown/types.ts: -------------------------------------------------------------------------------- 1 | export type FDropdownAppearance = 2 | | "filled-darker" 3 | | "filled-lighter" 4 | | "outline" 5 | | "underline"; 6 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/field/types.ts: -------------------------------------------------------------------------------- 1 | export type FFieldValidationState = "error" | "warning" | "success" | "none"; 2 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./accordion"; 2 | export * from "./avatar"; 3 | export * from "./breadcrumb"; 4 | export * from "./button"; 5 | export * from "./checkbox"; 6 | export * from "./dialog"; 7 | export * from "./divider"; 8 | export * from "./dropdown"; 9 | export * from "./field"; 10 | export * from "./input"; 11 | export * from "./label"; 12 | export * from "./popover"; 13 | export * from "./portal"; 14 | export * from "./progressBar"; 15 | export * from "./slider"; 16 | export * from "./switch"; 17 | export * from "./tab"; 18 | export * from "./textarea"; 19 | export * from "./tooltip"; 20 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/input/types.ts: -------------------------------------------------------------------------------- 1 | export type FInputAppearance = "outline" | "underline" | "filled"; 2 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/label/index.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | import useStyles from "./styles"; 3 | 4 | export class FLabel extends Component { 5 | $main( 6 | content: Content, 7 | required: Content | boolean = false, 8 | disabled = false, 9 | ): void { 10 | const requiredContent = typeof required === "boolean" ? "*" : required; 11 | 12 | const styles = useStyles(disabled); 13 | 14 | styles.root(); 15 | _._label({}, _ => { 16 | _._span({}, content); 17 | if (required !== false) { 18 | styles.required(); 19 | _._span({}, requiredContent); 20 | } 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/popover/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal 3 | * The default value of the tooltip's border radius (borderRadiusMedium). 4 | * 5 | * Unfortunately, Popper requires it to be specified as a variable instead of using CSS. 6 | * While we could use getComputedStyle, that adds a performance penalty for something that 7 | * will likely never change. 8 | */ 9 | export const popoverSurfaceBorderRadius = 4; 10 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/portal/index.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | import useStyles from "./styles"; 3 | 4 | export class FPortal extends Component { 5 | $main(children: Content): void { 6 | const styles = useStyles(); 7 | _.portal(_ => styles.root() && _._div({}, children)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/portal/styles.ts: -------------------------------------------------------------------------------- 1 | import { defineStyles, makeStyles } from "@refina/griffel"; 2 | 3 | const portalMountNodeStyles = makeStyles({ 4 | root: { 5 | // Creates new stacking context to prevent z-index issues 6 | // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context 7 | // 8 | // Also keeps a portal on top of a page to prevent scrollbars from appearing 9 | position: "absolute", 10 | top: 0, 11 | left: 0, 12 | right: 0, 13 | 14 | zIndex: 1000000, 15 | }, 16 | }); 17 | 18 | export default () => 19 | defineStyles({ 20 | root: [portalMountNodeStyles.root], 21 | }); 22 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/progressBar/index.ts: -------------------------------------------------------------------------------- 1 | import { Component, _ } from "refina"; 2 | import useStyles from "./styles"; 3 | import { FProgressBarColor, FProgressBarValue } from "./types"; 4 | 5 | export class FProgressBar extends Component { 6 | $main(value: FProgressBarValue, color?: FProgressBarColor): void { 7 | const styles = useStyles(value, color); 8 | 9 | styles.root(); 10 | _._div({}, _ => { 11 | styles.bar(); 12 | if (value !== "indertermine") 13 | _.$css(`width: ${Math.min(100, Math.max(0, value * 100))}%`); 14 | _._div(); 15 | }); 16 | } 17 | } 18 | 19 | export * from "./types"; 20 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/progressBar/types.ts: -------------------------------------------------------------------------------- 1 | export type FProgressBarValue = number | "indertermine"; 2 | export type FProgressBarColor = "brand" | "success" | "warning" | "error"; 3 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/switch/index.ts: -------------------------------------------------------------------------------- 1 | import { FiCircleFilled } from "@refina/fluentui-icons/circle"; 2 | import { Model, TriggerComponent, _, elementRef, unwrap } from "refina"; 3 | import { FLabel } from "../label"; 4 | import useStyles from "./styles"; 5 | 6 | export class FSwitch extends TriggerComponent { 7 | inputRef = elementRef<"input">(); 8 | $main( 9 | label: string, 10 | state: Model, 11 | disabled = false, 12 | ): this is { 13 | $ev: boolean; 14 | } { 15 | const stateValue = unwrap(state); 16 | 17 | const styles = useStyles(); 18 | 19 | styles.root(); 20 | _._div( 21 | { 22 | onclick: () => { 23 | if (!disabled) { 24 | const newState = !stateValue; 25 | this.$updateModel(state, newState); 26 | this.$fire(newState); 27 | } 28 | }, 29 | }, 30 | _ => { 31 | styles.input(); 32 | _.$ref(this.inputRef); 33 | _._input({ 34 | type: "checkbox", 35 | disabled: disabled, 36 | checked: stateValue, 37 | }); 38 | styles.indicator(); 39 | _._div({}, _ => _(FiCircleFilled)()); 40 | styles.label(); 41 | _(FLabel)(label); 42 | }, 43 | ); 44 | return this.$fired; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/tab/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./tab"; 2 | export * from "./tabList"; 3 | export * from "./tabs"; 4 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/tab/tab/index.ts: -------------------------------------------------------------------------------- 1 | import { Content, TriggerComponent, _ } from "refina"; 2 | import useStyles from "./styles"; 3 | 4 | export class FTab extends TriggerComponent { 5 | $main( 6 | selected: boolean, 7 | content: Content, 8 | disabled = false, 9 | animating = false, 10 | ): this is { 11 | $ev: void; 12 | } { 13 | const styles = useStyles(disabled, selected, animating, false, false); 14 | 15 | styles.root(); 16 | _._button( 17 | { 18 | onclick: this.$fireWith(), 19 | disabled: disabled, 20 | }, 21 | _ => { 22 | styles.content(); 23 | _._span({}, content); 24 | 25 | styles.contentReservedSpace(); 26 | _._span({}, content); 27 | }, 28 | ); 29 | return this.$fired; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/tab/tabList/styles.ts: -------------------------------------------------------------------------------- 1 | import { defineStyles, makeStyles } from "@refina/griffel"; 2 | 3 | export const tabListClassNames = { 4 | root: "fui-TabList", 5 | } as const; 6 | 7 | /** 8 | * Styles for the root slot 9 | */ 10 | const styles = makeStyles({ 11 | root: { 12 | display: "flex", 13 | flexDirection: "row", 14 | flexShrink: 0, 15 | flexWrap: "nowrap", 16 | position: "relative", 17 | }, 18 | horizontal: { 19 | alignItems: "stretch", 20 | flexDirection: "row", 21 | }, 22 | vertical: { 23 | alignItems: "stretch", 24 | flexDirection: "column", 25 | }, 26 | }); 27 | 28 | /** 29 | * Apply styling to the TabList slots based on the state 30 | */ 31 | export default (vertical: boolean) => 32 | defineStyles({ 33 | root: [ 34 | tabListClassNames.root, 35 | styles.root, 36 | vertical ? styles.vertical : styles.horizontal, 37 | ], 38 | }); 39 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/tab/tabs/styles.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WARNING: This style file is non-standard. 3 | * Copied from https://react.fluentui.dev/?path=/docs/components-tablist--default#with-panels 4 | */ 5 | import { defineStyles, makeStyles, shorthands } from "@refina/griffel"; 6 | 7 | const styles = makeStyles({ 8 | root: { 9 | alignItems: "flex-start", 10 | display: "flex", 11 | flexDirection: "column", 12 | justifyContent: "flex-start", 13 | ...shorthands.padding("50px", "20px"), 14 | rowGap: "20px", 15 | }, 16 | panels: { 17 | ...shorthands.padding(0, "10px"), 18 | "& th": { 19 | textAlign: "left", 20 | ...shorthands.padding(0, "30px", 0, 0), 21 | }, 22 | }, 23 | }); 24 | 25 | export default () => 26 | defineStyles({ 27 | root: [styles.root], 28 | panels: [styles.panels], 29 | }); 30 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/textarea/types.ts: -------------------------------------------------------------------------------- 1 | export type FTextareaAppearance = 2 | | "outline" 3 | | "filled-darker" 4 | | "filled-lighter"; 5 | 6 | export type FTextareaResize = "none" | "horizontal" | "vertical" | "both"; 7 | -------------------------------------------------------------------------------- /packages/fluentui/src/components/tooltip/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The height of the tooltip's arrow in pixels. 3 | */ 4 | export const arrowHeight = 6; 5 | 6 | /** 7 | * The default value of the tooltip's border radius (borderRadiusMedium). 8 | * 9 | * Unfortunately, Popper requires it to be specified as a variable instead of using CSS. 10 | * While we could use getComputedStyle, that adds a performance penalty for something that 11 | * will likely never change. 12 | */ 13 | export const tooltipBorderRadius = 4; 14 | -------------------------------------------------------------------------------- /packages/fluentui/src/focus/constants.ts: -------------------------------------------------------------------------------- 1 | export const KEYBOARD_NAV_ATTRIBUTE = "data-keyboard-nav" as const; 2 | export const KEYBOARD_NAV_SELECTOR = 3 | `:global([${KEYBOARD_NAV_ATTRIBUTE}])` as const; 4 | 5 | /** 6 | * @internal 7 | */ 8 | export const FOCUS_VISIBLE_ATTR = "data-fui-focus-visible"; 9 | 10 | /** 11 | * @internal 12 | */ 13 | export const FOCUS_WITHIN_ATTR = "data-fui-focus-within"; 14 | export const defaultOptions = { 15 | style: {}, 16 | selector: "focus", 17 | customizeSelector: (selector: string) => selector, 18 | } as const; 19 | -------------------------------------------------------------------------------- /packages/fluentui/src/focus/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./createCustomFocusIndicatorStyle"; 2 | export * from "./createFocusOutlineStyle"; 3 | export * from "./focusVisiblePolyfill"; 4 | export * from "./focusWithinPolyfill"; 5 | -------------------------------------------------------------------------------- /packages/fluentui/src/positioning/constants.ts: -------------------------------------------------------------------------------- 1 | export const DATA_POSITIONING_INTERSECTING = "data-popper-is-intersecting"; 2 | export const DATA_POSITIONING_ESCAPED = "data-popper-escaped"; 3 | export const DATA_POSITIONING_HIDDEN = "data-popper-reference-hidden"; 4 | export const DATA_POSITIONING_PLACEMENT = "data-popper-placement"; 5 | -------------------------------------------------------------------------------- /packages/fluentui/src/positioning/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants"; 2 | export * from "./createArrowStyles"; 3 | export * from "./createSlideStyles"; 4 | export * from "./types"; 5 | export * from "./utils"; 6 | export * from "./usePositioning"; 7 | -------------------------------------------------------------------------------- /packages/fluentui/src/positioning/middleware/flip.ts: -------------------------------------------------------------------------------- 1 | import { flip as baseFlip, Middleware, Placement } from "@floating-ui/dom"; 2 | import type { PositioningOptions } from "../types"; 3 | import { resolvePositioningShorthand } from "../utils/resolvePositioningShorthand"; 4 | import { toFloatingUIPlacement } from "../utils/toFloatingUIPlacement"; 5 | 6 | export interface FlipMiddlewareOptions 7 | extends Pick { 8 | hasScrollableElement?: boolean; 9 | container: HTMLElement | null; 10 | isRtl?: boolean; 11 | } 12 | 13 | export function flip(options: FlipMiddlewareOptions): Middleware { 14 | const { hasScrollableElement, fallbackPositions = [], isRtl } = options; 15 | 16 | const fallbackPlacements = fallbackPositions.reduce( 17 | (acc, shorthand) => { 18 | const { position, align } = resolvePositioningShorthand(shorthand); 19 | const placement = toFloatingUIPlacement(align, position, isRtl); 20 | if (placement) { 21 | acc.push(placement); 22 | } 23 | return acc; 24 | }, 25 | [], 26 | ); 27 | 28 | return baseFlip({ 29 | ...(hasScrollableElement && { boundary: "clippingAncestors" }), 30 | fallbackStrategy: "bestFit", 31 | ...(fallbackPlacements.length && { fallbackPlacements }), 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/fluentui/src/positioning/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./flip"; 2 | export * from "./offset"; 3 | -------------------------------------------------------------------------------- /packages/fluentui/src/positioning/middleware/offset.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, offset as baseOffset } from "@floating-ui/dom"; 2 | import type { PositioningOptions } from "../types"; 3 | import { getFloatingUIOffset } from "../utils/getFloatingUIOffset"; 4 | 5 | /** 6 | * Wraps floating UI offset middleware to to transform offset value 7 | */ 8 | export function offset(offsetValue: PositioningOptions["offset"]): Middleware { 9 | const floatingUIOffset = getFloatingUIOffset(offsetValue); 10 | return baseOffset(floatingUIOffset); 11 | } 12 | -------------------------------------------------------------------------------- /packages/fluentui/src/positioning/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./fromFloatingUIPlacement"; 2 | export * from "./mergeArrowOffset"; 3 | export * from "./parseFloatingUIPlacement"; 4 | export * from "./resolvePositioningShorthand"; 5 | export * from "./toFloatingUIPlacement"; 6 | -------------------------------------------------------------------------------- /packages/fluentui/src/positioning/utils/parseFloatingUIPlacement.ts: -------------------------------------------------------------------------------- 1 | import type { Side, Placement, Alignment } from "@floating-ui/dom"; 2 | 3 | /** 4 | * Parses Floating UI placement and returns the different components 5 | * @param placement - the floating ui placement (i.e. bottom-start) 6 | * 7 | * @returns side and alignment components of the placement 8 | */ 9 | export function parseFloatingUIPlacement(placement: Placement): { 10 | side: Side; 11 | alignment: Alignment; 12 | } { 13 | const tokens = placement.split("-"); 14 | return { 15 | side: tokens[0] as Side, 16 | alignment: tokens[1] as Alignment, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/fluentui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/fluentui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { RefinaLib } from "vite-plugin-refina"; 3 | 4 | export default defineConfig({ 5 | plugins: [RefinaLib()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/griffel/README.md: -------------------------------------------------------------------------------- 1 | # `@refina/griffel` 2 | 3 | [![npm](https://img.shields.io/npm/v/%40refina%2Fgriffel?color=green)](https://www.npmjs.com/package/@refina/griffel) 4 | 5 | The [Griffel](https://griffel.js.org/) integration for Refina. 6 | 7 | To learn more about Refina, please visit: 8 | 9 | - [Documentation](https://refina.vercel.app). 10 | - [Getting Started](https://refina.vercel.app/guide/introduction.html) 11 | - [GitHub Repository](https://github.com/refinajs/refina). 12 | - [Examples](https://gallery.refina.vercel.app). 13 | -------------------------------------------------------------------------------- /packages/griffel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/griffel", 3 | "version": "0.6.0", 4 | "description": "Griffel.js support for Refina framework.", 5 | "keywords": [ 6 | "refina", 7 | "griffel" 8 | ], 9 | "files": [ 10 | "./src", 11 | "README.md", 12 | "tsconfig.json" 13 | ], 14 | "type": "module", 15 | "main": "./src/index.ts", 16 | "types": "./src/index.ts", 17 | "exports": { 18 | ".": { 19 | "import": { 20 | "types": "./src/index.ts", 21 | "default": "./src/index.ts" 22 | } 23 | } 24 | }, 25 | "scripts": {}, 26 | "author": "_Kerman", 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/KermanX/refina" 30 | }, 31 | "readme": "https://github.com/KermanX/refina#readme", 32 | "bugs": "https://github.com/KermanX/refina/issues", 33 | "license": "MIT", 34 | "devDependencies": { 35 | "@refina/tsconfig": "workspace:^", 36 | "typescript": "^5.4.2" 37 | }, 38 | "dependencies": { 39 | "@griffel/core": "^1.15.2" 40 | }, 41 | "peerDependencies": { 42 | "refina": "workspace:^" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/griffel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/hmr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/hmr", 3 | "version": "0.0.0", 4 | "description": "The HMR compiler for Refina framework.", 5 | "keywords": [ 6 | "refina", 7 | "hmr" 8 | ], 9 | "files": [ 10 | "src" 11 | ], 12 | "type": "module", 13 | "main": "./src/index.ts", 14 | "types": "./src/index.ts", 15 | "exports": { 16 | ".": "./src/index.ts" 17 | }, 18 | "scripts": { 19 | "check": "tsc --noEmit" 20 | }, 21 | "author": "_Kerman", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/KermanX/refina" 25 | }, 26 | "readme": "https://github.com/KermanX/refina#readme", 27 | "bugs": "https://github.com/KermanX/refina/issues", 28 | "license": "MIT", 29 | "dependencies": { 30 | "@babel/parser": "^7.24.0", 31 | "@babel/types": "^7.24.0", 32 | "magic-string": "^0.30.8" 33 | }, 34 | "devDependencies": { 35 | "typescript": "^5.4.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/hmr/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const localsObjId = "__locals__"; 2 | export function getLocalsAccessor(id: string) { 3 | return `(${localsObjId}.${id})`; 4 | } 5 | 6 | export const mainFuncId = "__main__"; 7 | 8 | export const appInstDefaultId = "__app__"; 9 | 10 | export const initFuncId = "__init__"; 11 | 12 | export const mainUrlSuffix = "?refina-app-main"; 13 | -------------------------------------------------------------------------------- /packages/hmr/src/cutSrc.ts: -------------------------------------------------------------------------------- 1 | import t from "@babel/types"; 2 | import MagicString from "magic-string"; 3 | 4 | export function cutSrc(src: string, stmtsToRemove: t.Statement[]) { 5 | const s = new MagicString(src); 6 | for (const statement of stmtsToRemove) { 7 | s.remove(statement.start!, statement.end!); 8 | } 9 | return s; 10 | } 11 | -------------------------------------------------------------------------------- /packages/hmr/src/index.ts: -------------------------------------------------------------------------------- 1 | import { RefinaDescriptor, compile } from "./compile"; 2 | 3 | export class RefinaHmr { 4 | cache = new Map(); 5 | 6 | transform(id: string, src: string) { 7 | const descriptor = this.cache.get(id) ?? compile(id, src); 8 | this.cache.set(id, descriptor); 9 | return descriptor; 10 | } 11 | 12 | /** 13 | * @returns Can perform HMR. 14 | */ 15 | update(id: string, newSrc: string) { 16 | const oldDescriptor = this.cache.get(id); 17 | const newDescriptor = compile(id, newSrc); 18 | this.cache.set(id, newDescriptor); 19 | if (!oldDescriptor || !newDescriptor) return false; 20 | if (oldDescriptor.locals.code !== newDescriptor.locals.code) return false; 21 | return true; 22 | } 23 | } 24 | 25 | export * from "./constants"; 26 | -------------------------------------------------------------------------------- /packages/hmr/src/wrapMain.ts: -------------------------------------------------------------------------------- 1 | import MagicString from "magic-string"; 2 | import { 3 | appInstDefaultId, 4 | initFuncId, 5 | localsObjId, 6 | mainFuncId, 7 | } from "./constants"; 8 | import { ParseResult } from "./parser"; 9 | 10 | export function wrapMain( 11 | { appStmt: appCallAst, mainFuncExpr: mainFuncAst, appInstName }: ParseResult, 12 | mainSrc: MagicString, 13 | ) { 14 | const appId = appInstName ?? appInstDefaultId; 15 | 16 | mainSrc.update( 17 | appCallAst.start!, 18 | mainFuncAst.start!, 19 | `export const ${mainFuncId} = (${localsObjId}) => (`, 20 | ); 21 | 22 | mainSrc.append(` 23 | let ${appId}, ${localsObjId}; 24 | 25 | export function ${initFuncId}(__app_param__, __locals_param__) { 26 | ${appId} = __app_param__; 27 | ${localsObjId} = __locals_param__; 28 | } 29 | 30 | import.meta.hot?.accept(async (__new_main_mod__) => { 31 | __new_main_mod__.${initFuncId}(${appId}, ${localsObjId}); 32 | const newMain = __new_main_mod__.${mainFuncId}(${localsObjId}); 33 | if(${appId}.state !== "idle") { 34 | await ${appId}.promises.mainExecuted; 35 | } 36 | ${appId}.main = newMain; 37 | ${appId}.update(); 38 | }); 39 | `); 40 | } 41 | -------------------------------------------------------------------------------- /packages/hmr/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "lib": ["ESNext"], 8 | "strict": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/mdui/src/components/avatar.ts: -------------------------------------------------------------------------------- 1 | import { Avatar } from "mdui"; 2 | import { Component, _ } from "refina"; 3 | 4 | export class MdAvatar extends Component { 5 | $main(src: string, fit?: Avatar["fit"]): void { 6 | _._mdui_avatar({ 7 | src, 8 | fit, 9 | }); 10 | } 11 | } 12 | 13 | export class MdIconAvatar extends Component { 14 | $main(iconName: string): void { 15 | _._mdui_avatar({ 16 | icon: iconName, 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/mdui/src/components/badge.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class MdBadge extends Component { 4 | $main(children?: Content | undefined): void { 5 | if (children === undefined) { 6 | _._mdui_badge({ 7 | variant: "small", 8 | }); 9 | } else { 10 | _._mdui_badge( 11 | { 12 | variant: "large", 13 | }, 14 | children, 15 | ); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mdui/src/components/bottomAppBar.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class MdBottomAppBar extends Component { 4 | $main(children: Content): void { 5 | _._mdui_bottom_app_bar({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/mdui/src/components/button.ts: -------------------------------------------------------------------------------- 1 | import { Button } from "mdui"; 2 | import { Content, TriggerComponent, _ } from "refina"; 3 | 4 | export class MdButton extends TriggerComponent { 5 | variant: Button["variant"] = "filled"; 6 | $main( 7 | children: Content, 8 | disabled: boolean = false, 9 | ): this is { 10 | $ev: void; 11 | } { 12 | _._mdui_button( 13 | { 14 | disabled, 15 | onclick: this.$fireWith(), 16 | variant: this.variant, 17 | }, 18 | children, 19 | ); 20 | return this.$fired; 21 | } 22 | } 23 | 24 | export class MdTonalButton extends MdButton { 25 | variant = "tonal" as const; 26 | } 27 | 28 | export class MdOutlinedButton extends MdButton { 29 | variant = "outlined" as const; 30 | } 31 | 32 | export class MdTextButton extends MdButton { 33 | variant = "text" as const; 34 | } 35 | -------------------------------------------------------------------------------- /packages/mdui/src/components/checkbox.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Content, 3 | Model, 4 | TriggerComponent, 5 | _, 6 | elementRef, 7 | unwrap, 8 | } from "refina"; 9 | 10 | export type MdCheckboxState = boolean | undefined; 11 | 12 | export class MdCheckbox extends TriggerComponent { 13 | checkboxRef = elementRef<"mdui-checkbox">(); 14 | $main( 15 | state: Model, 16 | label?: Content, 17 | disabled = false, 18 | ): this is { 19 | $ev: MdCheckboxState; 20 | } { 21 | const stateValue = unwrap(state); 22 | const checked = stateValue; 23 | const indeterminate = stateValue === undefined; 24 | 25 | _.$ref(this.checkboxRef); 26 | _._mdui_checkbox( 27 | { 28 | checked, 29 | indeterminate, 30 | disabled, 31 | onchange: () => { 32 | const node = this.checkboxRef.current!.node; 33 | const newState = node.indeterminate ? undefined : node.checked; 34 | this.$updateModel(state, newState); 35 | this.$fire(newState); 36 | }, 37 | }, 38 | label, 39 | ); 40 | return this.$fired; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/mdui/src/components/circularProgress.ts: -------------------------------------------------------------------------------- 1 | import { Component, _ } from "refina"; 2 | 3 | export class MdCircularProgress extends Component { 4 | $main(percentage?: number | undefined): void { 5 | _._mdui_circular_progress({ 6 | value: percentage, 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/mdui/src/components/collapse.ts: -------------------------------------------------------------------------------- 1 | import { Content, TriggerComponent, _, elementRef } from "refina"; 2 | 3 | export class MdCollapse extends TriggerComponent { 4 | icon?: string; 5 | collapseRef = elementRef<"mdui-collapse">(); 6 | $main( 7 | header: Content, 8 | body: Content, 9 | disabled = false, 10 | ): this is { 11 | $ev: boolean; 12 | } { 13 | _.$ref(this.collapseRef); 14 | _._mdui_collapse( 15 | { 16 | disabled, 17 | accordion: true, 18 | onchange: () => { 19 | this.$fire( 20 | (this.collapseRef.current!.node.value as string[]).length > 0, 21 | ); 22 | }, 23 | }, 24 | _ => 25 | _._mdui_collapse_item( 26 | { 27 | value: "item", 28 | }, 29 | () => { 30 | _._mdui_list_item( 31 | { 32 | slot: "header", 33 | icon: this.icon, 34 | }, 35 | header, 36 | ); 37 | if (this.icon) { 38 | _.$css`margin-left: 2.5rem`; 39 | } 40 | _._div({}, _ => _._mdui_list_item({}, body)); 41 | }, 42 | ), 43 | ); 44 | return this.$fired; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/mdui/src/components/divider.ts: -------------------------------------------------------------------------------- 1 | import { Component, _ } from "refina"; 2 | 3 | export class MdDivider extends Component { 4 | $main(): void { 5 | _._mdui_divider(); 6 | } 7 | } 8 | 9 | export class MdVerticalDivider extends Component { 10 | $main(): void { 11 | _._mdui_divider({ 12 | vertical: true, 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/mdui/src/components/fab.ts: -------------------------------------------------------------------------------- 1 | import { Fab } from "mdui"; 2 | import { Content, TriggerComponent, _ } from "refina"; 3 | 4 | export type MdFabVariant = Fab["variant"]; 5 | 6 | export class MdFab extends TriggerComponent { 7 | varient: MdFabVariant = "primary"; 8 | $main( 9 | icon: string, 10 | disabled = false, 11 | extendedContent: Content | undefined = undefined, 12 | ): this is { 13 | $ev: void; 14 | } { 15 | _._mdui_fab( 16 | { 17 | icon, 18 | disabled, 19 | extended: extendedContent !== undefined, 20 | onclick: this.$fireWith(), 21 | variant: this.varient, 22 | }, 23 | extendedContent, 24 | ); 25 | return this.$fired; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/mdui/src/components/icon.ts: -------------------------------------------------------------------------------- 1 | import { Component, _ } from "refina"; 2 | 3 | export class MdIcon extends Component { 4 | $main(name: string): void { 5 | _._mdui_icon({ 6 | name, 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/mdui/src/components/iconButton.ts: -------------------------------------------------------------------------------- 1 | import { ButtonIcon } from "mdui"; 2 | import { TriggerComponent, _ } from "refina"; 3 | 4 | export type MdIconButtonVariant = ButtonIcon["variant"]; 5 | 6 | export class MdIconButton extends TriggerComponent { 7 | variant: MdIconButtonVariant = "standard"; 8 | $main( 9 | icon: string, 10 | disabled = false, 11 | variant = "standard", 12 | ): this is { 13 | $ev: void; 14 | } { 15 | _._mdui_button_icon({ 16 | icon, 17 | disabled, 18 | onclick: this.$fireWith(), 19 | variant: this.variant, 20 | }); 21 | return this.$fired; 22 | } 23 | } 24 | 25 | export class MdFilledIconButton extends MdIconButton { 26 | variant = "filled" as const; 27 | } 28 | 29 | export class MdTonalIconButton extends MdIconButton { 30 | variant = "tonal" as const; 31 | } 32 | 33 | export class MdOutlinedIconButton extends MdIconButton { 34 | variant = "outlined" as const; 35 | } 36 | -------------------------------------------------------------------------------- /packages/mdui/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./avatar"; 2 | export * from "./badge"; 3 | export * from "./bottomAppBar"; 4 | export * from "./button"; 5 | export * from "./checkbox"; 6 | export * from "./chip"; 7 | export * from "./circularProgress"; 8 | export * from "./collapse"; 9 | export * from "./dialog"; 10 | export * from "./divider"; 11 | export * from "./fab"; 12 | export * from "./icon"; 13 | export * from "./iconButton"; 14 | export * from "./layout"; 15 | export * from "./layoutMain"; 16 | export * from "./linearProgress"; 17 | export * from "./list"; 18 | export * from "./navBar"; 19 | export * from "./navDrawer"; 20 | export * from "./navRail"; 21 | export * from "./prose"; 22 | export * from "./radioGroup"; 23 | export * from "./rangeSlider"; 24 | export * from "./segmentedButton"; 25 | export * from "./select"; 26 | export * from "./slider"; 27 | export * from "./switch"; 28 | export * from "./table"; 29 | export * from "./tabs"; 30 | export * from "./textField"; 31 | export * from "./tooltip"; 32 | export * from "./topAppBar"; 33 | -------------------------------------------------------------------------------- /packages/mdui/src/components/layout.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class MdLayout extends Component { 4 | $main(children: Content): void { 5 | _._mdui_layout({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/mdui/src/components/layoutMain.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class MdLayoutMain extends Component { 4 | $main(children: Content): void { 5 | _._mdui_layout_main({}, children); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/mdui/src/components/linearProgress.ts: -------------------------------------------------------------------------------- 1 | import { Component, _ } from "refina"; 2 | 3 | export class MdLinearProgress extends Component { 4 | $main(percentage?: number | undefined): void { 5 | _._mdui_linear_progress({ 6 | value: percentage, 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/mdui/src/components/list.ts: -------------------------------------------------------------------------------- 1 | import { Component, LoopKey, _ } from "refina"; 2 | 3 | export class MdList extends Component { 4 | $main( 5 | data: Iterable, 6 | key: LoopKey, 7 | body: (item: T, index: number) => void, 8 | ): void { 9 | _._mdui_list({}, _ => 10 | _.for(data, key, (item, index) => 11 | _._mdui_list_item({}, _ => { 12 | body(item, index); 13 | }), 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/mdui/src/components/prose.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class MdProse extends Component { 4 | $main(children: Content): void { 5 | _.$cls`mdui-prose`; 6 | _._div({}, children); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/mdui/src/components/rangeSlider.ts: -------------------------------------------------------------------------------- 1 | import { Model, TriggerComponent, _, elementRef, unwrap } from "refina"; 2 | 3 | export class MdRangeSlider extends TriggerComponent { 4 | sliderRef = elementRef<"mdui-range-slider">(); 5 | $main( 6 | lowValue: Model, 7 | highValue: Model, 8 | disabled = false, 9 | step = 1, 10 | min = 0, 11 | max = 100, 12 | ): this is { 13 | $ev: [low: number, high: number]; 14 | } { 15 | _.$ref(this.sliderRef); 16 | _._mdui_range_slider({ 17 | disabled, 18 | min, 19 | max, 20 | step, 21 | oninput: () => { 22 | const [newLow, newHigh] = this.sliderRef.current!.node.value; 23 | this.$updateModel(lowValue, newLow); 24 | this.$updateModel(highValue, newHigh); 25 | this.$fire([newLow, newHigh]); 26 | }, 27 | }); 28 | 29 | // TODO: remove this hack 30 | setTimeout(() => { 31 | this.sliderRef.current!.node.value = [ 32 | unwrap(lowValue), 33 | unwrap(highValue), 34 | ]; 35 | }); 36 | return this.$fired; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/mdui/src/components/segmentedButton.ts: -------------------------------------------------------------------------------- 1 | import { Content, TriggerComponent, _, byIndex } from "refina"; 2 | 3 | export class MdSegmentedButton extends TriggerComponent { 4 | icons?: (string | undefined)[]; 5 | endIcons?: (string | undefined)[]; 6 | $main( 7 | contents: Content[], 8 | disabled: readonly boolean[] | boolean = false, 9 | ): this is { 10 | $ev: number; 11 | } { 12 | const groupDisabled = disabled === true; 13 | const optionsDisabled = typeof disabled === "boolean" ? [] : disabled; 14 | 15 | _._mdui_segmented_button_group( 16 | { 17 | disabled: groupDisabled, 18 | }, 19 | _ => 20 | _.for(contents, byIndex, (content, index) => 21 | _._mdui_segmented_button( 22 | { 23 | disabled: optionsDisabled[index], 24 | icon: this.icons?.[index], 25 | endIcon: this.endIcons?.[index], 26 | onclick: this.$fireWith(index), 27 | }, 28 | content, 29 | ), 30 | ), 31 | ); 32 | return this.$fired; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/mdui/src/components/slider.ts: -------------------------------------------------------------------------------- 1 | import { Model, TriggerComponent, _, elementRef, unwrap } from "refina"; 2 | 3 | export class MdSlider extends TriggerComponent { 4 | sliderRef = elementRef<"mdui-slider">(); 5 | $main( 6 | value: Model, 7 | disabled = false, 8 | step = 1, 9 | min = 0, 10 | max = 100, 11 | ): this is { 12 | $ev: number; 13 | } { 14 | _.$ref(this.sliderRef); 15 | _._mdui_slider({ 16 | value: unwrap(value), 17 | disabled, 18 | min, 19 | max, 20 | step, 21 | oninput: () => { 22 | const newValue = this.sliderRef.current!.node.value; 23 | this.$updateModel(value, newValue); 24 | this.$fire(newValue); 25 | }, 26 | }); 27 | return this.$fired; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/mdui/src/components/switch.ts: -------------------------------------------------------------------------------- 1 | import { Model, TriggerComponent, _, elementRef, unwrap } from "refina"; 2 | 3 | export class MdSwitch extends TriggerComponent { 4 | switchRef = elementRef<"mdui-switch">(); 5 | $main( 6 | checked: Model, 7 | disabled = false, 8 | ): this is { 9 | $ev: boolean; 10 | } { 11 | _.$ref(this.switchRef); 12 | _._mdui_switch({ 13 | checked: unwrap(checked), 14 | disabled, 15 | onchange: () => { 16 | const newState = this.switchRef.current!.node.checked; 17 | this.$updateModel(checked, newState); 18 | this.$fire(newState); 19 | }, 20 | }); 21 | return this.$fired; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/mdui/src/components/table.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, LoopKey, _, byIndex } from "refina"; 2 | 3 | export class MdTable extends Component { 4 | $main( 5 | data: Iterable, 6 | head: Content[] | Content, 7 | key: LoopKey, 8 | row: (item: T, index: number) => void, 9 | ): void { 10 | _.$cls`mdui-table`; 11 | _._div({}, _ => { 12 | _._table({}, _ => { 13 | _._thead({}, _ => { 14 | if (Array.isArray(head)) { 15 | _.for(head, byIndex, item => { 16 | _._th({}, item); 17 | }); 18 | } else { 19 | _.embed(head); 20 | } 21 | }); 22 | _._tbody({}, _ => { 23 | _.for(data, key, (item, index) => { 24 | _._tr({}, _ => { 25 | row(item, index); 26 | }); 27 | }); 28 | }); 29 | }); 30 | }); 31 | } 32 | } 33 | 34 | export class MdTableHeader extends Component { 35 | $main(children: Content): void { 36 | _._th({}, children); 37 | } 38 | } 39 | 40 | export class MdTableCell extends Component { 41 | $main(children: Content): void { 42 | _._td({}, children); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/mdui/src/components/tooltip.ts: -------------------------------------------------------------------------------- 1 | import { Content, TriggerComponent, _ } from "refina"; 2 | 3 | export class MdTooltip extends TriggerComponent { 4 | $main( 5 | text: string, 6 | children: Content, 7 | ): this is { 8 | $ev: boolean; 9 | } { 10 | _._mdui_tooltip( 11 | { 12 | content: text, 13 | }, 14 | children, 15 | { 16 | open: this.$fireWith(true), 17 | close: this.$fireWith(false), 18 | }, 19 | ); 20 | return this.$fired; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/mdui/src/components/topAppBar.ts: -------------------------------------------------------------------------------- 1 | import { Component, Content, _ } from "refina"; 2 | 3 | export class MdTopAppBar extends Component { 4 | $main(children: Content, append?: Content): void { 5 | _._mdui_top_app_bar( 6 | {}, 7 | append 8 | ? _ => { 9 | _.embed(children); 10 | 11 | _.$css`flex-grow: 1`; 12 | _._div(); 13 | 14 | _.embed(append); 15 | } 16 | : children, 17 | ); 18 | } 19 | } 20 | 21 | export class MdTopAppBarTitle extends Component { 22 | $main(children: Content): void { 23 | _._mdui_top_app_bar_title({}, children); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/mdui/src/theme/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useColorScheme"; 2 | export * from "./useTheme"; 3 | -------------------------------------------------------------------------------- /packages/mdui/src/theme/useColorScheme.ts: -------------------------------------------------------------------------------- 1 | import { setColorScheme } from "mdui"; 2 | import type { CustomColor } from "mdui/functions/utils/colorScheme"; 3 | import { $contextFunc } from "refina"; 4 | 5 | let currentColorSchemeHex: string = "$unset"; 6 | 7 | export const useMdColorScheme = $contextFunc( 8 | () => (hex: string, customColors?: CustomColor[]) => { 9 | if (hex !== currentColorSchemeHex || customColors) { 10 | setColorScheme(hex, { customColors }); 11 | currentColorSchemeHex = hex; 12 | } 13 | }, 14 | ); 15 | -------------------------------------------------------------------------------- /packages/mdui/src/theme/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { $contextFunc, _ } from "refina"; 2 | 3 | export type MdUITheme = "light" | "dark" | "auto"; 4 | 5 | export const useMdTheme = $contextFunc( 6 | () => 7 | (theme: MdUITheme = "auto"): void => { 8 | if (_.$updateContext) { 9 | _.$body.addCls(`mdui-theme-${theme}`); 10 | } 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/mdui/styles.css: -------------------------------------------------------------------------------- 1 | @import url(mdui/mdui.css); 2 | -------------------------------------------------------------------------------- /packages/mdui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/mdui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { RefinaLib } from "vite-plugin-refina"; 3 | 4 | export default defineConfig({ 5 | plugins: [RefinaLib()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/router", 3 | "version": "0.0.0", 4 | "description": "The router for Refina framework.", 5 | "keywords": [ 6 | "refina", 7 | "router", 8 | "components" 9 | ], 10 | "files": [ 11 | "src" 12 | ], 13 | "type": "module", 14 | "main": "./src/index.ts", 15 | "types": "./src/index.ts", 16 | "exports": { 17 | ".": "./src/index.ts" 18 | }, 19 | "scripts": { 20 | "check": "tsc --noEmit" 21 | }, 22 | "author": "_Kerman", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/KermanX/refina" 26 | }, 27 | "readme": "https://github.com/KermanX/refina#readme", 28 | "bugs": "https://github.com/KermanX/refina/issues", 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@refina/tsconfig": "workspace:^", 32 | "typescript": "^5.4.2" 33 | }, 34 | "peerDependencies": { 35 | "refina": "workspace:^" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/router/src/beforeRoute.ts: -------------------------------------------------------------------------------- 1 | import { $contextFunc, _ } from "refina"; 2 | import { BeforeRouteContext, getIncomingRoute } from "./router"; 3 | 4 | export const beforeRoute = $contextFunc( 5 | () => 6 | (): // @ts-expect-error 7 | this is BeforeRouteContext => { 8 | const incomingRoute = getIncomingRoute(); 9 | if (incomingRoute) { 10 | // @ts-expect-error 11 | const pendingRoute = _.$ev as BeforeRouteContext; 12 | if (pendingRoute) { 13 | Object.assign(_, pendingRoute); 14 | return true; 15 | } 16 | } 17 | return false; 18 | }, 19 | ); 20 | -------------------------------------------------------------------------------- /packages/router/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "refina"; 2 | import { beforeRoute } from "./beforeRoute"; 3 | import { route, routeNotFound } from "./route"; 4 | import { Router } from "./router"; 5 | 6 | export default { 7 | name: "router", 8 | contextFuncs: { 9 | beforeRoute, 10 | route, 11 | routeNotFound, 12 | }, 13 | onInstall(app) { 14 | app.router = new Router(app); 15 | }, 16 | initContext(context) { 17 | context.$router = this.router; 18 | }, 19 | afterMain() { 20 | this.router.updateCurrentPath(); 21 | }, 22 | } satisfies Plugin; 23 | 24 | declare module "refina" { 25 | interface App { 26 | router: Router; 27 | } 28 | interface IntrinsicBaseContext { 29 | $router: Router; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/router/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function matchPath(path: string, currentPath: string) { 2 | const params: Record = {}; 3 | let j = 0; 4 | for (let i = 0; i < path.length; i++) { 5 | if (path[i] === ":") { 6 | let paramName = ""; 7 | while (++i < path.length && path[i] !== "/") { 8 | paramName += path[i]; 9 | } 10 | 11 | let paramValue = ""; 12 | while (j < currentPath.length && currentPath[j] !== "/") { 13 | paramValue += currentPath[j]; 14 | j++; 15 | } 16 | 17 | params[paramName] = paramValue; 18 | } else { 19 | if (path[i] !== currentPath[j]) { 20 | return false; 21 | } 22 | j++; 23 | } 24 | } 25 | if (j !== currentPath.length) { 26 | return false; 27 | } 28 | return params; 29 | } 30 | -------------------------------------------------------------------------------- /packages/router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/tests/core/__snapshots__/app.r.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`simple app > should render a simple app 1`] = `"

Hello World!

This is a paragraph.

"`; 4 | -------------------------------------------------------------------------------- /packages/tests/core/app.r.test.ts: -------------------------------------------------------------------------------- 1 | import { $app } from "refina"; 2 | import { initRootElement } from "../utils"; 3 | 4 | beforeAll(() => { 5 | initRootElement(); 6 | }); 7 | 8 | describe("simple app", () => { 9 | it("should render a simple app", async () => { 10 | const appInstance = $app([], _ => { 11 | _._h1({}, "Hello World!"); 12 | _.$cls`main`; 13 | _._div({}, _ => { 14 | _.$css`color: red`; 15 | _._p({}, "This is a paragraph."); 16 | }); 17 | }); 18 | 19 | await appInstance.promises.DOMUpdated; 20 | 21 | expect(document.body.innerHTML).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/tests", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "check": "tsc --noEmit" 8 | }, 9 | "devDependencies": { 10 | "@refina/tsconfig": "workspace:^", 11 | "typescript": "^5.4.2" 12 | }, 13 | "dependencies": { 14 | "@refina/transformer": "workspace:^", 15 | "refina": "workspace:^" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@refina/tsconfig", 3 | "compilerOptions": { 4 | "composite": true, 5 | "lib": ["ESNext", "DOM"], 6 | "types": ["vitest/globals"], 7 | "paths": { 8 | "refina": ["./packages/core/src"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/tests/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function initRootElement() { 2 | document.body.innerHTML = `
`; 3 | } 4 | -------------------------------------------------------------------------------- /packages/transformer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@refina/transformer", 3 | "version": "0.6.0", 4 | "description": "The code transformer for Refina framework.", 5 | "keywords": [ 6 | "refina", 7 | "transformer" 8 | ], 9 | "files": [ 10 | "src" 11 | ], 12 | "type": "module", 13 | "main": "./src/index.ts", 14 | "types": "./src/index.ts", 15 | "exports": { 16 | ".": "./src/index.ts" 17 | }, 18 | "scripts": { 19 | "check": "tsc --noEmit" 20 | }, 21 | "author": "_Kerman", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/KermanX/refina" 25 | }, 26 | "readme": "https://github.com/KermanX/refina#readme", 27 | "bugs": "https://github.com/KermanX/refina/issues", 28 | "license": "MIT", 29 | "dependencies": { 30 | "magic-string": "^0.30.8" 31 | }, 32 | "devDependencies": { 33 | "typescript": "^5.4.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/transformer/src/patterns.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | TEXT_NODE_TAGFUNC: /(?)?\s*\(/g, 5 | DIRECT_CALL: /(? boolean; 28 | } 29 | 30 | export function getFilter(options: CommonOptions) { 31 | const idFilter = createFilter( 32 | options.include ?? /\.[tj]s(\?|$)/, 33 | options.exclude ?? [/\?(.*&)?raw/, /node_modules/], 34 | ); 35 | const rawFilter = createFilter( 36 | /./, 37 | options.ignore ?? /^(((^|\n)\s*\/\/[^\n]*)|\n)*\s*\/\/\s*@refina-ignore/, 38 | ); 39 | return (id: string, raw: string) => idFilter(id) && rawFilter(raw); 40 | } 41 | -------------------------------------------------------------------------------- /packages/vite-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "lib": ["ESNext", "WebWorker"], 8 | "strict": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/vite-plugin/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from "tsup"; 2 | 3 | export const tsup: Options = { 4 | entry: ["./src/index.ts"], 5 | format: ["cjs", "esm"], 6 | target: "es2019", 7 | noExternal: ["@refina/transformer", "@refina/hmr"], 8 | sourcemap: true, 9 | dts: true, 10 | splitting: false, 11 | }; 12 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "docs" 3 | - "packages/**" 4 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | import Refina from "./packages/vite-plugin"; 3 | 4 | export default defineConfig({ 5 | plugins: [Refina()], 6 | test: { 7 | include: ["packages/tests/**/*.test.ts"], 8 | globals: true, 9 | environment: "jsdom", 10 | coverage: { 11 | provider: "istanbul", 12 | reporter: ["text", "html", "lcov"], 13 | }, 14 | }, 15 | }); 16 | --------------------------------------------------------------------------------