mirror of
https://github.com/tabler/tabler.git
synced 2026-08-05 19:03:18 +04:00
Merge remote-tracking branch 'origin/dev' into dev-dm-sans
# Conflicts: # core/.build/import-fonts.ts # core/package.json # core/scss/_core.scss # core/scss/fonts/_geist.scss # pnpm-lock.yaml
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
---
|
||||
description: Tabler docs package rules (Astro, MDX documentation pages)
|
||||
globs: docs/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Tabler docs (@tabler/docs)
|
||||
|
||||
## Development
|
||||
|
||||
When starting the dev server, use background mode:
|
||||
|
||||
```
|
||||
astro dev --background
|
||||
```
|
||||
|
||||
Manage the background server with `astro dev stop`, `astro dev status`, and `astro dev logs`.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation: https://docs.astro.build
|
||||
|
||||
Consult these guides before working on related tasks:
|
||||
|
||||
- [Adding pages, dynamic routes, or middleware](https://docs.astro.build/en/guides/routing/)
|
||||
- [Working with Astro components](https://docs.astro.build/en/basics/astro-components/)
|
||||
- [Using React, Vue, Svelte, or other framework components](https://docs.astro.build/en/guides/framework-components/)
|
||||
- [Adding or managing content](https://docs.astro.build/en/guides/content-collections/)
|
||||
- [Adding styles or using Tailwind](https://docs.astro.build/en/guides/styling/)
|
||||
- [Supporting multiple languages](https://docs.astro.build/en/guides/internationalization/)
|
||||
|
||||
## Writing documentation pages
|
||||
|
||||
Pages live in `pages/**/*.mdx`. Use the flat convention: leaf pages are `foo.mdx`; only pages with sub-pages use `foo/index.mdx` (both render as `/foo/`).
|
||||
|
||||
- Write in simple English: short sentences, common words, direct verbs (`Use`, `Add`, `Set`).
|
||||
- Component docs use singular naming in frontmatter and filename: `title: Card`, `card.mdx` (not `cards.mdx`).
|
||||
- Do not add meta authoring notes (e.g. "this snippet is copy-paste ready").
|
||||
|
||||
### Frontmatter
|
||||
|
||||
Static YAML only. Required keys plus the layout:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Alert
|
||||
summary: Short one-sentence summary of what this component does.
|
||||
description: Practical description of when and why to use it.
|
||||
layout: '@shared/layouts/DocsMdxLayout.astro'
|
||||
---
|
||||
```
|
||||
|
||||
Optional: `bootstrapLink: components/alerts/` when the component extends a Bootstrap component.
|
||||
|
||||
### Examples
|
||||
|
||||
Import shared components after the frontmatter and wrap previews in `Example`:
|
||||
|
||||
```mdx
|
||||
import Example from '@components/Example.astro';
|
||||
import Alert from '@ui/Alert.astro';
|
||||
|
||||
<Example>
|
||||
<Alert type="success" title="Wow!" description="Your account has been saved!" />
|
||||
</Example>
|
||||
```
|
||||
|
||||
- Add 1-2 short sentences before each `Example` describing what the preview shows.
|
||||
- `Example` props: `hideCode` (visual-only preview), `code` (override the displayed snippet), `centered`, `vertical`, `column` (narrow ~25rem column), `raw` (no preview wrappers), `bg="dark"` (background), `height`, `codeOnly`.
|
||||
- Raw HTML in an `Example` slot is reserialized by MDX — keep example markup on single lines next to tags to avoid stray `<p>` wrapping.
|
||||
|
||||
### Structure
|
||||
|
||||
Recommended `##` section order: `Overview`, `Installation` (optional), `Variants` (components) or `Usage` (utilities/workflows), `Accessibility`, `Examples` (optional), `SCSS variables` (optional), `Migration notes` (optional). Keep heading hierarchy consistent (`##` then `###`), use stable descriptive headings.
|
||||
|
||||
### Checklist
|
||||
|
||||
- Documented classes exist in `core/scss/` sources; do not document internal-only or removed classes.
|
||||
- Interactive examples include accessibility attributes (`aria-label`, semantic markup).
|
||||
- Verify examples render (`pnpm --filter @tabler/docs build`) and links work.
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
description: Tabler Project Rules
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
|
||||
# Tabler — project rules
|
||||
|
||||
Shared instructions for all AI agents (Claude Code, Cursor, etc.). Canonical agent configuration lives in `.agents/` (`rules/` + `skills/`); `.claude/skills` is a symlink into it.
|
||||
|
||||
## Project structure
|
||||
|
||||
- `core/` — `@tabler/core`: SCSS sources (`core/scss/`), JS, the distributable framework.
|
||||
- `preview/` — `@tabler/preview`: demo site built with Astro (pages in `preview/pages/*.astro`).
|
||||
- `docs/` — `@tabler/docs`: documentation site built with Astro (pages in `docs/pages/**/*.mdx`); docs-specific rules: `.agents/rules/docs.mdc`.
|
||||
- `shared/` — Astro components (`ui/`, `components/`, `layouts/`, `lib/`) shared by `preview` and `docs` via `@shared`; JSON data in `data/` via `@data`; plus `static/`.
|
||||
- `docs/components/` — docs-only Astro components (`Example`, menu, …) via `@components`.
|
||||
- Package manager: pnpm workspaces + turbo. Node.js >= 22.12.
|
||||
|
||||
## Language
|
||||
|
||||
- Write all repository content in English: code, comments, documentation, commit messages, PR titles and descriptions.
|
||||
- Use simple English in documentation: short sentences, common words, direct instructions.
|
||||
|
||||
## HTML and component guidelines
|
||||
|
||||
### Astro pages and components
|
||||
|
||||
- Pages are Astro components; reusable markup lives in `shared/components/`.
|
||||
- Icons: use the shared component — `<Icon name="home" />` (import from `@ui/Icon.astro`).
|
||||
- Links between preview pages are relative: `./job-listing.html` (root-level pages).
|
||||
- Boolean HTML attributes in Astro: `selected={true}` renders a bare attribute, but some attributes (e.g. `multiple`) render `="true"` — use `multiple ? '' : undefined` when a bare attribute is required.
|
||||
|
||||
### CSS classes
|
||||
|
||||
- Use Bootstrap 5 classes plus Tabler's custom classes.
|
||||
- Follow Tabler's CSS custom properties pattern: `--#{$prefix}component-property`.
|
||||
- Cards: `card` for containers, `card-body` for content, `card-header` / `card-title` for headers.
|
||||
- Buttons: `btn` for all buttons; `btn-primary` for primary actions; plain `btn` for secondary actions (do not use `btn-outline-secondary`); `btn-sm` for small buttons; `w-100` for full width.
|
||||
- Forms: `form-control` for inputs, `form-label` for labels, `form-check` for checkboxes/radios, `form-select` for dropdowns.
|
||||
- Layout: Bootstrap grid (`row`, `col-*`), `container-xl` for main containers, `page-wrapper` / `page-body` for page structure.
|
||||
- Badges: plain `badge` class; do not use `badge-outline` or `badge-primary`; do not change badge text color.
|
||||
- Markdown content: wrap in a `markdown` class container.
|
||||
|
||||
### Accessibility
|
||||
|
||||
- Use semantic HTML elements and proper heading hierarchy.
|
||||
- Include ARIA labels (e.g. `aria-label` for icon-only buttons) and alt text for images.
|
||||
|
||||
## SCSS guidelines
|
||||
|
||||
- Use semantic class names that describe purpose, not appearance.
|
||||
- Keep Bootstrap-compatible class naming conventions.
|
||||
- Group related styles together with clear comments; keep consistent spacing and indentation.
|
||||
|
||||
## Git conventions
|
||||
|
||||
### Branch naming
|
||||
|
||||
- Lowercase, kebab-case, format: `<type>/<short-description>` or `<type>/<issue-id>-<short-description>` (issue id as `gh-123`, never `#`).
|
||||
- Allowed types: `feat`, `fix`, `docs`, `chore`, `refactor`, `test`, `build`, `ci`, `perf`, `style`, `revert`.
|
||||
- Examples: `feat/gh-123-add-stepper-component`, `fix/markdown-table-overflow`.
|
||||
- Branch off `dev` by default.
|
||||
|
||||
### Commit messages
|
||||
|
||||
- English, conventional commit format when possible: `feat: add progress steps component`, `fix: update icon stroke width for better visibility`.
|
||||
|
||||
### Pull requests
|
||||
|
||||
- Title in English, capitalized, present tense, ideally <= 72 chars, no trailing period.
|
||||
- Description in English, skimmable (bullets, short paragraphs), focused on **why** the change is needed and its user-visible effect. Recommended template:
|
||||
|
||||
```md
|
||||
## Summary
|
||||
- <1-3 bullets describing the change and why>
|
||||
|
||||
## Changes
|
||||
- <key implementation notes, non-obvious decisions>
|
||||
```
|
||||
|
||||
- Mark WIP PRs as drafts.
|
||||
|
||||
### Changesets
|
||||
|
||||
- Each change gets a separate file in `.changeset/` with a descriptive kebab-case filename (e.g. `progress-sizes.md`).
|
||||
- One sentence per changeset, starting with an action verb (`Added`, `Updated`, `Fixed`, `Removed`), with backticks for code elements (`.btn-ghost`, `stroke-width`, `1.5`, `arrow-up`).
|
||||
- Version bumps: **major** = breaking changes; **minor** = new features, components, or pages; **patch** = bug fixes, small improvements, style/accessibility tweaks.
|
||||
- Packages: `"@tabler/core"` for SCSS/JS/classes, `"@tabler/preview"` for demo pages, `"@tabler/docs"` for documentation; list multiple packages when the change spans areas.
|
||||
- Example:
|
||||
|
||||
```md
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/preview": minor
|
||||
---
|
||||
|
||||
Added Progress Steps component for step-by-step navigation indicators.
|
||||
```
|
||||
|
||||
- If a PR changes SCSS or any package behavior, it must include a changeset.
|
||||
@@ -38,15 +38,25 @@ From the repository root:
|
||||
|
||||
Use this to infer **intent**, **user-visible behavior**, and **risk**—not only filenames.
|
||||
|
||||
**Vercel preview URL:** after the branch is pushed, Vercel deploys a preview. Build the link from the branch name:
|
||||
**Existing PR content:** Before drafting, check whether a PR already exists for this branch (e.g. `gh pr view --json title,body,number,url` for the current branch, or a PR number/URL the user gave you). If one exists, read its current title and body first:
|
||||
|
||||
- Carry over any issue references it already contains — `Closes #N`, `Fixes #N`, `Resolves #N`, or a plain `#N` mention — into the new body. Put them in **Notes / rollout** (or, if the PR uses a dedicated `Issue`/`Closes` line, keep that same convention) so a regenerated description never silently drops the link to a tracked issue.
|
||||
- Don't assume the diff alone tells you which issue this closes — the existing PR body is often the only place that link is recorded.
|
||||
- If no PR exists yet, skip this step (there is nothing to carry over).
|
||||
|
||||
**Vercel preview URL:** only when the diff has **visual / UI changes** a reviewer can check in the browser (CSS, preview pages, docs examples, components, layout). Skip the preview URL for non-visual work (agent skills, CI, tooling, docs prose-only, lockfile, config with no rendered UI effect).
|
||||
|
||||
When a preview URL is needed and the branch is pushed, Vercel deploys a preview. Build the link from the branch name:
|
||||
|
||||
1. Take the current branch name from `git branch --show-current`.
|
||||
2. Replace `/` with `-` and use lowercase (e.g. `feature/pricing-banner` → `feature-pricing-banner`).
|
||||
2. Replace `/` with `-`, **remove dots entirely** (do not replace them with dashes), and use lowercase (e.g. `feature/pricing-banner` → `feature-pricing-banner`, `update-icons-3.45.0` → `update-icons-3450`).
|
||||
3. Insert into: `https://tabler-git-{branch-slug}-tabler-io.vercel.app/`
|
||||
|
||||
Example: branch `bundle-framing-posthog-flag` → `https://tabler-git-bundle-framing-posthog-flag-tabler-io.vercel.app/`
|
||||
Examples:
|
||||
- branch `bundle-framing-posthog-flag` → `https://tabler-git-bundle-framing-posthog-flag-tabler-io.vercel.app/`
|
||||
- branch `update-icons-3.45.0` → `https://tabler-git-update-icons-3450-tabler-io.vercel.app/`
|
||||
|
||||
If the diff touches specific routes or pages (e.g. `src/app/pricing/`, `/pricing`), append that path to the preview URL (e.g. `…vercel.app/pricing`). Mention the exact path(s) in **Preview**.
|
||||
If the diff touches specific routes or pages, append that path to the preview URL. **Preview pages use the `.html` extension**: a page from `preview/pages/icons.astro` is served at `/icons.html` (e.g. `…vercel.app/icons.html`), not `/icons`. Docs pages use directory URLs without extension: a page from `docs/pages/ui/components/badge.mdx` is served at `/ui/components/badge/`. Mention the exact path(s) in **Preview**.
|
||||
|
||||
## 3. Title
|
||||
|
||||
@@ -65,7 +75,7 @@ Remove `money-back-guarantee` flag; show label on paid plans
|
||||
|
||||
## 4. Body (markdown template)
|
||||
|
||||
Output the body in a **second** fenced **`markdown`** block after the title block. Use this structure inside that block. Omit **Notes / rollout** if nothing applies. Do **not** add a separate `## Test plan` section unless the user explicitly asks for one—use **Preview** instead.
|
||||
Output the body in a **second** fenced **`markdown`** block after the title block. Use this structure inside that block. Omit **Notes / rollout** if nothing applies. Omit the **URL** line (or the whole **Preview** section) when there are no visual changes—do not link a Vercel preview in that case. Do **not** add a separate `## Test plan` section unless the user explicitly asks for one—use **Preview** (or a short “how to review” note under Summary) instead.
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
@@ -84,9 +94,9 @@ Output the body in a **second** fenced **`markdown`** block after the title bloc
|
||||
|
||||
**Summary bullets:** 1–4 bullets tying changes to product/engineering impact.
|
||||
|
||||
**Preview:** Always include when the branch is pushed (or note that preview is unavailable until push). Use the Vercel URL format from §2. Link the most relevant path(s)—homepage only if changes are global; otherwise deep-link (e.g. `/pricing`, `/blog/…`). **How to test** should be actionable: which page, which UI element or behavior changed, and what the reviewer should expect to see.
|
||||
**Preview:** Include a Vercel **URL** only when the change is visual and the branch is pushed (or note that preview is unavailable until push). If there are no visual changes, omit the preview link entirely—do not add a homepage or generic preview URL. When a URL is included, use the Vercel format from §2 and deep-link the most relevant path(s)—homepage only if changes are global. **How to test** should be actionable: which page, which UI element or behavior changed, and what the reviewer should expect to see. For non-visual PRs, say how to review the diff instead (e.g. which files to read).
|
||||
|
||||
**Notes:** Feature flags, env vars, backwards compatibility—only when evidenced in the diff or commit messages.
|
||||
**Notes:** Feature flags, env vars, backwards compatibility—only when evidenced in the diff or commit messages. Also include any issue reference carried over from an existing PR (see §2), e.g. `Closes #123`.
|
||||
|
||||
## 5. Language (simple English)
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
---
|
||||
name: write-docs
|
||||
description: Write, update, or suggest Tabler documentation pages in simple English using the current docs schema. Use whenever the user asks to create docs, edit docs, add new feature docs to an existing page, or standardize docs structure across any docs category. Also consult this skill proactively — without being asked — whenever a new visual UI component (shared/ui/*.astro), plugin, or other user-facing feature has just been added or changed and has no matching, up-to-date page under docs/pages/**, since undocumented components are easy to forget about.
|
||||
---
|
||||
|
||||
# Write Tabler Docs
|
||||
|
||||
Follow the current Tabler documentation schema. How far to go depends on how the skill was triggered — see section 1.
|
||||
|
||||
## 1. Scope and behavior
|
||||
|
||||
- Works for any docs page type under `docs/pages/**` (components, utilities, forms, layout, plugins, base, getting started, emails, illustrations, icons, index pages). Pages are MDX: leaf pages `foo.mdx`, parents with sub-pages `foo/index.mdx`.
|
||||
- Edit existing pages when the user asks to document new functionality in an existing component/page.
|
||||
- Create new pages when needed.
|
||||
- Use simple English in all prose.
|
||||
|
||||
### Direct request vs. proactive suggestion
|
||||
|
||||
- If the user explicitly asked for docs (e.g. "document this", "write docs for X", "update the badge page"), write directly in the files — do not stop at draft mode.
|
||||
- If this skill triggered on its own because something was built or changed without matching docs, don't start editing files unprompted. Point out what's missing in one or two sentences (which component/plugin, which page it would live under) and ask whether to write it. Proceed to write directly, per the rule above, only once the user says go.
|
||||
|
||||
## 2. Required language style (simple English)
|
||||
|
||||
- Use short sentences and common words.
|
||||
- Keep one main idea per sentence when possible.
|
||||
- Prefer direct verbs: `Use`, `Add`, `Set`, `Show`.
|
||||
- Avoid buzzwords and marketing filler.
|
||||
- Keep paragraphs short and easy to scan.
|
||||
|
||||
## 3. Frontmatter rules
|
||||
|
||||
Default frontmatter (required unless user asks otherwise):
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: ...
|
||||
summary: ...
|
||||
description: ...
|
||||
layout: '@shared/layouts/DocsMdxLayout.astro'
|
||||
---
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Keep frontmatter static YAML only.
|
||||
- By default, include only `title`, `summary`, `description`, and `layout`.
|
||||
- Add extended keys (for example `bootstrapLink`, `order`, `plugin`, `docs-libs`, `redirect`) only when the user explicitly asks for them or nearby pages in the same category use them.
|
||||
|
||||
## 4. Documentation schema to follow
|
||||
|
||||
Use this section order as the default structure:
|
||||
|
||||
1. `## Overview`
|
||||
2. `## Installation` (optional)
|
||||
3. `## Variants` or `## Usage` (choose one based on page type)
|
||||
4. `## Examples` (optional)
|
||||
5. `## Accessibility`
|
||||
6. `## SCSS variables` (optional)
|
||||
7. `## Migration notes` (optional)
|
||||
|
||||
Guidance by page type:
|
||||
|
||||
- Component-like pages: prefer `Variants`.
|
||||
- Utility or workflow pages: prefer `Usage`.
|
||||
- Library/package pages (for example icons, emails): keep `Installation` + `Usage`.
|
||||
- Intro/index pages: may use concise overview sections, but keep hierarchy clear and consistent.
|
||||
|
||||
## 5. Example and snippet pattern
|
||||
|
||||
For visual examples, use the shared `Example` component (import after the frontmatter):
|
||||
|
||||
```mdx
|
||||
import Example from '@components/Example.astro';
|
||||
|
||||
<Example>
|
||||
<button class="btn btn-primary">Primary button</button>
|
||||
</Example>
|
||||
```
|
||||
|
||||
- Add 1-2 short sentences before each preview block to explain what the preview shows.
|
||||
- Use props when useful: `hideCode`, `centered`, `vertical`, `raw`, `column`, `bg`, `height`, `codeOnly`.
|
||||
- For a cleaner displayed snippet than the rendered preview, pass `code={...}`.
|
||||
- For icons and other shared components inside examples, import them from `@ui/` (for example `<Icon name="plus" />`).
|
||||
- Raw HTML in the `Example` slot is reserialized by MDX — keep markup lines attached to tags to avoid stray `<p>` wrapping.
|
||||
|
||||
## 6. Analyzing the component or plugin source
|
||||
|
||||
Prose and example markup are only as accurate as their source. Before writing examples, ground them in the actual implementation rather than in guesses or in what a similar-looking page happens to show — nearby `.mdx` pages are a good style reference but can drift out of sync with the code.
|
||||
|
||||
- **UI components** live at `shared/ui/<ComponentName>.astro`. Read its `interface Props` block: each field (and its JSDoc comment, when present) is the authoritative list of supported options, defaults, and edge cases — for example `shared/ui/Badge.astro` documents `scale`, `light`, `icon`, and `personId` this way.
|
||||
- **Plugins and utilities** are often CSS-class-driven rather than a single Astro component. Check `core/scss/**/_<name>.scss` (or `core/scss/tabler-<name>.scss`) for the class variants that actually exist, and `shared/data/<name>.json` when the plugin is data-driven (e.g. `flags`, `payments`, `social-icons`).
|
||||
- **Real usage** in `preview/pages/**` renders the finished HTML and often exercises more combinations in practice than the docs page does — a useful cross-check for realistic examples.
|
||||
- Turn what you find into `<Example>` blocks that reflect real, valid prop/class combinations rather than invented markup.
|
||||
|
||||
## 7. Registering new pages in the docs menu
|
||||
|
||||
`docs/pages/**` is not scanned automatically to build navigation — the sidebar tree is frozen in `shared/data/docs.json` (see the comment in `docs/components/DocsMenu.astro`). A new leaf page with no entry there exists but is unreachable from the docs site.
|
||||
|
||||
- Only touch `docs.json` for genuinely **new** pages. Editing an existing page needs no menu change.
|
||||
- Find the matching section in the `menu` array by directory: `docs/pages/ui/components/*` → the `"Components"` entry under `"Tabler UI"`, `docs/pages/ui/plugins/*` → `"Plugins"`, `docs/pages/icons/libraries/*` → `"Libraries"` under `"Tabler Icons"`, and so on — the section `title`/`url` pairs mirror the `docs/pages/ui/*` and `docs/pages/icons/*` subdirectory names.
|
||||
- Add `{ "title": "<Title Case name>", "url": "/<matching>/<slug>/" }` to that section's `children`, in the same alphabetical position its neighbors already follow.
|
||||
- The `title` and `url` must match the new page's frontmatter `title` and its file path exactly, or the sidebar entry will point at the wrong place.
|
||||
|
||||
## 8. Workflow for each request
|
||||
|
||||
1. Identify target file(s) in `docs/pages/**` (or determine none exist yet — see section 7).
|
||||
2. Read the target page and 2-3 nearby pages in the same category to match tone and conventions.
|
||||
3. Read the underlying component/plugin source per section 6 so examples are accurate, not guessed.
|
||||
4. Apply the schema from section 4.
|
||||
5. Write/update the page directly in file(s).
|
||||
6. If the page is new, add its entry to `shared/data/docs.json` per section 7.
|
||||
7. Keep only required frontmatter by default.
|
||||
8. Ensure prose is in simple English.
|
||||
9. Verify heading hierarchy (`##` then `###`) and snippet validity.
|
||||
|
||||
## 9. Rules while updating existing docs
|
||||
|
||||
- Preserve valid existing content that is still correct.
|
||||
- Add new functionality docs as focused new subsection(s), usually under `Variants`, `Usage`, or `Examples`.
|
||||
- Remove or rewrite only conflicting or outdated text.
|
||||
- Keep naming and terms consistent across the page.
|
||||
|
||||
## 10. Quality checklist before finishing
|
||||
|
||||
- [ ] Uses simple English.
|
||||
- [ ] Examples reflect the real Props/classes found in the source (section 6), not guesses.
|
||||
- [ ] Frontmatter uses static YAML.
|
||||
- [ ] Default frontmatter contains only `title`, `summary`, `description`, `layout` (unless user requested extra keys).
|
||||
- [ ] Follows schema and heading hierarchy.
|
||||
- [ ] Examples use the `Example` component pattern where applicable.
|
||||
- [ ] Accessibility section exists for interactive UI docs.
|
||||
- [ ] New pages have a matching entry in `shared/data/docs.json` (section 7); edits to existing pages don't touch it.
|
||||
- [ ] No mention of changeset reminders unless user asks.
|
||||
@@ -0,0 +1,157 @@
|
||||
// Single-pass CSS pipeline shared by @tabler/core and @tabler/preview.
|
||||
//
|
||||
// Replaces the `sass` + `postcss` + `cleancss` CLI chain: each output file used
|
||||
// to be written twice (sass first, then postcss rewriting it in place), which
|
||||
// briefly exposed un-prefixed CSS on disk and made file watchers (dev-server
|
||||
// live reload) fire once per write burst. Here everything runs in-process and
|
||||
// each output file is written exactly once, fully processed.
|
||||
//
|
||||
// All options mirror the replaced CLI invocations — the output was verified
|
||||
// byte-identical (css, rtl, min and their source maps), bar the deliberate
|
||||
// breaks:afterComment deviation documented on minify() below:
|
||||
// - sass --no-source-map --load-path=node_modules --style expanded
|
||||
// - postcss (autoprefixer cascade:false, rtlcss for --rtl, external map with
|
||||
// annotation + sourcesContent — the former core/.build/postcss.config.mjs)
|
||||
// - cleancss -O1 --format breakWith=lf --with-rebase --source-map
|
||||
// --source-map-inline-sources --batch --batch-suffix ".min"
|
||||
//
|
||||
// --banner replaces the former core/.build/add-banner.ts, which ran as a
|
||||
// separate step *after* minification and rewrote the finished files in place.
|
||||
// Prepending the 6-line license comment there shifted every rule down without
|
||||
// touching the already-written .map, so all mappings pointed 7 lines too high
|
||||
// (#2766). Here the banner is part of the css before any map is generated, so
|
||||
// postcss and clean-css both account for it.
|
||||
//
|
||||
// Usage: tsx ../.build/build-css.ts <scssDir> <outDir> [--rtl] [--minify] [--banner]
|
||||
// (cwd = the package)
|
||||
/// <reference path="./modules.d.ts" />
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { EOL } from 'node:os'
|
||||
import { basename, join, resolve } from 'node:path'
|
||||
import { compile as compileSass } from 'sass'
|
||||
import postcss, { type Result } from 'postcss'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import rtlcss from 'rtlcss'
|
||||
import CleanCSS from 'clean-css'
|
||||
import { addBanner } from '../shared/banner/index.mjs'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const flags = args.filter((arg) => arg.startsWith('--'))
|
||||
const [scssDir, outDir] = args.filter((arg) => !arg.startsWith('--'))
|
||||
if (!scssDir || !outDir) {
|
||||
console.error('usage: tsx build-css.ts <scssDir> <outDir> [--rtl] [--minify] [--banner]')
|
||||
process.exit(1)
|
||||
}
|
||||
const withRtl = flags.includes('--rtl')
|
||||
const withMinify = flags.includes('--minify')
|
||||
const withBanner = flags.includes('--banner')
|
||||
|
||||
const mapOptions = { inline: false, annotation: true, sourcesContent: true }
|
||||
const written: string[] = []
|
||||
const pendingWrites: { file: string; content: string }[] = []
|
||||
|
||||
// Skipping identical content keeps file watchers quiet for outputs a given scss
|
||||
// edit did not actually change (the dev servers full-reload on these writes).
|
||||
function queueWrite(file: string, content: string) {
|
||||
if (existsSync(file) && readFileSync(file, 'utf8') === content) return
|
||||
pendingWrites.push({ file, content })
|
||||
}
|
||||
|
||||
// sass + autoprefixer for one entry: scss/x.scss → outDir/x.css (+ .map)
|
||||
async function compile(entry: string): Promise<{ outFile: string; result: Result }> {
|
||||
const outFile = join(outDir, `${basename(entry, '.scss')}.css`)
|
||||
const compiled = compileSass(join(scssDir, entry), { loadPaths: ['node_modules'], style: 'expanded' })
|
||||
// The sass CLI ends its output files with a newline; the JS API's css string
|
||||
// does not. Keep the byte-identical CLI behavior (the annotation comment then
|
||||
// lands after a blank line, exactly like `postcss --replace` produced).
|
||||
const css = `${compiled.css}\n`
|
||||
// Banner goes in before postcss runs, so the map it generates already counts
|
||||
// the banner's lines — and so does the rtl map chained onto it below.
|
||||
const input = withBanner ? addBanner(css, outFile) : css
|
||||
const result = await postcss([autoprefixer({ cascade: false })]).process(input, {
|
||||
from: outFile,
|
||||
to: outFile,
|
||||
map: mapOptions,
|
||||
})
|
||||
queueWrite(outFile, result.css)
|
||||
if (result.map) queueWrite(`${outFile}.map`, result.map.toString())
|
||||
written.push(outFile)
|
||||
return { outFile, result }
|
||||
}
|
||||
|
||||
// rtlcss over the prefixed output: outDir/x.css → outDir/x.rtl.css (+ .map)
|
||||
async function rtl(entry: string, outFile: string, base: Result): Promise<void> {
|
||||
const rtlFile = join(outDir, `${basename(entry, '.scss')}.rtl.css`)
|
||||
// Same input the CLI read from disk: the prefixed css including its
|
||||
// sourceMappingURL annotation, so postcss picks up the previous map.
|
||||
// The previous map is not on disk yet (writes are buffered) — pass it in.
|
||||
const result = await postcss([autoprefixer({ cascade: false }), rtlcss()]).process(base.css, {
|
||||
from: outFile,
|
||||
to: rtlFile,
|
||||
map: { ...mapOptions, prev: base.map.toString() },
|
||||
})
|
||||
queueWrite(rtlFile, result.css)
|
||||
if (result.map) queueWrite(`${rtlFile}.map`, result.map.toString())
|
||||
written.push(rtlFile)
|
||||
}
|
||||
|
||||
// All compile work is done before the first write: compiles take seconds while
|
||||
// writes take milliseconds, so watchers see one tight burst instead of writes
|
||||
// spread across the whole build (which would need a long reload debounce).
|
||||
function flushWrites(): void {
|
||||
for (const { file, content } of pendingWrites) {
|
||||
writeFileSync(file, content)
|
||||
console.log(`build-css: ${file}`)
|
||||
}
|
||||
}
|
||||
|
||||
// clean-css over every produced file: outDir/x.css → outDir/x.min.css (+ .map).
|
||||
// Mirrors clean-css-cli's option coercion and batch output naming/annotation
|
||||
// (see the clean-css-cli package's index.js).
|
||||
//
|
||||
// breaks:afterComment is the one deviation: clean-css counts the lines a kept
|
||||
// `/*! … */` comment spans but not the `*/` it leaves on the last one, so every
|
||||
// mapping on that line comes out `'*/'.length` columns short. Ending the line
|
||||
// after the comment puts the css at column 0 — exactly where the map says.
|
||||
async function minify(files: string[]): Promise<void> {
|
||||
const minified = await new CleanCSS({
|
||||
batch: true,
|
||||
format: 'breakWith=lf;breaks:afterComment=on',
|
||||
inline: 'local',
|
||||
level: { 1: true },
|
||||
rebase: true,
|
||||
rebaseTo: resolve(outDir),
|
||||
returnPromise: true,
|
||||
sourceMap: true,
|
||||
sourceMapInlineSources: true,
|
||||
}).minify(files)
|
||||
for (const inputFile of files) {
|
||||
const fileResult = minified[inputFile]
|
||||
if (!fileResult) throw new Error(`build-css: no minify result for ${inputFile}`)
|
||||
if (fileResult.errors.length > 0) throw new Error(fileResult.errors.join('\n'))
|
||||
for (const warning of fileResult.warnings) console.warn(`build-css: ${warning}`)
|
||||
const minFile = inputFile.replace(/\.css$/, '.min.css')
|
||||
writeFileSync(minFile, `${fileResult.styles}${EOL}/*# sourceMappingURL=${basename(minFile)}.map */`)
|
||||
writeFileSync(`${minFile}.map`, fileResult.sourceMap.toString())
|
||||
console.log(`build-css: ${minFile}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapped in a function because tsx compiles root-level .ts as CJS, where
|
||||
// top-level await is unavailable.
|
||||
async function main() {
|
||||
const entries = readdirSync(scssDir).filter((file) => file.endsWith('.scss') && !file.startsWith('_'))
|
||||
mkdirSync(outDir, { recursive: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const { outFile, result } = await compile(entry)
|
||||
if (withRtl) await rtl(entry, outFile, result)
|
||||
}
|
||||
flushWrites()
|
||||
if (withMinify) await minify(written)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,156 @@
|
||||
// Shared Astro integration that rebuilds a package's public/ directory from
|
||||
// source and generated workspace assets (the Eleventy passthrough-copy
|
||||
// equivalent), then keeps it in sync during dev. Used by @tabler/docs and
|
||||
// @tabler/preview — each passes its own manifest from astro.config.mjs.
|
||||
//
|
||||
// Runs inside Astro's own lifecycle (astro:config:done) instead of a standalone
|
||||
// pre-script, so the copy is ordered by Astro for both dev and build — mirrors
|
||||
// Bootstrap's site/src/libs/astro.ts integration. Cross-package ordering (core
|
||||
// before preview before docs) still comes from the turbo dev graph.
|
||||
import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync, statSync } from 'node:fs'
|
||||
import { dirname, join, relative, sep } from 'node:path'
|
||||
|
||||
// Structural stand-ins for the astro/vite types used here. The real `astro`
|
||||
// package is not resolvable from the repo root (it lives in docs/ and preview/
|
||||
// node_modules), and `astro check` verifies the produced object against the
|
||||
// real AstroIntegration type at the usage site in each astro.config.mjs anyway.
|
||||
interface Logger {
|
||||
info(message: string): void
|
||||
warn(message: string): void
|
||||
}
|
||||
interface DevServer {
|
||||
watcher: {
|
||||
add(paths: string[]): void
|
||||
on(event: string, callback: (file: string) => void): void
|
||||
}
|
||||
hot: { send(payload: { type: string }): void }
|
||||
}
|
||||
interface Integration {
|
||||
name: string
|
||||
hooks: {
|
||||
'astro:config:setup'?: (options: { command: string }) => void
|
||||
'astro:config:done'?: (options: { logger: Logger }) => void
|
||||
'astro:server:setup'?: (options: { server: DevServer; logger: Logger }) => void
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A directory (or single file) to copy into publicDir. `requiredFile` guards
|
||||
* against a present-but-stale source; `required: false` only warns when the
|
||||
* source is missing; `allowDestinationFallback` keeps the existing destination
|
||||
* when the source disappears mid-copy (e.g. @tabler/core rebuilding
|
||||
* concurrently).
|
||||
*/
|
||||
interface CopyEntry {
|
||||
from: string
|
||||
to: string
|
||||
label: string
|
||||
required?: boolean
|
||||
requiredFile?: string
|
||||
allowDestinationFallback?: boolean
|
||||
}
|
||||
|
||||
interface CopyAssetsOptions {
|
||||
/** repo root, for log-friendly relative paths */
|
||||
repo: string
|
||||
/** the package's public/ directory (fully generated) */
|
||||
publicDir: string
|
||||
copies: CopyEntry[]
|
||||
/** generated source dirs to live-sync into publicDir while `astro dev` runs */
|
||||
syncDirs?: { from: string; to: string }[]
|
||||
/** dirs written to directly by watchers — already in place, only trigger a browser reload */
|
||||
reloadDirs?: string[]
|
||||
}
|
||||
|
||||
export function copyAssets({ repo, publicDir, copies, syncDirs = [], reloadDirs = [] }: CopyAssetsOptions): Integration {
|
||||
let command: string
|
||||
|
||||
function rebuildPublicDir(logger: Logger) {
|
||||
// Always start from a clean public/ (mirrors Bootstrap's own docs
|
||||
// integration): running this twice in a row, or without a prior
|
||||
// `pnpm run clean`, must never accumulate stale/nested content —
|
||||
// see preview/.build/vite.config.mts for the unbounded-growth story.
|
||||
rmSync(publicDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })
|
||||
mkdirSync(publicDir, { recursive: true })
|
||||
|
||||
for (const { from, to, label, required = true, requiredFile, allowDestinationFallback } of copies) {
|
||||
if (!existsSync(from) || (requiredFile && !existsSync(requiredFile))) {
|
||||
const message = `copy-assets: missing ${requiredFile ?? from}`
|
||||
if (required) throw new Error(`${message} — build ${label} first`)
|
||||
logger.warn(`${message} (skipped — build ${label} to get it)`)
|
||||
continue
|
||||
}
|
||||
mkdirSync(dirname(to), { recursive: true })
|
||||
if (statSync(from).isFile()) {
|
||||
copyFileSync(from, to)
|
||||
continue
|
||||
}
|
||||
try {
|
||||
// dereference: sources may be symlinks (e.g. preview/static → shared/static)
|
||||
cpSync(from, to, {
|
||||
recursive: true,
|
||||
dereference: true,
|
||||
filter: (src) => !src.includes('/.vscode') && !src.includes('\\.vscode'),
|
||||
})
|
||||
} catch (error) {
|
||||
// In turbo dev, @tabler/core can clean dist while we copy it.
|
||||
// If fallback is allowed and destination exists, keep current assets.
|
||||
if (allowDestinationFallback && error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT' && existsSync(to)) {
|
||||
logger.warn(`copy-assets: source changed during copy ${from} (using existing ${to})`)
|
||||
continue
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
logger.info('public/ rebuilt from workspace assets')
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'copy-assets',
|
||||
hooks: {
|
||||
'astro:config:setup': (options) => {
|
||||
command = options.command
|
||||
},
|
||||
'astro:config:done': ({ logger }) => {
|
||||
// `astro check`/`astro sync` (command 'sync') runs these hooks too, but
|
||||
// only needs types — public/ is irrelevant there and CI's type-check
|
||||
// job has no built workspace assets to copy.
|
||||
if (command === 'sync') return
|
||||
rebuildPublicDir(logger)
|
||||
},
|
||||
'astro:server:setup': ({ server, logger }) => {
|
||||
if (command !== 'dev') return
|
||||
// The copy above runs once at startup, so edits rebuilt into the source
|
||||
// dirs during `pnpm run dev` (e.g. core/dist by core's watchers) would
|
||||
// never reach the served public/ copy. Watch them, sync changed files
|
||||
// into public/, and trigger a browser reload.
|
||||
server.watcher.add([...syncDirs.map((dir) => dir.from), ...reloadDirs])
|
||||
let reloadTimer: ReturnType<typeof setTimeout> | undefined
|
||||
const scheduleReload = (file: string) => {
|
||||
// Source maps piggyback on their css/js file's reload. build-css.ts
|
||||
// buffers all writes into one tight burst, so a short quiet window is
|
||||
// enough to coalesce a whole rebuild into a single reload.
|
||||
if (file.endsWith('.map')) return
|
||||
clearTimeout(reloadTimer)
|
||||
reloadTimer = setTimeout(() => {
|
||||
server.hot.send({ type: 'full-reload' })
|
||||
logger.info(`reloaded after change in ${relative(repo, file)}`)
|
||||
}, 250)
|
||||
}
|
||||
const sync = (file: string) => {
|
||||
for (const { from, to } of syncDirs) {
|
||||
if (!file.startsWith(from + sep)) continue
|
||||
const dest = join(to, relative(from, file))
|
||||
mkdirSync(dirname(dest), { recursive: true })
|
||||
copyFileSync(file, dest)
|
||||
scheduleReload(file)
|
||||
return
|
||||
}
|
||||
if (reloadDirs.some((dir) => file.startsWith(dir + sep))) scheduleReload(file)
|
||||
}
|
||||
server.watcher.on('add', sync)
|
||||
server.watcher.on('change', sync)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
// Minimal ambient typings for build-tool dependencies that ship no types.
|
||||
// Only the surface used by .build/build-css.ts is declared.
|
||||
declare module 'rtlcss' {
|
||||
import type { Plugin } from 'postcss'
|
||||
export default function rtlcss(config?: unknown): Plugin
|
||||
}
|
||||
|
||||
declare module 'clean-css' {
|
||||
interface MinifyResult {
|
||||
styles: string
|
||||
errors: string[]
|
||||
warnings: string[]
|
||||
sourceMap: { toString(): string }
|
||||
}
|
||||
export default class CleanCSS {
|
||||
constructor(options: Record<string, unknown>)
|
||||
minify(input: string[]): Promise<Record<string, MinifyResult>>
|
||||
}
|
||||
}
|
||||
+40
-51
@@ -8,72 +8,61 @@ import * as prettier from 'prettier'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const docs: string[] = sync(join(__dirname, '..', 'docs', '**', '*.md'))
|
||||
const docs: string[] = sync(join(__dirname, '..', 'docs', 'pages', '**', '*.mdx'))
|
||||
|
||||
async function formatHTML(htmlString: string): Promise<string> {
|
||||
try {
|
||||
const formattedHtml = await prettier.format(htmlString, {
|
||||
parser: 'html',
|
||||
printWidth: 100,
|
||||
})
|
||||
return formattedHtml
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error('Error formatting HTML:', errorMessage)
|
||||
return htmlString // Return original in case of an error
|
||||
}
|
||||
try {
|
||||
const formattedHtml = await prettier.format(htmlString, {
|
||||
parser: 'html',
|
||||
printWidth: 100,
|
||||
})
|
||||
return formattedHtml
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error('Error formatting HTML:', errorMessage)
|
||||
return htmlString // Return original in case of an error
|
||||
}
|
||||
}
|
||||
|
||||
async function replaceAsync(
|
||||
str: string,
|
||||
regex: RegExp,
|
||||
asyncFn: (...args: string[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const matches = [...str.matchAll(regex)]
|
||||
async function replaceAsync(str: string, regex: RegExp, asyncFn: (...args: string[]) => Promise<string>): Promise<string> {
|
||||
const matches = [...str.matchAll(regex)]
|
||||
|
||||
const replacements = await Promise.all(
|
||||
matches.map(async (match: RegExpMatchArray) => asyncFn(...match))
|
||||
)
|
||||
const replacements = await Promise.all(matches.map(async (match: RegExpMatchArray) => asyncFn(...match)))
|
||||
|
||||
let result = str
|
||||
matches.forEach((match: RegExpMatchArray, i: number) => {
|
||||
result = result.replace(match[0], replacements[i])
|
||||
})
|
||||
let result = str
|
||||
matches.forEach((match: RegExpMatchArray, i: number) => {
|
||||
result = result.replace(match[0], replacements[i])
|
||||
})
|
||||
|
||||
return result
|
||||
return result
|
||||
}
|
||||
|
||||
async function processFiles(): Promise<void> {
|
||||
for (const file of docs) {
|
||||
const oldContent = readFileSync(file, 'utf8')
|
||||
for (const file of docs) {
|
||||
const oldContent = readFileSync(file, 'utf8')
|
||||
|
||||
// get codeblocks from markdown
|
||||
const content = await replaceAsync(
|
||||
oldContent,
|
||||
/(```([a-z0-9]+).*?\n)(.*?)(```)/gs,
|
||||
async (m: string, m1: string, m2: string, m3: string, m4: string) => {
|
||||
if (m2 === 'html') {
|
||||
let formattedHtml = await formatHTML(m3)
|
||||
// get codeblocks from markdown
|
||||
const content = await replaceAsync(oldContent, /(```([a-z0-9]+).*?\n)(.*?)(```)/gs, async (m: string, m1: string, m2: string, m3: string, m4: string) => {
|
||||
if (m2 === 'html') {
|
||||
let formattedHtml = await formatHTML(m3)
|
||||
|
||||
// remove empty lines
|
||||
formattedHtml = formattedHtml.replace(/^\s*[\r\n]/gm, '')
|
||||
// remove empty lines
|
||||
formattedHtml = formattedHtml.replace(/^\s*[\r\n]/gm, '')
|
||||
|
||||
return m1 + formattedHtml.trim() + '\n' + m4
|
||||
}
|
||||
return m.trim()
|
||||
}
|
||||
)
|
||||
return m1 + formattedHtml.trim() + '\n' + m4
|
||||
}
|
||||
return m.trim()
|
||||
})
|
||||
|
||||
if (content !== oldContent) {
|
||||
writeFileSync(file, content, 'utf8')
|
||||
console.log(`Reformatted ${file}`)
|
||||
}
|
||||
}
|
||||
if (content !== oldContent) {
|
||||
writeFileSync(file, content, 'utf8')
|
||||
console.log(`Reformatted ${file}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processFiles().catch((error) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error('Error processing files:', errorMessage)
|
||||
process.exit(1)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error('Error processing files:', errorMessage)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
|
||||
@@ -3,74 +3,63 @@ import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig, type UserConfig } from 'vite'
|
||||
|
||||
interface CreateViteConfigOptions {
|
||||
entry: string
|
||||
name?: string
|
||||
fileName: string | ((format: string) => string)
|
||||
formats: ('es' | 'umd' | 'iife' | 'cjs')[]
|
||||
outDir: string
|
||||
banner?: string
|
||||
minify?: boolean | 'esbuild'
|
||||
entry: string
|
||||
name?: string
|
||||
fileName: string | ((format: string) => string)
|
||||
formats: ('es' | 'umd' | 'iife' | 'cjs')[]
|
||||
outDir: string
|
||||
banner?: string
|
||||
minify?: boolean | 'esbuild'
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Vite configuration for building libraries
|
||||
*/
|
||||
export function createViteConfig({
|
||||
entry,
|
||||
name,
|
||||
fileName,
|
||||
formats,
|
||||
outDir,
|
||||
banner,
|
||||
minify = false
|
||||
}: CreateViteConfigOptions): UserConfig {
|
||||
const rollupOutput: {
|
||||
generatedCode: {
|
||||
constBindings: boolean
|
||||
}
|
||||
banner?: string
|
||||
} = {
|
||||
generatedCode: {
|
||||
constBindings: true
|
||||
}
|
||||
}
|
||||
export function createViteConfig({ entry, name, fileName, formats, outDir, banner, minify = false }: CreateViteConfigOptions): UserConfig {
|
||||
// Vite 8 (Rolldown) always emits const bindings and no longer accepts the
|
||||
// Rollup-only generatedCode.constBindings option
|
||||
const rollupOutput: { banner?: string } = {}
|
||||
|
||||
// Add banner if provided
|
||||
if (banner) {
|
||||
rollupOutput.banner = banner
|
||||
}
|
||||
// Add banner if provided
|
||||
if (banner) {
|
||||
rollupOutput.banner = banner
|
||||
}
|
||||
|
||||
const config: UserConfig = {
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(entry),
|
||||
name: name,
|
||||
fileName: typeof fileName === 'function' ? fileName : () => fileName,
|
||||
formats: formats
|
||||
},
|
||||
outDir: path.resolve(outDir),
|
||||
emptyOutDir: false,
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: rollupOutput
|
||||
},
|
||||
target: 'es2015',
|
||||
minify: minify
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
},
|
||||
esbuild: {
|
||||
target: 'es2015',
|
||||
tsconfigRaw: {
|
||||
compilerOptions: {
|
||||
module: 'ES2020',
|
||||
target: 'ES2015'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const config: UserConfig = {
|
||||
// These are library (JS bundle) builds, not app builds — Vite's default
|
||||
// behavior of copying <root>/public into outDir on every build is not wanted
|
||||
// here (and in @tabler/preview, where public/ is itself generated from this
|
||||
// same outDir, caused unbounded growth across repeated builds).
|
||||
publicDir: false,
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(entry),
|
||||
name: name,
|
||||
fileName: typeof fileName === 'function' ? fileName : () => fileName,
|
||||
formats: formats,
|
||||
},
|
||||
outDir: path.resolve(outDir),
|
||||
emptyOutDir: false,
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: rollupOutput,
|
||||
},
|
||||
target: 'es2015',
|
||||
minify: minify,
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
},
|
||||
esbuild: {
|
||||
target: 'es2015',
|
||||
tsconfigRaw: {
|
||||
compilerOptions: {
|
||||
module: 'ES2020',
|
||||
target: 'ES2015',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return defineConfig(config)
|
||||
return defineConfig(config)
|
||||
}
|
||||
|
||||
|
||||
+6
-20
@@ -9,38 +9,24 @@ import { readFileSync } from 'node:fs'
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
interface PackageJson {
|
||||
version: string
|
||||
[key: string]: unknown
|
||||
version: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const pkg: PackageJson = JSON.parse(
|
||||
readFileSync(path.join(__dirname, '../core', 'package.json'), 'utf8')
|
||||
)
|
||||
const pkg: PackageJson = JSON.parse(readFileSync(path.join(__dirname, '../core', 'package.json'), 'utf8'))
|
||||
|
||||
// Create zip instance and add folder
|
||||
const zip = new AdmZip()
|
||||
zip.addLocalFolder(path.join(__dirname, '../preview/dist'), 'dashboard')
|
||||
|
||||
zip.addLocalFile(
|
||||
path.join(__dirname, '../preview/static', 'og.png'),
|
||||
'.',
|
||||
'preview.png'
|
||||
)
|
||||
zip.addLocalFile(path.join(__dirname, '../shared/static', 'og.png'), '.', 'preview.png')
|
||||
|
||||
zip.addFile(
|
||||
'documentation.url',
|
||||
Buffer.from('[InternetShortcut]\nURL = https://tabler.io/docs')
|
||||
)
|
||||
zip.addFile('documentation.url', Buffer.from('[InternetShortcut]\nURL = https://tabler.io/docs'))
|
||||
|
||||
// Folder to zip and output path
|
||||
const outputZipPath = path.join(
|
||||
__dirname,
|
||||
'../packages-zip',
|
||||
`tabler-${pkg.version}.zip`
|
||||
)
|
||||
const outputZipPath = path.join(__dirname, '../packages-zip', `tabler-${pkg.version}.zip`)
|
||||
|
||||
// Write the zip file
|
||||
zip.writeZip(outputZipPath)
|
||||
|
||||
console.log(`Zipped folder to ${outputZipPath}`)
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Updated the `active-users-2` card chart include to use `chart-id="active-users-2"` with `height="11"` for a more compact layout.
|
||||
Updated the `active-users-2` card chart to use `chart-id="active-users-2"` with `height="11"` for a more compact layout.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
---
|
||||
|
||||
Added `.text-gray-*` utility classes (`.text-gray-50` through `.text-gray-950`) alongside the existing `.bg-gray-*` and `.text-gray-*-fg` utilities.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/docs": minor
|
||||
---
|
||||
|
||||
Added `Astro` icons library documentation page for the new `@tabler/icons-astro` package.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tabler/preview": minor
|
||||
"@tabler/docs": minor
|
||||
---
|
||||
|
||||
Updated the `preview` and `docs` packages to build with Astro instead of Eleventy, keeping the generated pages identical and moving shared components to `shared/astro/`.
|
||||
@@ -2,5 +2,4 @@
|
||||
"@tabler/preview": minor
|
||||
---
|
||||
|
||||
Added new `card-gradients.html` page showcasing various gradient card styles and components.
|
||||
|
||||
Added new `card-gradients` page showcasing various gradient card styles and components.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
"@tabler/preview": minor
|
||||
---
|
||||
|
||||
Added color palette to signing component.
|
||||
Added a color palette to the signature pad component for selecting the pen color.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Replaced hardcoded `rem` and `px` spacing, sizing, and offset values with SCSS variables across icon, avatar, badge, button, card, alert, empty state, ribbon, status, dropdown, form, nav, pagination, progress, steps, and tag components for easier theme customization.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Updated the contributing guide, README and Docker setup for the Astro-based development toolchain.
|
||||
@@ -2,4 +2,4 @@
|
||||
"@tabler/preview": minor
|
||||
---
|
||||
|
||||
Added new `dashboard-crm.html` page with reusable CRM cards.
|
||||
Added new `dashboard-crm` page with reusable CRM cards.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Updated dev servers to run on fixed ports: `3000` for preview and `3010` for docs.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Fixed documentation formatting issues: heading hierarchy, missing image alt texts and broken list structure across docs pages.
|
||||
@@ -2,4 +2,4 @@
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Added `sitemap.xml` and `robots.txt` for the docs site, with a `sitemap` Eleventy collection and fixed docs layout title rendering outside production.
|
||||
Added `sitemap.xml` and `robots.txt` endpoints for the docs site and fixed docs layout title rendering outside production.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Update Tabler Icons to v3.35.0
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Updated preview and docs Astro code to use shared `date-format`, `string-format`, `pseudo-random`, and `include-args` helpers instead of duplicated inline Liquid filter ports.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `.card-header` background-color to respect the `--tblr-card-cap-bg` CSS variable instead of being silently overridden by a `background: transparent` shorthand.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `.card-tabs .nav-tabs` sharing `z-index` with `.dropdown-menu`, which made dropdowns opened over card tabs render behind them.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed barely visible checkbox and radio borders in dark theme by using `$input-border-color` instead of translucent border color for `.form-check-input`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed disabled form control background in dark theme by introducing `--tblr-bg-forms-disabled` (gray-800) so disabled inputs are visually distinct from enabled ones.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Fixed the `Plugins` card on the docs homepage linking to a 404 `/plugins` route instead of `/ui/plugins`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `.input-icon` inline-start padding for `.form-select` by updating the selector from `:not(:last-child)` to `:not(:first-child)`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `.form-select` keeping its default box-shadow inside `.input-group` by adding it to the existing `.form-control`/`.btn` box-shadow reset.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `--tblr-gray-*-fg` token generation to map directly to `--tblr-gray-*` instead of contrast-based fallbacks that could resolve to white.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed transform animations (`.icon-pulse`, `.icon-tada`, `.icon-rotate`) not working on webfont icons by adding `display: inline-block` and `transform-origin: center` to `.icon`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `sm` and `lg` size consistency for form controls, buttons, and input groups by aligning `line-height` and computed height variables to resolve [#2456](https://github.com/tabler/tabler/issues/2456).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `.steps` horizontal overflow on small screens by enabling scrollable overflow below the `sm` breakpoint.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
"@tabler/docs": patch
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Unified local and CI quality gates: added root-level `lint-prettier`/`format-prettier` scripts covering `core`, `preview`, and `shared`, wired `check` to run lint and type-check together, and updated the lint workflow to run both. Reformatted the covered files with Prettier.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/docs": minor
|
||||
---
|
||||
|
||||
Added framework integration guides for Laravel, React, Next.js, Vue, Angular, Nuxt, Symfony, Django, Rails, SvelteKit, and Astro.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Updated Tabler Icons to v3.45.0 and restored the `import-icons` and `import-illustrations` scripts for refreshing icon and illustration data after the Astro migration.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
Use `.nvmrc` for Node version in CI workflows
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Updated icons to v3.34.1 with 75 new icons.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Updated root color tokens to use CSS `light-dark()` so paired light and dark values live in one `:root` declaration instead of separate dark-mode overrides.
|
||||
@@ -1,8 +1,5 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/preview": minor
|
||||
"@tabler/docs": minor
|
||||
---
|
||||
|
||||
Migrated build system from Rollup to Vite across all packages. Replaced `rollup.config.mjs` with `vite.config.mjs` and updated build scripts to use `vite build` instead of `rollup`. Build outputs remain identical (UMD and ESM formats) with no breaking changes for end users.
|
||||
|
||||
Migrated the core build system from Rollup to Vite. Replaced `rollup.config.mjs` with `vite.config.mjs` and updated build scripts to use `vite build` instead of `rollup`. Build outputs remain identical (UMD and ESM formats) with no breaking changes for end users.
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
"@tabler/preview": minor
|
||||
---
|
||||
|
||||
Refactored navbar-side component by consolidating separate include files (apps, language, notifications, theme, user) into a single `navbar-side.html` file for better maintainability.
|
||||
|
||||
Refactored the navbar-side component and reorganized its apps, language, notifications, theme and user sections for better maintainability.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Fixed Vercel deployment to serve `error-404.html` as the custom 404 page.
|
||||
@@ -3,4 +3,4 @@
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Added `Progress Step` component documentation and updated `ui/progress-steps.html` formatting for cleaner rendered output.
|
||||
Added `Progress Step` component documentation and cleaned up the progress steps preview markup for cleaner rendered output.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tabler/core": major
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Removed `@hotwired/turbo` integration, including `.turbo-progress-bar` styles and the Turbo loader preview demo.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Removed the unused `bootstrapLink` front matter field from `DocsLayout`, `DocsMdxLayout`, and all docs pages.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@tabler/core': major
|
||||
---
|
||||
|
||||
Remove unused SCSS `!default` variables (deprecated Bootstrap leftovers such as `$variable-prefix`, `$alert-*-scale`, accordion icon/button variables, `$popover-arrow-*`, numbered `$font-size-*` / `$line-height-*` scales and others). Configuring any of the removed variables via `@use ... with (...)` now raises a Sass error — they had no effect on the compiled CSS before. The color shade ladders (`$blue-100`…`$cyan-900`) and per-color maps (`$blues`…`$cyans`) are kept as configurable API. Compiled CSS output is unchanged.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed the `caret()` mixin by restoring `$caret-width` to `0.36em`, which had shrunk to `0.3em` in a previous SCSS refactor.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
---
|
||||
|
||||
Updated core SCSS to the Sass module system with `@use`/`@forward`, including `_extends.scss` for cross-module `@extend` rules and a parameterized `utilities-api` mixin for marketing utilities.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Updated Tabler Illustrations to v1.16.0 with 20 new illustrations, including `teamwork`, `onboarding`, `hacker`, `detective` and seasonal artwork.
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: astro-components
|
||||
description: Builds new Astro components, pages, and layouts for the Tabler monorepo (preview and docs packages, shared library). Use when creating or extending demo pages, UI components, cards, layouts, or docs examples. Knows the project's component conventions, script/modal architecture, and shared building blocks.
|
||||
---
|
||||
|
||||
You are a specialist in building Astro components and pages for Tabler.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `shared/` is the single component library used by both site packages:
|
||||
`ui/`, `components/` (cards + parts), `layouts/`, `lib/`, plus `data/` and `static/`.
|
||||
- `preview/` (`@tabler/preview`) — demo site; pages in `preview/pages/*.astro`
|
||||
(`srcDir: '.'`, `build.format: 'file'` → `foo.astro` → `/foo.html`).
|
||||
- `docs/` (`@tabler/docs`) — documentation; pages in `docs/pages/**/*.mdx`
|
||||
(`build.format: 'directory'`; docs writing rules: `.agents/rules/docs.mdc`).
|
||||
Docs-only components live in `docs/components/` (`@components` alias).
|
||||
- Aliases (vite + tsconfig, per package): `@shared` → `../shared`, `@ui` → `../shared/ui`,
|
||||
`@data` → `../shared/data`, `@components` → package components
|
||||
(`preview`: `shared/components`, `docs`: `docs/components`),
|
||||
`@pages` → the package's pages dir.
|
||||
Inside `shared`, use relative imports; from shared code to
|
||||
package-specific files use the package aliases. Any bare npm import used in
|
||||
`shared` must be declared in `shared/package.json`.
|
||||
|
||||
## Component conventions
|
||||
|
||||
- Components are `.astro` only; helper logic goes to `shared/lib/*.ts`.
|
||||
- TypeScript frontmatter: `interface Props` + destructuring with defaults.
|
||||
- Props are camelCase; the `class` prop stays `class`
|
||||
(destructure as `class: className`).
|
||||
- Inline `<style>`/`<script>` in markup ALWAYS with `is:inline` (otherwise
|
||||
Astro adds `data-astro-cid-*` everywhere or bundles the script).
|
||||
No scoped styles, no CSS imports in frontmatter.
|
||||
- Data that may contain markup or entities: render with `set:html`.
|
||||
Entities in attribute strings: pass as expressions (`title={"...…"}`),
|
||||
because JSX decodes entities in string literals.
|
||||
- Boolean attributes are inconsistent in Astro: `selected={true}` renders a
|
||||
bare attribute, but some (e.g. `multiple`) render `="true"` — use
|
||||
`multiple ? '' : undefined` when a bare attribute is required. Check
|
||||
the built output when unsure.
|
||||
- Follow the HTML/CSS class guidelines in `.agents/rules/main.mdc`
|
||||
(Bootstrap 5 + Tabler classes, buttons, badges, accessibility).
|
||||
|
||||
## Building blocks — use, do not duplicate
|
||||
|
||||
Read the `Props` interface of a component before using it; extend components
|
||||
additively instead of creating parallel variants.
|
||||
|
||||
- `@ui/Icon.astro` — `<Icon name="eye" size="sm" class="..." />`.
|
||||
- `@ui/Button.astro` — full button API (color, outline, ghost,
|
||||
size, icon, iconOnly, dismiss, loading, modalId, ...).
|
||||
- `@ui/Chart.astro` + `@shared/lib/chart-script.ts` — ApexCharts
|
||||
engine driven by `@data/charts.json`. Never hand-write chart configs; add
|
||||
fields to `chart-script.ts` if a new chart needs them.
|
||||
- `@ui/*` — Avatar, Badge, Progress, Pagination, Flag,
|
||||
Dropdown, Table, Steps, Nav, Spinner, and ~45 more.
|
||||
- `@shared/components/cards/*` — dashboard/demo cards.
|
||||
- Layouts in `@shared/layouts/`: `BaseLayout` (head, assets, theme settings),
|
||||
`DefaultLayout` (navbar/sidebar page chrome; props for navbar variants,
|
||||
wrapper/container classes, page header), `SingleLayout` (auth pages),
|
||||
`ErrorLayout`, `SettingsLayout`, `MarketingLayout`, `PayLayout`,
|
||||
`DocsLayout`/`DocsMdxLayout` (docs).
|
||||
- Docs examples: `@components/Example.astro` (slot or `html` prop;
|
||||
props: hideCode, code, centered, vertical, column, raw, bg, height, codeOnly).
|
||||
|
||||
## Page scripts and modals
|
||||
|
||||
- Capture markup with `CaptureScript` / `CaptureModal` (HTML in the slot, not
|
||||
template strings). They register via `addPageScript()` / `addPageModal()`
|
||||
(`@shared/lib/page-scripts.ts` / `page-modals.ts`).
|
||||
Wrap at the call site, e.g. `<CaptureModal><Modal …>…</Modal></CaptureModal>`.
|
||||
Registration MUST be synchronous in the component frontmatter (before the
|
||||
first `await`) — Astro renders siblings concurrently, and a registration
|
||||
after `await Astro.slots.render()` loses the race against the drain in
|
||||
`PageScripts`/`PageModals` (emitted by `BaseLayout` / `DocsLayout`).
|
||||
- Third-party page libraries: list names in the layout's `pageLibs` prop —
|
||||
resolved via `@tabler/core/libs.json` (a full `http` URL in there is emitted
|
||||
verbatim; `head: true` libs go into `<head>`).
|
||||
|
||||
## Data
|
||||
|
||||
- JSON data: import from `@data/*.json` (single source of truth in
|
||||
`shared/data/`). Site config: `site` from `@shared/lib/site.ts` (but pages
|
||||
that feed theme colors to charts import `@data/site.json` directly — the two
|
||||
have different `themeColors` semantics).
|
||||
- Deterministic pseudo-randomness for demo content (photos, dates): derive from
|
||||
an index prop, never from `Math.random()`.
|
||||
|
||||
## Workflow for a new preview page
|
||||
|
||||
1. Create `preview/pages/<name>.astro`; pick the layout (usually
|
||||
`DefaultLayout`) and pass `title`, menu/page-header props, `pageLibs`.
|
||||
2. Compose from existing components; add new ones to `shared/components/`.
|
||||
3. Build and verify: `pnpm --filter @tabler/preview build` (output in
|
||||
`preview/dist/<name>.html`), or `astro dev` for live preview.
|
||||
4. If the page should appear in navigation, update `@data` menu sources.
|
||||
|
||||
Final report: list created/modified files, prop signatures of new components,
|
||||
and any assumptions or open questions.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.agents/skills
|
||||
@@ -0,0 +1 @@
|
||||
Generate PR title and description, use ALL changes in current branch vs dev branch. Use simple english, title should be no longer than 70 chars. Generate title and description in markdown. If issue numer are in branch name add reference in PR to this issue on Github.
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
description: Git Branch Naming Rules
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
## Branch naming
|
||||
|
||||
- Use lowercase branch names.
|
||||
- Use a type prefix and a short description in kebab-case.
|
||||
- Format: `<type>/<short-description>` or `<type>/<issue-id>-<short-description>`
|
||||
- Use `gh-123` as the issue id format (avoid `#` in branch names).
|
||||
|
||||
### Allowed types
|
||||
|
||||
- `feat` - new features
|
||||
- `fix` - bug fixes
|
||||
- `docs` - documentation changes
|
||||
- `chore` - maintenance / tooling
|
||||
- `refactor` - code refactoring (no behavior change)
|
||||
- `test` - tests only
|
||||
- `build` - build system changes
|
||||
- `ci` - CI changes
|
||||
- `perf` - performance improvements
|
||||
- `style` - formatting / lint-only changes
|
||||
- `revert` - reverting prior changes
|
||||
|
||||
### Examples
|
||||
|
||||
- `feat/gh-123-add-stepper-component`
|
||||
- `fix/markdown-table-overflow`
|
||||
- `docs/gh-45-update-contributing`
|
||||
- `chore/update-pnpm-lock`
|
||||
|
||||
### Notes
|
||||
|
||||
- Branch off `dev` by default (unless maintainers request otherwise).
|
||||
- Avoid spaces, uppercase letters, and special characters other than `/` and `-`.
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
description: Cursor Rules for Tabler Changesets
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
|
||||
### File Structure
|
||||
|
||||
- Each change must be in a separate changeset file in `.changeset/` directory
|
||||
- Use descriptive kebab-case filenames (e.g., `progress-sizes.md`, `button-ghost.md`)
|
||||
- Follow the standard changeset format with frontmatter and description
|
||||
|
||||
### Change Description Format
|
||||
|
||||
- **One sentence per changeset** - keep descriptions concise and focused
|
||||
- Use **backticks for code elements**: classes (`.btn-ghost`), properties (`stroke-width`), values (`1.5`), icons (`arrow-up`)
|
||||
- Start with action verbs: "Added", "Updated", "Fixed", "Removed"
|
||||
- Be specific about what was changed
|
||||
|
||||
### Version Bump Guidelines
|
||||
|
||||
- **Major**: Breaking changes, complete rewrites
|
||||
- **Minor**: New features, new components, new pages, significant enhancements
|
||||
- **Patch**: Bug fixes, small improvements, style updates, accessibility fixes
|
||||
|
||||
### Package Selection
|
||||
|
||||
- `"@tabler/core"`: Changes to SCSS, core functionality, CSS classes
|
||||
- `"@tabler/preview"`: New pages, demo updates, preview-specific changes
|
||||
- `"@tabler/docs"`: Documentation updates
|
||||
- Use multiple packages when change affects multiple areas
|
||||
|
||||
### Examples
|
||||
|
||||
#### New Feature (Minor)
|
||||
|
||||
```md
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/preview": minor
|
||||
---
|
||||
|
||||
Added Progress Steps component for step-by-step navigation indicators.
|
||||
```
|
||||
|
||||
#### Bug Fix (Patch)
|
||||
|
||||
```md
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Updated `stroke-width` for `.icon-sm` from `1` to `1.5` for better visibility.
|
||||
```
|
||||
|
||||
#### New Page (Minor)
|
||||
|
||||
```md
|
||||
---
|
||||
"@tabler/preview": minor
|
||||
---
|
||||
|
||||
Added new onboarding page with progress indicator and navigation layout.
|
||||
```
|
||||
|
||||
#### Style Enhancement (Patch)
|
||||
|
||||
```md
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Added smooth transitions for progress bar width and background color changes.
|
||||
```
|
||||
|
||||
### Code Formatting Rules
|
||||
|
||||
- Class names: `.btn-ghost`, `.progress-lg`, `.icon-sm`
|
||||
- CSS properties: `stroke-width`, `background-color`, `width`
|
||||
- Values: `1.5`, `transparent`, `100%`
|
||||
- Icon names: `arrow-up`, `arrow-down`, `trending-up`
|
||||
- HTML attributes: `aria-label`, `role`, `data-*`
|
||||
- JavaScript functions: `addEventListener()`, `querySelector()`
|
||||
|
||||
### Common Patterns
|
||||
|
||||
- **Component additions**: "Added [ComponentName] component for [purpose]"
|
||||
- **Size variants**: "Added [size] size variant for [component] (`.class-size`)"
|
||||
- **Style fixes**: "Fixed [issue] in [component/element]"
|
||||
- **Icon updates**: "Updated [component] to use `new-icon` instead of `old-icon`"
|
||||
- **Accessibility**: "Improved accessibility by [specific change]"
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
Use English for commit messages following conventional commit format when possible:
|
||||
|
||||
- `feat: add progress steps component`
|
||||
- `fix: update icon stroke width for better visibility`
|
||||
- `style: add smooth transitions to progress bars`
|
||||
@@ -1,341 +0,0 @@
|
||||
---
|
||||
description: Markdown documentation writing standards for docs pages
|
||||
globs: docs/content/**/*.md
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Markdown Documentation Rules
|
||||
|
||||
## Scope
|
||||
|
||||
- Apply these rules when creating or editing Markdown documentation files.
|
||||
- Write technical documentation in English.
|
||||
- Use simple English: short sentences, common words, and direct instructions.
|
||||
|
||||
## Frontmatter
|
||||
|
||||
- Use static YAML frontmatter only (no Liquid in frontmatter).
|
||||
- Include `title`, `summary`, and `description` for docs pages in `docs/content/**`.
|
||||
- For component docs, use singular naming in frontmatter and filename:
|
||||
`title: Card`, `summary` and `description` written with a singular component form, and filename `card.md` (not `cards.md`).
|
||||
- Add optional frontmatter `related` entries (instead of a `Related` content section).
|
||||
- In `related`, always use absolute docs paths in `url` (without `./`), for example:
|
||||
`ui/components/breadcrumb`.
|
||||
- Use this structure:
|
||||
```yaml
|
||||
related:
|
||||
- name: Pagination
|
||||
url: ui/components/pagination
|
||||
- name: Dropdowns
|
||||
url: ui/components/dropdowns
|
||||
```
|
||||
- If documentation describes a component defined in SCSS, add `source-scss` with a relative path
|
||||
inside `core/scss/` (for example: `source-scss: ui/buttons.scss`).
|
||||
- If a component extends a Bootstrap component, add `bootstrap-url: components/<name>` in frontmatter
|
||||
(for example: `bootstrap-url: components/buttons`).
|
||||
- Add versioning notes for API and class lifecycle changes using fields like
|
||||
`added-in: 1.4.0` and `deprecated-in: 1.2.0`.
|
||||
- If documentation introduces a new component, add a changeset with a `minor` version bump.
|
||||
- Components should include an optional `classnames` section in frontmatter.
|
||||
- Add only groups that exist for a given component. Do not include empty or irrelevant groups.
|
||||
- `classnames` entries must match classes that exist in the relevant SCSS source files.
|
||||
- Do not document classes that are not defined in SCSS, deprecated, or internal-only.
|
||||
- You can add `status` to class entries: `stable`, `deprecated`, or `experimental`.
|
||||
- If `status` is not provided, treat the class as `stable` by default.
|
||||
- Allowed `classnames` groups and meanings:
|
||||
- `component`: Main component class.
|
||||
- `modifier`: Modifies behavior of component or part.
|
||||
- `part`: Part of a component.
|
||||
- `style`: Visual style of component or part.
|
||||
- `behavior`: Behavioural style of component or part.
|
||||
- `color`: Color of component or part.
|
||||
- `size`: Size of component or part.
|
||||
- `placement`: Placement of component or part.
|
||||
- `direction`: Direction of component or part.
|
||||
- `variable`: CSS variable.
|
||||
- `variant`: Use like `variant:utility` to apply a utility conditionally.
|
||||
- Use this structure:
|
||||
|
||||
```yaml
|
||||
classnames:
|
||||
component:
|
||||
- class: btn
|
||||
desc: Main button class
|
||||
part:
|
||||
- class: btn-group
|
||||
desc: Group container for buttons
|
||||
modifier:
|
||||
- class: btn-block
|
||||
desc: Full width button
|
||||
status: stable
|
||||
style:
|
||||
- class: btn-outline
|
||||
desc: Outline style button
|
||||
behavior:
|
||||
- class: btn-disabled
|
||||
desc: Disabled visual state
|
||||
status: deprecated
|
||||
color:
|
||||
- class: btn-primary
|
||||
desc: Primary color button
|
||||
size:
|
||||
- class: btn-sm
|
||||
desc: Small size button
|
||||
placement:
|
||||
- class: dropdown-menu-end
|
||||
desc: Align dropdown menu to end
|
||||
direction:
|
||||
- class: carousel-vertical
|
||||
desc: Vertical carousel direction
|
||||
variable:
|
||||
- class: --tblr-btn-padding-x
|
||||
desc: Horizontal button padding variable
|
||||
variant:
|
||||
- class: hover:bg-primary
|
||||
desc: Apply background color on hover
|
||||
status: experimental
|
||||
```
|
||||
|
||||
|
||||
## Structure and readability
|
||||
|
||||
- Start with a short purpose statement, then explain usage and variants.
|
||||
- Keep heading hierarchy consistent (`##` then `###`), without skipping levels.
|
||||
- Keep core documentation sections as `H2` headings in this order:
|
||||
`Overview`, `Installation` (optional), `Variants` (for components that support variants or `Usage` for components that don't support variants), `Accessibility`,
|
||||
`Examples` (optional), `SCSS variables` (optional), `Migration notes` (optional).
|
||||
- In `Overview`, add one visual component preview without code (`hide-code=true`).
|
||||
- Use descriptive section headings that are stable for linking.
|
||||
- Vary intro phrasing across pages; avoid repeating the same sentence pattern.
|
||||
|
||||
## Markdown style
|
||||
|
||||
- Follow `.markdownlint.json` conventions (ATX headings, `-` lists, fenced code blocks).
|
||||
- Keep line length within 120 characters where practical.
|
||||
- Keep examples aligned with current API/classes and practical usage.
|
||||
- Use active voice and direct verbs (`Use`, `Add`, `Set`).
|
||||
- Keep one main idea per sentence.
|
||||
- Do not add meta authoring notes in docs text (for example: "this snippet is copy-paste ready").
|
||||
|
||||
## Terminology and section naming
|
||||
|
||||
- Use consistent terminology across pages; one term per concept.
|
||||
- Use clear and consistent section names across component docs.
|
||||
- Prefer shared section labels such as `Overview`, `Accessibility`, `Examples` (optional), and `Migration notes`.
|
||||
- You can add an `SCSS variables` section for components with documented SCSS tokens/variables.
|
||||
|
||||
### Recommended section order
|
||||
|
||||
```md
|
||||
## Overview
|
||||
## Installation (optional)
|
||||
## Variants or Usage/API
|
||||
## Accessibility
|
||||
## Examples (optional)
|
||||
## SCSS variables (optional)
|
||||
## Migration notes (optional)
|
||||
```
|
||||
|
||||
## Project-specific docs patterns
|
||||
|
||||
- In docs examples, prefer `{% capture html %}...{% endcapture %}` and, when needed, add `{% capture code %}...{% endcapture %}` for code snippets.
|
||||
- Use `{% include "docs/example.html" ... %}` to render visual component previews in any relevant section
|
||||
(`Overview`, `Variants`, `Usage/API`, or `Examples`).
|
||||
- Before each `{% include "docs/example.html" ... %}`, add 1-2 short sentences that describe
|
||||
the component state or behavior shown in the preview.
|
||||
- For examples with explicit source snippets, use `{% include "docs/example.html" html=html code=code %}`.
|
||||
- For visual-only examples, use `{% include "docs/example.html" html=html hide-code=true %}`.
|
||||
- If an example shows multiple elements as a list, use `separated` to display items with clean spacing:
|
||||
`{% include "docs/example.html" html=html separated %}`.
|
||||
- Add `centered` when the example should be centered (for example, a list of buttons):
|
||||
`{% include "docs/example.html" html=html separated centered %}`.
|
||||
- If elements are wide, you can stack them vertically with `vertical` (for example):
|
||||
`{% include "docs/example.html" html=html centered vertical %}`.
|
||||
- Use `raw` when you need to render the example without extra preview wrappers
|
||||
(no padding, no centering), for example full page layout previews:
|
||||
`{% include "docs/example.html" html=html raw %}`.
|
||||
- Use `column` to render content in a narrow column (about 25rem), useful for components
|
||||
commonly presented in columns (for example stat cards):
|
||||
`{% include "docs/example.html" html=html column %}`.
|
||||
- For `SCSS variables` sections, use:
|
||||
`{% scss-docs "alert-variables" "ui/_alerts.scss" %}`.
|
||||
- This renders the fragment between `// scss-docs-start alert-variables` and `// scss-docs-end`
|
||||
from the given SCSS file (path relative to `core/scss/`).
|
||||
- If an SCSS file contains a `scss-docs-start` block, it should be included in documentation
|
||||
via a matching `{% scss-docs %}` entry.
|
||||
- Use `hide-code=true` only when source markup is not useful to the reader.
|
||||
- If an example needs a different background, set it with the include attribute `bg="surface-primary"`.
|
||||
- Treat `html` as the visual preview layer and `code` as the clean source layer shown to users.
|
||||
- Keep `code` concise and practical: include only the essential markup needed to explain usage.
|
||||
- Do not duplicate visual wrapper-only structure in `code` when it exists only to style the preview.
|
||||
- Do not promote deprecated patterns; when needed, mention the replacement.
|
||||
- Add accessibility attributes (for example `aria-label`) in interactive examples.
|
||||
|
||||
## Publishing checklist
|
||||
|
||||
- Verify that examples render correctly.
|
||||
- Verify that internal and external links work.
|
||||
- Verify heading hierarchy and section naming consistency.
|
||||
- Verify code snippets are accurate and minimal for the documented use case.
|
||||
- Validate documented classes against SCSS files before publishing.
|
||||
- Ensure every class in `classnames` maps to a real SCSS class or variable.
|
||||
- Ensure every class listed in `classnames` appears at least once in page content or examples.
|
||||
- For interactive components, add a short accessibility mini-check (`aria-label`, keyboard focus, semantic markup).
|
||||
|
||||
## Documentation structure examples
|
||||
|
||||
### Minimal component page structure
|
||||
|
||||
```md
|
||||
---
|
||||
title: Component name
|
||||
summary: Short one-sentence summary of what this component does.
|
||||
description: Practical description of when and why to use this component.
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
One short paragraph explaining purpose and primary use case.
|
||||
|
||||
Show the component in its default state so users can recognize it immediately.
|
||||
Keep this first example focused on appearance and context.
|
||||
|
||||
{% capture html -%}
|
||||
<button class="btn btn-primary">Primary button</button>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html hide-code=true %}
|
||||
|
||||
## Variants
|
||||
|
||||
### Outline
|
||||
|
||||
The outline variant presents a lighter visual emphasis than a filled primary action.
|
||||
Use it for secondary actions in dense interfaces.
|
||||
|
||||
{% capture html -%}
|
||||
<button class="btn">Secondary</button>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html %}
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Use clear labels for interactive controls.
|
||||
- Add `aria-label` for icon-only buttons.
|
||||
|
||||
## Examples (optional, visual usage)
|
||||
|
||||
### Default
|
||||
|
||||
The default variant represents the primary action style.
|
||||
Use it as a baseline before introducing additional variants.
|
||||
|
||||
{% capture html -%}
|
||||
<button class="btn btn-primary">Save</button>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html %}
|
||||
```
|
||||
|
||||
### Visual preview patterns (usable in any section)
|
||||
|
||||
```md
|
||||
## Variants
|
||||
|
||||
### Default
|
||||
|
||||
The default variant represents the primary action style.
|
||||
Use it as a baseline before introducing additional variants.
|
||||
|
||||
{% capture html -%}
|
||||
<button class="btn btn-primary">Primary button</button>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html %}
|
||||
|
||||
### With icon
|
||||
|
||||
This variant combines a leading icon with a text label.
|
||||
Use it for actions that benefit from an additional visual cue.
|
||||
|
||||
{% capture html -%}
|
||||
<button class="btn btn-primary">
|
||||
{% include "ui/icon.html" icon="plus" %}
|
||||
Add item
|
||||
</button>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html %}
|
||||
|
||||
### Custom background
|
||||
|
||||
This variant presents the component on a dark background surface.
|
||||
Use it to verify contrast and visual hierarchy.
|
||||
|
||||
{% capture html -%}
|
||||
<button class="btn btn-light">Light button on dark preview</button>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html bg="dark" %}
|
||||
```
|
||||
|
||||
```md
|
||||
## Usage
|
||||
|
||||
Use this pattern in any section when source markup should be shown together with the visual output.
|
||||
Use `code` for clean implementation markup without preview-only wrappers.
|
||||
|
||||
### Card with footer actions
|
||||
|
||||
This example presents a card layout with footer actions in a realistic container.
|
||||
Show `html` for rendered context and `code` for implementation markup.
|
||||
|
||||
{% capture html -%}
|
||||
<div class="p-4 border rounded bg-light">
|
||||
<div class="card">
|
||||
<div class="card-body">Content</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endcapture %}
|
||||
|
||||
{% capture code -%}
|
||||
<div class="card">
|
||||
<div class="card-body">Content</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html code=code %}
|
||||
```
|
||||
|
||||
```md
|
||||
## Accessibility
|
||||
|
||||
Use this pattern in any section only for visual comparison cases where raw markup is not useful,
|
||||
for example screenshots, behavior notes, or before/after UI states.
|
||||
|
||||
This pattern highlights visual state communication without extra source details.
|
||||
Use it for quick comparisons such as status, before/after states, or behavior notes.
|
||||
|
||||
{% capture html -%}
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-green"></span>
|
||||
<span>Active status</span>
|
||||
</div>
|
||||
{% endcapture %}
|
||||
{% include "docs/example.html" html=html hide-code=true %}
|
||||
|
||||
### Do
|
||||
|
||||
- Explain what changed and why it matters.
|
||||
- Link to a related practical example if available.
|
||||
|
||||
### Don't
|
||||
|
||||
- Do not replace all practical examples with image-only examples.
|
||||
- Do not omit practical markup examples for core use cases.
|
||||
```
|
||||
|
||||
- Do not move core documentation content (for example API details, states, accessibility rules,
|
||||
migration notes) into `Examples`.
|
||||
- Keep core sections as normal documentation; `Examples` is an optional showcase section,
|
||||
while visual preview blocks can be used across multiple sections.
|
||||
@@ -1,171 +0,0 @@
|
||||
---
|
||||
description: Tabler Project HTML Elements Guidelines
|
||||
globs: ["**/*.html", "**/*.liquid", "**/*.md"]
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
## HTML Elements Guidelines
|
||||
|
||||
### 1. Icons
|
||||
|
||||
When you need to use an icon, always use the Tabler icon include syntax:
|
||||
|
||||
```html
|
||||
{% include "ui/icon.html" icon="ICON_NAME" %}
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `{% include "ui/icon.html" icon="home" %}`
|
||||
- `{% include "ui/icon.html" icon="building-community" %}`
|
||||
- `{% include "ui/icon.html" icon="map-pin" %}`
|
||||
|
||||
### 2. Page Links
|
||||
|
||||
When linking to other pages, always use the relative page syntax:
|
||||
|
||||
```html
|
||||
href="{{ page | relative }}/url.html"
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `href="{{ page | relative }}/job-post.html"`
|
||||
- `href="{{ page | relative }}/job-listing.html"`
|
||||
- `href="{{ page | relative }}/marketing/index.html"`
|
||||
|
||||
### 3. Static Generation
|
||||
|
||||
All pages are statically generated to HTML using Eleventy (11ty). Keep this in mind when:
|
||||
|
||||
- Writing frontmatter (must be static YAML, no Liquid templating)
|
||||
- Creating dynamic content (use Liquid templating in the body, not frontmatter)
|
||||
- Linking between pages (use relative paths)
|
||||
|
||||
### 4. Additional Guidelines
|
||||
|
||||
#### Frontmatter Rules
|
||||
|
||||
- Frontmatter must be static YAML
|
||||
- Cannot use Liquid templating in frontmatter
|
||||
- Use static values for title, permalink, etc.
|
||||
|
||||
#### Liquid Templating
|
||||
|
||||
- Use Liquid templating only in the HTML body
|
||||
- Access data using `{{ variable }}` syntax
|
||||
- Use `{% for %}` loops for dynamic content
|
||||
- Use `{% if %}` conditions for conditional rendering
|
||||
|
||||
#### File Structure
|
||||
|
||||
- Pages go in `preview/pages/`
|
||||
- Includes go in `shared/includes/`
|
||||
- Data files go in `shared/data/`
|
||||
- Documentation goes in `docs/content/`
|
||||
|
||||
#### CSS Classes
|
||||
|
||||
- Use Bootstrap 5 classes
|
||||
- Use Tabler's custom CSS classes
|
||||
- Follow the pattern: `--#{$prefix}component-property`
|
||||
|
||||
#### Accessibility
|
||||
|
||||
- Include proper ARIA labels
|
||||
- Use semantic HTML elements
|
||||
- Ensure proper heading hierarchy
|
||||
- Add alt text for images
|
||||
|
||||
### 5. Component Usage
|
||||
|
||||
#### Cards
|
||||
|
||||
- Use `card` class for main containers
|
||||
- Use `card-body` for content areas
|
||||
- Use `card-header` for card headers
|
||||
- Use `card-title` for card title
|
||||
|
||||
#### Buttons
|
||||
|
||||
- Use `btn` class for all buttons
|
||||
- Use `btn-primary` for primary actions
|
||||
- Use `btn` for secondary actions, don't use `btn-outline-secondary`
|
||||
- Use `btn-sm` for smaller buttons
|
||||
- Use `w-100` for full-width buttons
|
||||
|
||||
#### Forms
|
||||
|
||||
- Use `form-control` for input fields
|
||||
- Use `form-label` for labels
|
||||
- Use `form-check` for checkboxes/radio buttons
|
||||
- Use `form-select` for dropdowns
|
||||
|
||||
#### Layout
|
||||
|
||||
- Use Bootstrap grid system (`row`, `col-*`)
|
||||
- Use `container-xl` for main containers
|
||||
- Use `page-wrapper` for page structure
|
||||
- Use `page-body` for main content area
|
||||
|
||||
#### Badges
|
||||
|
||||
- Use `badge` class for badges
|
||||
- Don't use `badge-outline` for badges, use `badge` class instead
|
||||
- Don't use `badge-primary` for badges, use `badge` class instead
|
||||
- Don't change the text color of badges
|
||||
|
||||
#### Markdown
|
||||
|
||||
- Use `markdown` class for markdown content
|
||||
- Apply to containers that render markdown content
|
||||
- Example: `<div class="markdown">...</div>`
|
||||
|
||||
#### Rest of the rules
|
||||
|
||||
- Read the rest of the rules in the `docs/content/ui/` folder
|
||||
|
||||
### 6. Data Integration
|
||||
|
||||
#### Using JSON Data
|
||||
|
||||
```liquid
|
||||
{% for item in items %}
|
||||
<div>{{ item.name }}</div>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
#### Conditional Rendering
|
||||
|
||||
```liquid
|
||||
{% if condition %}
|
||||
<div>Content</div>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
#### Including Components
|
||||
|
||||
```liquid
|
||||
{% include "ui/button.html" color="primary" text="Click me" %}
|
||||
```
|
||||
|
||||
### 7. Best Practices
|
||||
|
||||
#### Performance
|
||||
|
||||
- Minimize nested loops
|
||||
- Use `limit` filters when iterating large datasets
|
||||
- Optimize images for web use
|
||||
|
||||
#### Code Organization
|
||||
|
||||
- Keep components modular and reusable
|
||||
- Use consistent naming conventions
|
||||
- Comment complex logic
|
||||
- Group related functionality together
|
||||
|
||||
#### Error Handling
|
||||
|
||||
- Always check if data exists before using it
|
||||
- Provide fallback content for missing data
|
||||
- Use `{% if %}` guards for optional content
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
description: Tabler Project Rules
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
- Always write documentation in English (not Polish) for technical content
|
||||
- Use clear, descriptive headings with proper hierarchy (##, ###)
|
||||
- Include practical examples with code snippets
|
||||
- Add explanations for each component's purpose and usage
|
||||
- Use consistent formatting for code blocks and examples
|
||||
|
||||
## CSS/SCSS Guidelines
|
||||
|
||||
- Follow Tabler's CSS custom properties pattern: `--#{$prefix}component-property`
|
||||
- Use semantic class names that describe purpose, not appearance
|
||||
- Maintain consistent spacing and indentation in SCSS files
|
||||
- Group related styles together with clear comments
|
||||
- Use Bootstrap-compatible class naming conventions
|
||||
|
||||
## Component Documentation Structure
|
||||
|
||||
- Start with a brief description of the component's purpose
|
||||
- Show basic usage examples first
|
||||
- Include variations and modifiers
|
||||
- Add accessibility considerations where relevant
|
||||
- Provide code examples that are copy-paste ready
|
||||
|
||||
## File Organization
|
||||
|
||||
- Keep documentation files in `docs/content/ui/components/`
|
||||
- Use consistent naming: lowercase with hyphens
|
||||
- Include frontmatter with title, summary, and description
|
||||
- Link to Bootstrap documentation when relevant
|
||||
|
||||
## Code Examples
|
||||
|
||||
- Use Liquid templating syntax for dynamic examples
|
||||
- Include both HTML and rendered output
|
||||
- Show responsive behavior where applicable
|
||||
- Demonstrate proper accessibility attributes
|
||||
|
||||
## Git Commit Messages
|
||||
|
||||
- Use English for commit messages
|
||||
- Follow conventional commit format when possible
|
||||
- Be descriptive about what was changed and why
|
||||
|
||||
## Project-Specific Conventions
|
||||
|
||||
- Tabler uses Bootstrap 5 as a foundation
|
||||
- Custom components extend Bootstrap functionality
|
||||
- Documentation should be comprehensive but concise
|
||||
- Examples should be practical and immediately usable
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
description: Pull Request Title & Description Rules
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
## Pull request title
|
||||
|
||||
- Write PR titles in **English**.
|
||||
- Start the title with a **capital letter**.
|
||||
- Use **present tense** and keep it concise (ideally <= 72 chars).
|
||||
- Avoid a trailing period.
|
||||
|
||||
### Examples
|
||||
|
||||
- `Improve markdown table overflow handling`
|
||||
- `Clarify contributing branch naming`
|
||||
- `Add onboarding stepper page`
|
||||
|
||||
## Pull request description
|
||||
|
||||
- Write PR descriptions in **English**.
|
||||
- Focus on **why** the change is needed and what user-visible effect it has.
|
||||
- Keep it skimmable: bullets, short paragraphs, clear headings.
|
||||
|
||||
### Recommended template
|
||||
|
||||
```md
|
||||
## Summary
|
||||
- <1–3 bullets describing the change and why>
|
||||
|
||||
## Changes
|
||||
- <key implementation notes, non-obvious decisions>
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- If you changed SCSS or any package behavior, add a **changeset** describing it (one sentence, with backticks for code elements).
|
||||
- If a PR is WIP, mark it as draft and prefix the title with `WIP:` only while it is not ready for review.
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
name: write-docs
|
||||
description: Write or update Tabler documentation pages in simple English using the current docs schema. Use when the user asks to create docs, edit docs, add new feature docs to an existing page, or standardize docs structure across any docs/content category.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Write Tabler Docs
|
||||
|
||||
Write docs directly in files (do not stop at draft mode) and follow the current Tabler documentation schema.
|
||||
|
||||
## 1. Scope and behavior
|
||||
|
||||
- Works for any docs page type under `docs/content/**` (components, utilities, forms, layout, plugins, base, getting started, emails, illustrations, icons, index pages).
|
||||
- Edit existing pages when the user asks to document new functionality in an existing component/page.
|
||||
- Create new pages when needed.
|
||||
- Use simple English in all prose.
|
||||
|
||||
## 2. Required language style (simple English)
|
||||
|
||||
- Use short sentences and common words.
|
||||
- Keep one main idea per sentence when possible.
|
||||
- Prefer direct verbs: `Use`, `Add`, `Set`, `Show`.
|
||||
- Avoid buzzwords and marketing filler.
|
||||
- Keep paragraphs short and easy to scan.
|
||||
|
||||
## 3. Frontmatter rules
|
||||
|
||||
Default frontmatter (required unless user asks otherwise):
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: ...
|
||||
summary: ...
|
||||
description: ...
|
||||
---
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Keep frontmatter static YAML only.
|
||||
- By default, include only `title`, `summary`, and `description`.
|
||||
- Add extended keys (for example `order`, `plugin`, `docs-libs`, `layout`, `redirect`, `related`, `source-scss`, `bootstrap-url`, `classnames`) only when the user explicitly asks for them.
|
||||
|
||||
## 4. Documentation schema to follow
|
||||
|
||||
Use this section order as the default structure:
|
||||
|
||||
1. `## Overview`
|
||||
2. `## Installation` (optional)
|
||||
3. `## Variants` or `## Usage` (choose one based on page type)
|
||||
4. `## Examples` (optional)
|
||||
5. `## Accessibility`
|
||||
6. `## SCSS variables` (optional)
|
||||
7. `## Migration notes` (optional)
|
||||
|
||||
Guidance by page type:
|
||||
|
||||
- Component-like pages: prefer `Variants`.
|
||||
- Utility or workflow pages: prefer `Usage`.
|
||||
- Library/package pages (for example icons, emails): keep `Installation` + `Usage`.
|
||||
- Intro/index pages: may use concise overview sections, but keep hierarchy clear and consistent.
|
||||
|
||||
## 5. Example and snippet pattern
|
||||
|
||||
For visual examples, use project includes:
|
||||
|
||||
- Wrap preview markup with:
|
||||
- `{% capture html -%} ... {%- endcapture %}`
|
||||
- `{% include "docs/example.html" html=html %}`
|
||||
- Add 1-2 short sentences before each preview block to explain what the preview shows.
|
||||
- Use include options when useful: `hide-code`, `separated`, `centered`, `vertical`, `raw`, `column`, `bg`.
|
||||
- For examples with cleaner source markup, also add:
|
||||
- `{% capture code -%} ... {%- endcapture %}`
|
||||
- `{% include "docs/example.html" html=html code=code %}`
|
||||
- For icons inside examples, use:
|
||||
- `{% include "ui/icon.html" icon="icon-name" %}`
|
||||
|
||||
## 6. Workflow for each request
|
||||
|
||||
1. Identify target file(s) in `docs/content/**`.
|
||||
2. Read the target page and 2-3 nearby pages in the same category to match tone and conventions.
|
||||
3. Apply the schema from section 4.
|
||||
4. Write/update the page directly in file(s).
|
||||
5. Keep only required frontmatter by default.
|
||||
6. Ensure prose is in simple English.
|
||||
7. Verify heading hierarchy (`##` then `###`) and snippet validity.
|
||||
|
||||
## 7. Rules while updating existing docs
|
||||
|
||||
- Preserve valid existing content that is still correct.
|
||||
- Add new functionality docs as focused new subsection(s), usually under `Variants`, `Usage`, or `Examples`.
|
||||
- Remove or rewrite only conflicting or outdated text.
|
||||
- Keep naming and terms consistent across the page.
|
||||
|
||||
## 8. Quality checklist before finishing
|
||||
|
||||
- [ ] Uses simple English.
|
||||
- [ ] Frontmatter uses static YAML.
|
||||
- [ ] Default frontmatter contains only `title`, `summary`, `description` (unless user requested extra keys).
|
||||
- [ ] Follows schema and heading hierarchy.
|
||||
- [ ] Examples use `docs/example.html` pattern where applicable.
|
||||
- [ ] Accessibility section exists for interactive UI docs.
|
||||
- [ ] No mention of changeset reminders unless user asks.
|
||||
@@ -1,3 +1,8 @@
|
||||
demo/
|
||||
.git/
|
||||
.github/
|
||||
node_modules/
|
||||
**/node_modules/
|
||||
**/dist/
|
||||
**/.astro/
|
||||
packages-zip/
|
||||
|
||||
@@ -4,3 +4,6 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github actions"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
core:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/**'
|
||||
|
||||
preview:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'preview/**'
|
||||
|
||||
docs:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'docs/**'
|
||||
|
||||
shared:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'shared/**'
|
||||
|
||||
scss:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'core/scss/**'
|
||||
- 'preview/scss/**'
|
||||
- 'docs/scss/**'
|
||||
|
||||
javascript:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'core/js/**'
|
||||
- 'preview/js/**'
|
||||
|
||||
icons:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'shared/data/icons.json'
|
||||
- 'shared/data/icons-info.json'
|
||||
- 'docs/pages/icons/**'
|
||||
|
||||
illustrations:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'shared/static/illustrations/**'
|
||||
- 'shared/data/illustrations.json'
|
||||
- 'docs/pages/illustrations/**'
|
||||
|
||||
dependencies:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'package.json'
|
||||
- '**/package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -8,6 +8,10 @@ on:
|
||||
- '**.png'
|
||||
- '**.webp'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# Only run on Pull Requests within the same repository, and not from forks.
|
||||
@@ -23,4 +27,4 @@ jobs:
|
||||
- name: Compress Images
|
||||
uses: calibreapp/image-actions@main
|
||||
with:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v11
|
||||
with:
|
||||
days-before-issue-stale: 360
|
||||
days-before-issue-close: 14
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Labeler
|
||||
|
||||
on:
|
||||
pull_request_target: null
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply labels based on changed files
|
||||
uses: actions/labeler@v7
|
||||
with:
|
||||
sync-labels: true
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -25,5 +25,8 @@ jobs:
|
||||
- name: Install pnpm dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint Markdown
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Type Check
|
||||
run: pnpm run type-check
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
+5
-1
@@ -39,13 +39,17 @@ dist/
|
||||
packages-zip/
|
||||
.env
|
||||
sri.json
|
||||
preview-astro
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
.tsbuildinfo
|
||||
|
||||
# Astro generated types
|
||||
.astro/
|
||||
|
||||
# Test coverage
|
||||
coverage/
|
||||
__screenshots__/
|
||||
__pycache__/
|
||||
.claude/launch.json
|
||||
.claude/settings.local.json
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./.markdownlint.json",
|
||||
"MD013": false,
|
||||
"MD024": {
|
||||
"siblings_only": true
|
||||
},
|
||||
"MD041": false
|
||||
}
|
||||
+26
-1
@@ -2,4 +2,29 @@ dist
|
||||
.tmp
|
||||
.vscode
|
||||
.cache
|
||||
node_modules
|
||||
node_modules
|
||||
# generated / build outputs
|
||||
.astro
|
||||
coverage
|
||||
demo
|
||||
packages-zip
|
||||
preview/public
|
||||
preview/tmp-assets
|
||||
docs/public
|
||||
|
||||
# prettier-plugin-astro cannot parse HTML comments or <script> tags inside
|
||||
# JSX expressions ({cond && (<Fragment><!-- ... --><script>...)}) — these files
|
||||
# use those constructs on purpose (BEGIN/END markers emitted into demo HTML,
|
||||
# conditional inline scripts). Re-check when the plugin gains support.
|
||||
docs/layouts/DocsLayout.astro
|
||||
shared/components/cards/Card.astro
|
||||
shared/components/layout/PageHeader.astro
|
||||
shared/components/navbar/Navbar.astro
|
||||
shared/ui/Chart.astro
|
||||
shared/ui/ChartSparkline.astro
|
||||
shared/ui/Datepicker.astro
|
||||
shared/ui/InlinePlayer.astro
|
||||
shared/ui/Map.astro
|
||||
shared/ui/MapVector.astro
|
||||
shared/ui/Range.astro
|
||||
shared/ui/Select.astro
|
||||
|
||||
@@ -9,12 +9,19 @@
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-astro"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.scss",
|
||||
"options": {
|
||||
"parser": "scss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.astro",
|
||||
"options": {
|
||||
"parser": "astro"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"]
|
||||
}
|
||||
Vendored
+12
-10
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/.idea/": true
|
||||
},
|
||||
"explorerExclude.backup": {}
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/.idea/": true
|
||||
},
|
||||
"explorerExclude.backup": {},
|
||||
"typescript.tsdk": "preview/node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
# Contributing to Tabler
|
||||
|
||||
Thank you for wanting to contribute! This is a short overview - the full guide lives in the documentation: [How to Contribute](https://docs.tabler.io/ui/getting-started/how-to-contribute/).
|
||||
|
||||
## Quick start
|
||||
|
||||
Requirements: Node.js 22.12+ and [pnpm](https://pnpm.io/).
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
This starts the preview website at [http://localhost:3000](http://localhost:3000) and the documentation at [http://localhost:3010](http://localhost:3010), both with live reload.
|
||||
|
||||
## Where things live
|
||||
|
||||
- `core/` - the framework: SCSS (`core/scss/`) and JavaScript (`core/js/`)
|
||||
- `preview/` - demo website (Astro, pages in `preview/pages/*.astro`)
|
||||
- `docs/` - documentation website (Astro, pages in `docs/pages/**/*.mdx`)
|
||||
- `shared/` - components and layouts shared by preview and docs (`@shared` alias), plus `data/` and `static/`
|
||||
- `shared/data/` - JSON data for demo pages (`@data` alias)
|
||||
|
||||
Do not edit `dist/` folders - they are generated by the build.
|
||||
|
||||
## Before you open a pull request
|
||||
|
||||
1. **Branch off `dev`** and use the branch naming convention: `<type>/<short-description>`, for example `feat/gh-123-add-stepper-component` or `fix/markdown-table-overflow`. Allowed types: `feat`, `fix`, `docs`, `chore`, `refactor`, `test`, `build`, `ci`, `perf`, `style`, `revert`.
|
||||
2. **Build and test**: `pnpm run build` must pass. For docs changes, also run `pnpm run lint`.
|
||||
3. **Add a changeset** if your change affects any package: `pnpm exec changeset`. Use one sentence starting with `Added`, `Updated`, `Fixed` or `Removed`, with backticks for code tokens (for example `` Added `.btn-ghost` variant for buttons. ``).
|
||||
4. **Write the PR in English**: a concise title in present tense (max ~72 chars, no trailing period) and a short description that explains why the change is needed.
|
||||
|
||||
## Code style
|
||||
|
||||
- Use Bootstrap 5 conventions and Tabler's CSS custom properties pattern: `--#{$prefix}component-property`.
|
||||
- Write documentation in simple English: short sentences, common words, direct instructions.
|
||||
- Ensure changes work in all supported browsers (see the [browser support docs](https://docs.tabler.io/ui/getting-started/browser-support/)).
|
||||
|
||||
## Code of conduct
|
||||
|
||||
By participating, you agree to follow our [Code of Conduct](.github/CODE_OF_CONDUCT.md).
|
||||
+12
-14
@@ -1,20 +1,18 @@
|
||||
FROM ruby:3.2-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
ADD _config.yml /app/
|
||||
ADD _config_prod.yml /app/
|
||||
ADD package.json /app/
|
||||
ADD pnpm-lock.yaml /app/
|
||||
ADD gulpfile.js /app/
|
||||
|
||||
RUN apk add --virtual build-dependencies build-base npm
|
||||
RUN apk upgrade
|
||||
RUN npm i -g pnpm
|
||||
RUN pnpm install
|
||||
# pnpm via corepack, pinned by the "packageManager" field in package.json
|
||||
RUN corepack enable
|
||||
|
||||
# website
|
||||
COPY . .
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# preview website
|
||||
EXPOSE 3000
|
||||
# website management (browser auto reload)
|
||||
EXPOSE 3001
|
||||
# run tabler
|
||||
# documentation website
|
||||
EXPOSE 3010
|
||||
|
||||
# run tabler dev servers
|
||||
ENTRYPOINT [ "pnpm", "run", "start" ]
|
||||
|
||||
@@ -49,7 +49,7 @@ Tabler is fully responsive and compatible with all modern browsers. Thanks to it
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
We've created this admin panel for everyone who wants to create templates based on our pre-made components. Our mission is to deliver a user-friendly, clear and easy administration panel that can be used by both simple websites and sophisticated systems. The only requirement is basic HTML and CSS (and some [Liquid](https://github.com/Shopify/liquid/wiki)) knowledge — as a reward, you'll be able to manage and visualize different types of data in the easiest possible way!
|
||||
We've created this admin panel for everyone who wants to create templates based on our pre-made components. Our mission is to deliver a user-friendly, clear and easy administration panel that can be used by both simple websites and sophisticated systems. The only requirement is basic HTML and CSS knowledge — as a reward, you'll be able to manage and visualize different types of data in the easiest possible way!
|
||||
|
||||
* **Responsive:** With the support for mobile, tablet and desktop displays, it doesn’t matter what device you’re using. Tabler is responsive in all major browsers.
|
||||
* **Cross Browser:** Our theme works perfectly with the latest Chrome, Firefox+, Safari, Opera, Edge and mobile browsers. We work hard to provide continuous support for them.
|
||||
@@ -114,7 +114,7 @@ If you **do** wish to edit the source code after downloading it, for example to
|
||||
|
||||
### Installing and running development tools directly
|
||||
|
||||
1. [Install Node.js](https://nodejs.org/download/), which we use to manage our dependencies.
|
||||
1. [Install Node.js](https://nodejs.org/download/) (version 22.12 or newer), which we use to manage our dependencies.
|
||||
2. [Install pnpm](https://pnpm.io/installation) (We recommend either by [Using Corepack](https://pnpm.io/installation#using-corepack) or by [Using npm](https://pnpm.io/installation#using-npm))
|
||||
3. From the root `/tabler` directory where you downloaded the Tabler source files, run installation on the command line:
|
||||
```sh
|
||||
@@ -128,11 +128,10 @@ pnpm run start
|
||||
Changes to most of the source files of Tabler core, preview and docs will rebuild the application and refresh the page.
|
||||
|
||||
**Note**:
|
||||
If you wish to perform a one-off build without auto-refresh on any changes, you can run:
|
||||
If you wish to perform a one-off production build without a dev server, you can run:
|
||||
```sh
|
||||
pnpm run build
|
||||
```
|
||||
You can open [http://localhost:3001](http://localhost:3001) to configure the Web server.
|
||||
|
||||
|
||||
### Installing and running development tools with Docker
|
||||
@@ -145,15 +144,15 @@ Here is an example of how to use this image:
|
||||
```sh
|
||||
docker build -t tabler .
|
||||
```
|
||||
2. Run the tabler image. The following command mounts the `src` directory into the container, exposes port 3000 to browse the website locally, and exposes port 3001 to automatically sync changes:
|
||||
2. Run the tabler image. The following command mounts the source directories into the container, exposes port 3000 to browse the preview website and port 3010 to browse the documentation website:
|
||||
```sh
|
||||
docker run -p 3000:3000 -p 3001:3001 -v $(pwd)/src:/app/src tabler
|
||||
docker run -p 3000:3000 -p 3010:3010 -v $(pwd)/core:/app/core -v $(pwd)/preview:/app/preview -v $(pwd)/docs:/app/docs -v $(pwd)/shared:/app/shared tabler
|
||||
```
|
||||
3. Open your browser to [http://localhost:3000](http://localhost:3000). Edit anything in the `src/` folder and watch your browser refresh the page after it has been rebuilt.
|
||||
3. Open your browser to [http://localhost:3000](http://localhost:3000) (preview) or [http://localhost:3010](http://localhost:3010) (documentation). Edit anything in the `core/`, `preview/`, `docs/` or `shared/` folders and watch your browser refresh the page after it has been rebuilt.
|
||||
|
||||
**Docker Compose**
|
||||
|
||||
You can also use the docker compose config from this repo. From the root `/tabler` directory where you downloaded the Tabler source files, use `docker compose build && docker compose up` or `docker compose up --build` to build and start the container. Edit anything in the `src/` folder the same way as with plain docker and access the same URLs and ports in your browser.
|
||||
You can also use the docker compose config from this repo. From the root `/tabler` directory where you downloaded the Tabler source files, use `docker compose build && docker compose up` or `docker compose up --build` to build and start the container. Edit anything in the `core/`, `preview/`, `docs/` or `shared/` folders the same way as with plain docker and access the same URLs and ports in your browser.
|
||||
|
||||
## Bugs and feature requests
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join, dirname, basename } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { sync } from 'glob'
|
||||
import banner from '../../shared/banner/index.mjs'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const styles: string[] = sync(join(__dirname, '..', 'dist', 'css', '*.css'))
|
||||
|
||||
interface Plugins {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
const plugins: Plugins = {
|
||||
'tabler-flags': 'Flags',
|
||||
'tabler-flags.rtl': 'Flags RTL',
|
||||
'tabler-marketing': 'Marketing',
|
||||
'tabler-marketing.rtl': 'Marketing RTL',
|
||||
'tabler-payments': 'Payments',
|
||||
'tabler-payments.rtl': 'Payments RTL',
|
||||
'tabler-socials': 'Socials',
|
||||
'tabler-socials.rtl': 'Socials RTL',
|
||||
'tabler-vendors': 'Vendors',
|
||||
'tabler-vendors.rtl': 'Vendors RTL',
|
||||
}
|
||||
|
||||
styles.forEach((file: string) => {
|
||||
const content = readFileSync(file, 'utf8')
|
||||
const filename = basename(file)
|
||||
const pluginKey = Object.keys(plugins).find((plugin: string) => filename.includes(plugin))
|
||||
const plugin = pluginKey ? plugins[pluginKey] : undefined
|
||||
const regex = /^(@charset ['"][a-zA-Z0-9-]+['"];?)\n?/i
|
||||
|
||||
let newContent = ''
|
||||
const bannerText = banner(plugin)
|
||||
|
||||
if (content.match(regex)) {
|
||||
newContent = content.replace(regex, (m: string, m1: string) => {
|
||||
return `${m1}\n${bannerText}\n`
|
||||
})
|
||||
} else {
|
||||
newContent = `${bannerText}\n${content}`
|
||||
}
|
||||
|
||||
writeFileSync(file, newContent, 'utf8')
|
||||
})
|
||||
|
||||
@@ -12,73 +12,72 @@ const tablerPath = path.join(__dirname, '../scss/_variables.scss')
|
||||
|
||||
// Function to extract variable names from SCSS file
|
||||
function extractVariables(filePath: string): Set<string> {
|
||||
const content = readFileSync(filePath, 'utf8')
|
||||
const variables = new Set<string>()
|
||||
const content = readFileSync(filePath, 'utf8')
|
||||
const variables = new Set<string>()
|
||||
|
||||
// Regex to find SCSS variables
|
||||
// Looks for patterns like: $variable-name: value
|
||||
// Includes variables in maps and lists
|
||||
const variableRegex = /\$([a-zA-Z0-9_-]+)\s*[:=]/g
|
||||
// Regex to find SCSS variables
|
||||
// Looks for patterns like: $variable-name: value
|
||||
// Includes variables in maps and lists
|
||||
const variableRegex = /\$([a-zA-Z0-9_-]+)\s*[:=]/g
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = variableRegex.exec(content)) !== null) {
|
||||
const varName = match[1]
|
||||
variables.add(varName)
|
||||
}
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = variableRegex.exec(content)) !== null) {
|
||||
const varName = match[1]
|
||||
variables.add(varName)
|
||||
}
|
||||
|
||||
return variables
|
||||
return variables
|
||||
}
|
||||
|
||||
// Main function
|
||||
function compareVariables(): void {
|
||||
console.log('Analyzing Bootstrap variables...')
|
||||
const bootstrapVars = extractVariables(bootstrapPath)
|
||||
console.log(`Found ${bootstrapVars.size} variables in Bootstrap\n`)
|
||||
console.log('Analyzing Bootstrap variables...')
|
||||
const bootstrapVars = extractVariables(bootstrapPath)
|
||||
console.log(`Found ${bootstrapVars.size} variables in Bootstrap\n`)
|
||||
|
||||
console.log('Analyzing Tabler variables...')
|
||||
const tablerVars = extractVariables(tablerPath)
|
||||
console.log(`Found ${tablerVars.size} variables in Tabler\n`)
|
||||
console.log('Analyzing Tabler variables...')
|
||||
const tablerVars = extractVariables(tablerPath)
|
||||
console.log(`Found ${tablerVars.size} variables in Tabler\n`)
|
||||
|
||||
// Find variables that are in Bootstrap but not in Tabler
|
||||
const missingInTabler: string[] = []
|
||||
for (const varName of bootstrapVars) {
|
||||
if (!tablerVars.has(varName)) {
|
||||
missingInTabler.push(varName)
|
||||
}
|
||||
}
|
||||
// Find variables that are in Bootstrap but not in Tabler
|
||||
const missingInTabler: string[] = []
|
||||
for (const varName of bootstrapVars) {
|
||||
if (!tablerVars.has(varName)) {
|
||||
missingInTabler.push(varName)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort alphabetically
|
||||
missingInTabler.sort()
|
||||
// Sort alphabetically
|
||||
missingInTabler.sort()
|
||||
|
||||
console.log('='.repeat(60))
|
||||
console.log(`Variables in Bootstrap that are missing in Tabler: ${missingInTabler.length}`)
|
||||
console.log('='.repeat(60))
|
||||
console.log('='.repeat(60))
|
||||
console.log(`Variables in Bootstrap that are missing in Tabler: ${missingInTabler.length}`)
|
||||
console.log('='.repeat(60))
|
||||
|
||||
if (missingInTabler.length === 0) {
|
||||
console.log('All Bootstrap variables are present in Tabler!')
|
||||
} else {
|
||||
console.log('\nList of missing variables:\n')
|
||||
missingInTabler.forEach((varName: string, index: number) => {
|
||||
console.log(`${(index + 1).toString().padStart(4)}. $${varName}`)
|
||||
})
|
||||
}
|
||||
if (missingInTabler.length === 0) {
|
||||
console.log('All Bootstrap variables are present in Tabler!')
|
||||
} else {
|
||||
console.log('\nList of missing variables:\n')
|
||||
missingInTabler.forEach((varName: string, index: number) => {
|
||||
console.log(`${(index + 1).toString().padStart(4)}. $${varName}`)
|
||||
})
|
||||
}
|
||||
|
||||
// Optionally: show statistics
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('Statistics:')
|
||||
console.log(` Bootstrap: ${bootstrapVars.size} variables`)
|
||||
console.log(` Tabler: ${tablerVars.size} variables`)
|
||||
console.log(` Missing: ${missingInTabler.length} variables`)
|
||||
console.log(` Coverage: ${((1 - missingInTabler.length / bootstrapVars.size) * 100).toFixed(1)}%`)
|
||||
console.log('='.repeat(60))
|
||||
// Optionally: show statistics
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('Statistics:')
|
||||
console.log(` Bootstrap: ${bootstrapVars.size} variables`)
|
||||
console.log(` Tabler: ${tablerVars.size} variables`)
|
||||
console.log(` Missing: ${missingInTabler.length} variables`)
|
||||
console.log(` Coverage: ${((1 - missingInTabler.length / bootstrapVars.size) * 100).toFixed(1)}%`)
|
||||
console.log('='.repeat(60))
|
||||
}
|
||||
|
||||
// Run analysis
|
||||
try {
|
||||
compareVariables()
|
||||
compareVariables()
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error('Error during analysis:', errorMessage)
|
||||
process.exit(1)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error('Error during analysis:', errorMessage)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
||||
+18
-19
@@ -9,14 +9,14 @@ import { join, dirname } from 'node:path'
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
interface LibConfig {
|
||||
npm?: string
|
||||
js?: string[]
|
||||
css?: string[]
|
||||
head?: boolean
|
||||
npm?: string
|
||||
js?: string[]
|
||||
css?: string[]
|
||||
head?: boolean
|
||||
}
|
||||
|
||||
interface Libs {
|
||||
[key: string]: LibConfig
|
||||
[key: string]: LibConfig
|
||||
}
|
||||
|
||||
const libsData = libs as Libs
|
||||
@@ -24,22 +24,21 @@ const libsData = libs as Libs
|
||||
emptyDirSync(join(__dirname, '..', 'dist/libs'))
|
||||
|
||||
for (const name in libsData) {
|
||||
const { npm } = libsData[name]
|
||||
const { npm } = libsData[name]
|
||||
|
||||
if (npm) {
|
||||
const from = join(__dirname, '..', `node_modules/${npm}`)
|
||||
const to = join(__dirname, '..', `dist/libs/${npm}`)
|
||||
if (npm) {
|
||||
const from = join(__dirname, '..', `node_modules/${npm}`)
|
||||
const to = join(__dirname, '..', `dist/libs/${npm}`)
|
||||
|
||||
// create dir in dist/libs
|
||||
if (!existsSync(to)) {
|
||||
mkdirSync(to, { recursive: true })
|
||||
}
|
||||
// create dir in dist/libs
|
||||
if (!existsSync(to)) {
|
||||
mkdirSync(to, { recursive: true })
|
||||
}
|
||||
|
||||
copySync(from, to, {
|
||||
dereference: true,
|
||||
})
|
||||
copySync(from, to, {
|
||||
dereference: true,
|
||||
})
|
||||
|
||||
console.log(`Successfully copied ${npm}`)
|
||||
}
|
||||
console.log(`Successfully copied ${npm}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+94
-95
@@ -8,114 +8,113 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const configFile = path.join(__dirname, '../../shared/data/sri.json')
|
||||
|
||||
interface FileConfig {
|
||||
file: string
|
||||
configPropertyName: string
|
||||
file: string
|
||||
configPropertyName: string
|
||||
}
|
||||
|
||||
const files: FileConfig[] = [
|
||||
{
|
||||
file: 'dist/css/tabler.min.css',
|
||||
configPropertyName: 'css'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler.rtl.min.css',
|
||||
configPropertyName: 'css-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-flags.min.css',
|
||||
configPropertyName: 'css-flags'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-flags.rtl.min.css',
|
||||
configPropertyName: 'css-flags-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-marketing.min.css',
|
||||
configPropertyName: 'css-marketing'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-marketing.rtl.min.css',
|
||||
configPropertyName: 'css-marketing-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-payments.min.css',
|
||||
configPropertyName: 'css-payments'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-payments.rtl.min.css',
|
||||
configPropertyName: 'css-payments-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-props.min.css',
|
||||
configPropertyName: 'css-props'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-props.rtl.min.css',
|
||||
configPropertyName: 'css-props-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-themes.min.css',
|
||||
configPropertyName: 'css-themes'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-themes.rtl.min.css',
|
||||
configPropertyName: 'css-themes-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-socials.min.css',
|
||||
configPropertyName: 'css-socials'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-socials.rtl.min.css',
|
||||
configPropertyName: 'css-socials-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-vendors.min.css',
|
||||
configPropertyName: 'css-vendors'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-vendors.rtl.min.css',
|
||||
configPropertyName: 'css-vendors-rtl'
|
||||
},
|
||||
{
|
||||
file: 'dist/js/tabler.min.js',
|
||||
configPropertyName: 'js'
|
||||
},
|
||||
{
|
||||
file: 'dist/js/tabler-theme.min.js',
|
||||
configPropertyName: 'js-theme'
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler.min.css',
|
||||
configPropertyName: 'css',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler.rtl.min.css',
|
||||
configPropertyName: 'css-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-flags.min.css',
|
||||
configPropertyName: 'css-flags',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-flags.rtl.min.css',
|
||||
configPropertyName: 'css-flags-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-marketing.min.css',
|
||||
configPropertyName: 'css-marketing',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-marketing.rtl.min.css',
|
||||
configPropertyName: 'css-marketing-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-payments.min.css',
|
||||
configPropertyName: 'css-payments',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-payments.rtl.min.css',
|
||||
configPropertyName: 'css-payments-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-props.min.css',
|
||||
configPropertyName: 'css-props',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-props.rtl.min.css',
|
||||
configPropertyName: 'css-props-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-themes.min.css',
|
||||
configPropertyName: 'css-themes',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-themes.rtl.min.css',
|
||||
configPropertyName: 'css-themes-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-socials.min.css',
|
||||
configPropertyName: 'css-socials',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-socials.rtl.min.css',
|
||||
configPropertyName: 'css-socials-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-vendors.min.css',
|
||||
configPropertyName: 'css-vendors',
|
||||
},
|
||||
{
|
||||
file: 'dist/css/tabler-vendors.rtl.min.css',
|
||||
configPropertyName: 'css-vendors-rtl',
|
||||
},
|
||||
{
|
||||
file: 'dist/js/tabler.min.js',
|
||||
configPropertyName: 'js',
|
||||
},
|
||||
{
|
||||
file: 'dist/js/tabler-theme.min.js',
|
||||
configPropertyName: 'js-theme',
|
||||
},
|
||||
]
|
||||
|
||||
function generateSRI(): void {
|
||||
const sriData: Record<string, string> = {}
|
||||
const sriData: Record<string, string> = {}
|
||||
|
||||
for (const { file, configPropertyName } of files) {
|
||||
try {
|
||||
const filePath = path.join(__dirname, '..', file)
|
||||
const data = readFileSync(filePath, 'utf8')
|
||||
for (const { file, configPropertyName } of files) {
|
||||
try {
|
||||
const filePath = path.join(__dirname, '..', file)
|
||||
const data = readFileSync(filePath, 'utf8')
|
||||
|
||||
const algorithm = 'sha384'
|
||||
const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64')
|
||||
const integrity = `${algorithm}-${hash}`
|
||||
const algorithm = 'sha384'
|
||||
const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64')
|
||||
const integrity = `${algorithm}-${hash}`
|
||||
|
||||
console.log(`${configPropertyName}: ${integrity}`)
|
||||
console.log(`${configPropertyName}: ${integrity}`)
|
||||
|
||||
sriData[configPropertyName] = integrity
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error(`Error processing ${file}:`, errorMessage)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
sriData[configPropertyName] = integrity
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error(`Error processing ${file}:`, errorMessage)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(configFile, JSON.stringify(sriData, null, 2) + '\n', 'utf8')
|
||||
writeFileSync(configFile, JSON.stringify(sriData, null, 2) + '\n', 'utf8')
|
||||
}
|
||||
|
||||
try {
|
||||
generateSRI()
|
||||
generateSRI()
|
||||
} catch (error) {
|
||||
console.error('Failed to generate SRI:', error)
|
||||
process.exit(1)
|
||||
console.error('Failed to generate SRI:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export default context => {
|
||||
return {
|
||||
map: {
|
||||
inline: false,
|
||||
annotation: true,
|
||||
sourcesContent: true
|
||||
},
|
||||
plugins: {
|
||||
autoprefixer: {
|
||||
cascade: false
|
||||
},
|
||||
rtlcss: context.env === 'RTL'
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-11
@@ -16,15 +16,14 @@ const entryPath = path.resolve(__dirname, `../js/${entryFile}`)
|
||||
const entry = `${entryPath}.ts`
|
||||
|
||||
export default createViteConfig({
|
||||
entry: entry,
|
||||
name: libraryName,
|
||||
fileName: (format) => {
|
||||
const esmSuffix = format === 'es' ? '.esm' : ''
|
||||
return `${baseName}${esmSuffix}.js`
|
||||
},
|
||||
formats: ['es', 'umd'],
|
||||
outDir: path.resolve(__dirname, '../dist/js'),
|
||||
banner: bannerText,
|
||||
minify: false
|
||||
entry: entry,
|
||||
name: libraryName,
|
||||
fileName: (format) => {
|
||||
const esmSuffix = format === 'es' ? '.esm' : ''
|
||||
return `${baseName}${esmSuffix}.js`
|
||||
},
|
||||
formats: ['es', 'umd'],
|
||||
outDir: path.resolve(__dirname, '../dist/js'),
|
||||
banner: bannerText,
|
||||
minify: false,
|
||||
})
|
||||
|
||||
|
||||
@@ -38,5 +38,5 @@ export const bootstrap = {
|
||||
ScrollSpy,
|
||||
Tab,
|
||||
Toast,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class BaseComponent extends Config {
|
||||
EventHandler.off(this._element, ctor.EVENT_KEY)
|
||||
|
||||
for (const propertyName of Object.getOwnPropertyNames(this)) {
|
||||
(this as Record<string, unknown>)[propertyName] = null
|
||||
;(this as Record<string, unknown>)[propertyName] = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,7 @@ import BaseComponent from './base-component'
|
||||
import EventHandler from './dom/event-handler'
|
||||
import Manipulator from './dom/manipulator'
|
||||
import SelectorEngine from './dom/selector-engine'
|
||||
import {
|
||||
getNextActiveElement,
|
||||
isRTL,
|
||||
isVisible,
|
||||
reflow,
|
||||
triggerTransitionEnd
|
||||
} from './util/index'
|
||||
import { getNextActiveElement, isRTL, isVisible, reflow, triggerTransitionEnd } from './util/index'
|
||||
import Swipe from './util/swipe'
|
||||
import type { ComponentConfig, ComponentConfigType } from './types'
|
||||
|
||||
@@ -60,7 +54,7 @@ const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"], [data-tblr-ride="carousel
|
||||
|
||||
const KEY_TO_DIRECTION: Record<string, string> = {
|
||||
[ARROW_LEFT_KEY]: DIRECTION_RIGHT,
|
||||
[ARROW_RIGHT_KEY]: DIRECTION_LEFT
|
||||
[ARROW_RIGHT_KEY]: DIRECTION_LEFT,
|
||||
}
|
||||
|
||||
const Default: ComponentConfig = {
|
||||
@@ -69,7 +63,7 @@ const Default: ComponentConfig = {
|
||||
pause: 'hover',
|
||||
ride: false,
|
||||
touch: true,
|
||||
wrap: true
|
||||
wrap: true,
|
||||
}
|
||||
|
||||
const DefaultType: ComponentConfigType = {
|
||||
@@ -78,7 +72,7 @@ const DefaultType: ComponentConfigType = {
|
||||
pause: '(string|boolean)',
|
||||
ride: '(boolean|string)',
|
||||
touch: 'boolean',
|
||||
wrap: 'boolean'
|
||||
wrap: 'boolean',
|
||||
}
|
||||
|
||||
class Carousel extends BaseComponent {
|
||||
@@ -230,7 +224,7 @@ class Carousel extends BaseComponent {
|
||||
const swipeConfig = {
|
||||
leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),
|
||||
rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),
|
||||
endCallback: endCallBack
|
||||
endCallback: endCallBack,
|
||||
}
|
||||
|
||||
this._swipeHelper = new Swipe(this._element, swipeConfig)
|
||||
@@ -289,7 +283,7 @@ class Carousel extends BaseComponent {
|
||||
|
||||
const activeElement = this._getActive()
|
||||
const isNext = order === ORDER_NEXT
|
||||
const nextElement = element || getNextActiveElement(this._getItems(), activeElement!, isNext, this._config.wrap as boolean) as HTMLElement
|
||||
const nextElement = element || (getNextActiveElement(this._getItems(), activeElement!, isNext, this._config.wrap as boolean) as HTMLElement)
|
||||
|
||||
if (nextElement === activeElement) {
|
||||
return
|
||||
@@ -302,7 +296,7 @@ class Carousel extends BaseComponent {
|
||||
relatedTarget: nextElement,
|
||||
direction: this._orderToDirection(order),
|
||||
from: this._getItemIndex(activeElement!),
|
||||
to: nextElementIndex
|
||||
to: nextElementIndex,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="collapse"], [data-tblr-toggle="co
|
||||
|
||||
const Default: ComponentConfig = {
|
||||
parent: null,
|
||||
toggle: true
|
||||
toggle: true,
|
||||
}
|
||||
|
||||
const DefaultType: ComponentConfigType = {
|
||||
parent: '(null|element)',
|
||||
toggle: 'boolean'
|
||||
toggle: 'boolean',
|
||||
}
|
||||
|
||||
class Collapse extends BaseComponent {
|
||||
@@ -59,8 +59,7 @@ class Collapse extends BaseComponent {
|
||||
|
||||
for (const elem of toggleList) {
|
||||
const selector = SelectorEngine.getSelectorFromElement(elem)
|
||||
const filterElement = SelectorEngine.find(selector!)
|
||||
.filter(foundElement => foundElement === this._element)
|
||||
const filterElement = SelectorEngine.find(selector!).filter((foundElement) => foundElement === this._element)
|
||||
|
||||
if (selector !== null && filterElement.length) {
|
||||
this._triggerArray.push(elem)
|
||||
@@ -107,8 +106,8 @@ class Collapse extends BaseComponent {
|
||||
|
||||
if (this._config.parent) {
|
||||
activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)
|
||||
.filter(element => element !== this._element)
|
||||
.map(element => Collapse.getOrCreateInstance(element, { toggle: false }) as Collapse)
|
||||
.filter((element) => element !== this._element)
|
||||
.map((element) => Collapse.getOrCreateInstance(element, { toggle: false }) as Collapse)
|
||||
}
|
||||
|
||||
if (activeChildren.length && activeChildren[0]._isTransitioning) {
|
||||
@@ -225,7 +224,7 @@ class Collapse extends BaseComponent {
|
||||
|
||||
_getFirstLevelChildren(selector: string): HTMLElement[] {
|
||||
const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent as HTMLElement)
|
||||
return SelectorEngine.find(selector, this._config.parent as HTMLElement).filter(element => !children.includes(element))
|
||||
return SelectorEngine.find(selector, this._config.parent as HTMLElement).filter((element) => !children.includes(element))
|
||||
}
|
||||
|
||||
_addAriaAndCollapsedClass(triggerArray: HTMLElement[], isOpen: boolean): void {
|
||||
@@ -246,7 +245,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
|
||||
}
|
||||
|
||||
for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {
|
||||
(Collapse.getOrCreateInstance(element, { toggle: false }) as Collapse).toggle()
|
||||
;(Collapse.getOrCreateInstance(element, { toggle: false }) as Collapse).toggle()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ const Data = {
|
||||
if (instanceMap.size === 0) {
|
||||
elementMap.delete(element)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default Data
|
||||
|
||||
@@ -26,7 +26,7 @@ const eventRegistry: Record<string | number, Record<string, Record<string | numb
|
||||
let uidEvent = 1
|
||||
const customEvents: Record<string, string> = {
|
||||
mouseenter: 'mouseover',
|
||||
mouseleave: 'mouseout'
|
||||
mouseleave: 'mouseout',
|
||||
}
|
||||
|
||||
const nativeEvents = new Set([
|
||||
@@ -75,7 +75,7 @@ const nativeEvents = new Set([
|
||||
'readystatechange',
|
||||
'error',
|
||||
'abort',
|
||||
'scroll'
|
||||
'scroll',
|
||||
])
|
||||
|
||||
function makeEventUid(element: EventableElement | EventCallback, uid?: string): string | number {
|
||||
@@ -125,20 +125,11 @@ function bootstrapDelegationHandler(element: EventTarget, selector: string, fn:
|
||||
} as BootstrapHandler
|
||||
}
|
||||
|
||||
function findHandler(
|
||||
events: Record<string | number, BootstrapHandler>,
|
||||
callable: EventCallback,
|
||||
delegationSelector: string | null = null
|
||||
): BootstrapHandler | undefined {
|
||||
return Object.values(events)
|
||||
.find(event => event.callable === callable && event.delegationSelector === delegationSelector)
|
||||
function findHandler(events: Record<string | number, BootstrapHandler>, callable: EventCallback, delegationSelector: string | null = null): BootstrapHandler | undefined {
|
||||
return Object.values(events).find((event) => event.callable === callable && event.delegationSelector === delegationSelector)
|
||||
}
|
||||
|
||||
function normalizeParameters(
|
||||
originalTypeEvent: string,
|
||||
handler: string | EventCallback | undefined,
|
||||
delegationFunction: EventCallback | undefined
|
||||
): [boolean, EventCallback, string] {
|
||||
function normalizeParameters(originalTypeEvent: string, handler: string | EventCallback | undefined, delegationFunction: EventCallback | undefined): [boolean, EventCallback, string] {
|
||||
const isDelegated = typeof handler === 'string'
|
||||
const callable = isDelegated ? delegationFunction! : (handler || delegationFunction)!
|
||||
let typeEvent = getTypeEvent(originalTypeEvent)
|
||||
@@ -150,13 +141,7 @@ function normalizeParameters(
|
||||
return [isDelegated, callable, typeEvent]
|
||||
}
|
||||
|
||||
function addHandler(
|
||||
element: EventTarget | null,
|
||||
originalTypeEvent: string,
|
||||
handler: string | EventCallback | undefined,
|
||||
delegationFunction: EventCallback | undefined,
|
||||
oneOff: boolean
|
||||
): void {
|
||||
function addHandler(element: EventTarget | null, originalTypeEvent: string, handler: string | EventCallback | undefined, delegationFunction: EventCallback | undefined, oneOff: boolean): void {
|
||||
if (typeof originalTypeEvent !== 'string' || !element) {
|
||||
return
|
||||
}
|
||||
@@ -178,7 +163,7 @@ function addHandler(
|
||||
|
||||
const events = getElementEvents(element)
|
||||
const handlers = events[typeEvent] || (events[typeEvent] = {})
|
||||
const previousFunction = findHandler(handlers, callable, isDelegated ? handler as string : null)
|
||||
const previousFunction = findHandler(handlers, callable, isDelegated ? (handler as string) : null)
|
||||
|
||||
if (previousFunction) {
|
||||
previousFunction.oneOff = previousFunction.oneOff && oneOff
|
||||
@@ -187,11 +172,9 @@ function addHandler(
|
||||
}
|
||||
|
||||
const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))
|
||||
const fn: BootstrapHandler = isDelegated ?
|
||||
bootstrapDelegationHandler(element, handler as string, callable) :
|
||||
bootstrapHandler(element, callable)
|
||||
const fn: BootstrapHandler = isDelegated ? bootstrapDelegationHandler(element, handler as string, callable) : bootstrapHandler(element, callable)
|
||||
|
||||
fn.delegationSelector = isDelegated ? handler as string : null
|
||||
fn.delegationSelector = isDelegated ? (handler as string) : null
|
||||
fn.callable = callable
|
||||
fn.oneOff = oneOff
|
||||
fn.uidEvent = uid
|
||||
@@ -200,13 +183,7 @@ function addHandler(
|
||||
element.addEventListener(typeEvent, fn, isDelegated)
|
||||
}
|
||||
|
||||
function removeHandler(
|
||||
element: EventTarget,
|
||||
events: Record<string, Record<string | number, BootstrapHandler>>,
|
||||
typeEvent: string,
|
||||
handler: EventCallback,
|
||||
delegationSelector?: string | null
|
||||
): void {
|
||||
function removeHandler(element: EventTarget, events: Record<string, Record<string | number, BootstrapHandler>>, typeEvent: string, handler: EventCallback, delegationSelector?: string | null): void {
|
||||
const fn = findHandler(events[typeEvent], handler, delegationSelector ?? null)
|
||||
|
||||
if (!fn) {
|
||||
@@ -217,12 +194,7 @@ function removeHandler(
|
||||
delete events[typeEvent][fn.uidEvent!]
|
||||
}
|
||||
|
||||
function removeNamespacedHandlers(
|
||||
element: EventTarget,
|
||||
events: Record<string, Record<string | number, BootstrapHandler>>,
|
||||
typeEvent: string,
|
||||
namespace: string
|
||||
): void {
|
||||
function removeNamespacedHandlers(element: EventTarget, events: Record<string, Record<string | number, BootstrapHandler>>, typeEvent: string, namespace: string): void {
|
||||
const storeElementEvent = events[typeEvent] || {}
|
||||
|
||||
for (const [handlerKey, event] of Object.entries(storeElementEvent)) {
|
||||
@@ -262,7 +234,7 @@ const EventHandler = {
|
||||
return
|
||||
}
|
||||
|
||||
removeHandler(element, events, typeEvent, callable, isDelegated ? handler as string : null)
|
||||
removeHandler(element, events, typeEvent, callable, isDelegated ? (handler as string) : null)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -291,19 +263,19 @@ const EventHandler = {
|
||||
element.dispatchEvent(evt)
|
||||
|
||||
return evt
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function hydrateObj<T extends object>(obj: T, meta: Record<string, unknown> = {}): T {
|
||||
for (const [key, value] of Object.entries(meta)) {
|
||||
try {
|
||||
(obj as Record<string, unknown>)[key] = value
|
||||
;(obj as Record<string, unknown>)[key] = value
|
||||
} catch {
|
||||
Object.defineProperty(obj, key, {
|
||||
configurable: true,
|
||||
get() {
|
||||
return value
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ function normalizeData(value: string): DataValue {
|
||||
}
|
||||
|
||||
function normalizeDataKey(key: string): string {
|
||||
return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)
|
||||
return key.replace(/[A-Z]/g, (chr) => `-${chr.toLowerCase()}`)
|
||||
}
|
||||
|
||||
const PREFIXES = ['tblr', 'bs'] as const
|
||||
@@ -60,7 +60,7 @@ const Manipulator = {
|
||||
const attributes: Record<string, DataValue> = {}
|
||||
|
||||
for (const prefix of PREFIXES) {
|
||||
const keys = Object.keys(element.dataset).filter(key => key.startsWith(prefix) && !key.startsWith(`${prefix}Config`))
|
||||
const keys = Object.keys(element.dataset).filter((key) => key.startsWith(prefix) && !key.startsWith(`${prefix}Config`))
|
||||
|
||||
for (const key of keys) {
|
||||
let pureKey = key.replace(new RegExp(`^${prefix}`), '')
|
||||
@@ -83,7 +83,7 @@ const Manipulator = {
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default Manipulator
|
||||
|
||||
@@ -24,7 +24,12 @@ const getSelector = (element: HTMLElement): string | null => {
|
||||
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
|
||||
}
|
||||
|
||||
return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null
|
||||
return selector
|
||||
? selector
|
||||
.split(',')
|
||||
.map((sel) => parseSelector(sel))
|
||||
.join(',')
|
||||
: null
|
||||
}
|
||||
|
||||
const SelectorEngine = {
|
||||
@@ -37,7 +42,7 @@ const SelectorEngine = {
|
||||
},
|
||||
|
||||
children(element: HTMLElement, selector: string): HTMLElement[] {
|
||||
return Array.from(element.children).filter(child => child.matches(selector)) as HTMLElement[]
|
||||
return Array.from(element.children).filter((child) => child.matches(selector)) as HTMLElement[]
|
||||
},
|
||||
|
||||
parents(element: HTMLElement, selector: string): HTMLElement[] {
|
||||
@@ -81,18 +86,9 @@ const SelectorEngine = {
|
||||
},
|
||||
|
||||
focusableChildren(element: HTMLElement): HTMLElement[] {
|
||||
const focusables = [
|
||||
'a',
|
||||
'button',
|
||||
'input',
|
||||
'textarea',
|
||||
'select',
|
||||
'details',
|
||||
'[tabindex]',
|
||||
'[contenteditable="true"]'
|
||||
].map(selector => `${selector}:not([tabindex^="-"])`).join(',')
|
||||
const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map((selector) => `${selector}:not([tabindex^="-"])`).join(',')
|
||||
|
||||
return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))
|
||||
return this.find(focusables, element).filter((el) => !isDisabled(el) && isVisible(el))
|
||||
},
|
||||
|
||||
getSelectorFromElement(element: HTMLElement): string | null {
|
||||
@@ -115,7 +111,7 @@ const SelectorEngine = {
|
||||
const selector = getSelector(element)
|
||||
|
||||
return selector ? SelectorEngine.find(selector) : []
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default SelectorEngine
|
||||
|
||||
@@ -10,16 +10,7 @@ import BaseComponent from './base-component'
|
||||
import EventHandler from './dom/event-handler'
|
||||
import Manipulator from './dom/manipulator'
|
||||
import SelectorEngine from './dom/selector-engine'
|
||||
import {
|
||||
execute,
|
||||
getElement,
|
||||
getNextActiveElement,
|
||||
isDisabled,
|
||||
isElement,
|
||||
isRTL,
|
||||
isVisible,
|
||||
noop
|
||||
} from './util/index'
|
||||
import { execute, getElement, getNextActiveElement, isDisabled, isElement, isRTL, isVisible, noop } from './util/index'
|
||||
import type { ComponentConfig, ComponentConfigType } from './types'
|
||||
|
||||
const NAME = 'dropdown'
|
||||
@@ -70,7 +61,7 @@ const Default: ComponentConfig = {
|
||||
display: 'dynamic',
|
||||
offset: [0, 2],
|
||||
popperConfig: null,
|
||||
reference: 'toggle'
|
||||
reference: 'toggle',
|
||||
}
|
||||
|
||||
const DefaultType: ComponentConfigType = {
|
||||
@@ -79,7 +70,7 @@ const DefaultType: ComponentConfigType = {
|
||||
display: 'string',
|
||||
offset: '(array|string|function)',
|
||||
popperConfig: '(null|object|function)',
|
||||
reference: '(string|element|object)'
|
||||
reference: '(string|element|object)',
|
||||
}
|
||||
|
||||
class Dropdown extends BaseComponent {
|
||||
@@ -93,9 +84,7 @@ class Dropdown extends BaseComponent {
|
||||
|
||||
this._popper = null
|
||||
this._parent = this._element.parentNode as HTMLElement
|
||||
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||
|
||||
SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||
|
||||
SelectorEngine.findOne(SELECTOR_MENU, this._parent)!
|
||||
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent)!
|
||||
this._inNavbar = this._detectNavbar()
|
||||
}
|
||||
|
||||
@@ -121,7 +110,7 @@ class Dropdown extends BaseComponent {
|
||||
}
|
||||
|
||||
const relatedTarget = {
|
||||
relatedTarget: this._element
|
||||
relatedTarget: this._element,
|
||||
}
|
||||
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)
|
||||
@@ -152,7 +141,7 @@ class Dropdown extends BaseComponent {
|
||||
}
|
||||
|
||||
const relatedTarget = {
|
||||
relatedTarget: this._element
|
||||
relatedTarget: this._element,
|
||||
}
|
||||
|
||||
this._completeHide(relatedTarget)
|
||||
@@ -199,9 +188,7 @@ class Dropdown extends BaseComponent {
|
||||
_getConfig(config: Partial<ComponentConfig>): ComponentConfig {
|
||||
config = super._getConfig(config)
|
||||
|
||||
if (typeof config.reference === 'object' && !isElement(config.reference) &&
|
||||
typeof (config.reference as any).getBoundingClientRect !== 'function'
|
||||
) {
|
||||
if (typeof config.reference === 'object' && !isElement(config.reference) && typeof (config.reference as any).getBoundingClientRect !== 'function') {
|
||||
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
|
||||
}
|
||||
|
||||
@@ -210,7 +197,7 @@ class Dropdown extends BaseComponent {
|
||||
|
||||
_createPopper(): void {
|
||||
if (typeof Popper === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)')
|
||||
throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org/docs/v2/)")
|
||||
}
|
||||
|
||||
let referenceElement: HTMLElement | Popper.VirtualElement = this._element
|
||||
@@ -267,7 +254,7 @@ class Dropdown extends BaseComponent {
|
||||
const { offset } = this._config
|
||||
|
||||
if (typeof offset === 'string') {
|
||||
return offset.split(',').map(value => Number.parseInt(value, 10))
|
||||
return offset.split(',').map((value) => Number.parseInt(value, 10))
|
||||
}
|
||||
|
||||
if (typeof offset === 'function') {
|
||||
@@ -280,37 +267,41 @@ class Dropdown extends BaseComponent {
|
||||
_getPopperConfig(): Partial<Popper.Options> {
|
||||
const defaultBsPopperConfig: Partial<Popper.Options> = {
|
||||
placement: this._getPlacement() as Popper.Placement,
|
||||
modifiers: [{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: this._config.boundary
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: this._getOffset()
|
||||
}
|
||||
}]
|
||||
modifiers: [
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: this._config.boundary,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: this._getOffset(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
if (this._inNavbar || this._config.display === 'static') {
|
||||
Manipulator.setDataAttribute(this._menu, 'popper', 'static')
|
||||
defaultBsPopperConfig.modifiers = [{
|
||||
name: 'applyStyles',
|
||||
enabled: false
|
||||
}]
|
||||
defaultBsPopperConfig.modifiers = [
|
||||
{
|
||||
name: 'applyStyles',
|
||||
enabled: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const popperConfig = execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
|
||||
return {
|
||||
...defaultBsPopperConfig,
|
||||
...(typeof popperConfig === 'object' && popperConfig !== null ? popperConfig : {})
|
||||
...(typeof popperConfig === 'object' && popperConfig !== null ? popperConfig : {}),
|
||||
}
|
||||
}
|
||||
|
||||
_selectMenuItem({ key, target }: { key: string; target: HTMLElement }): void {
|
||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))
|
||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter((element) => isVisible(element))
|
||||
|
||||
if (!items.length) {
|
||||
return
|
||||
@@ -334,11 +325,7 @@ class Dropdown extends BaseComponent {
|
||||
|
||||
const composedPath = event.composedPath()
|
||||
const isMenuTarget = composedPath.includes(context._menu)
|
||||
if (
|
||||
composedPath.includes(context._element) ||
|
||||
(context._config.autoClose === 'inside' && !isMenuTarget) ||
|
||||
(context._config.autoClose === 'outside' && isMenuTarget)
|
||||
) {
|
||||
if (composedPath.includes(context._element) || (context._config.autoClose === 'inside' && !isMenuTarget) || (context._config.autoClose === 'outside' && isMenuTarget)) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -371,11 +358,7 @@ class Dropdown extends BaseComponent {
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?
|
||||
this :
|
||||
(SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||
|
||||
SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||
|
||||
SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, (event as any).delegateTarget.parentNode))
|
||||
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, (event as any).delegateTarget.parentNode)
|
||||
|
||||
const instance = Dropdown.getOrCreateInstance(getToggleButton!) as Dropdown
|
||||
|
||||
|
||||
@@ -11,9 +11,7 @@ import SelectorEngine from './dom/selector-engine'
|
||||
import Backdrop from './util/backdrop'
|
||||
import { enableDismissTrigger } from './util/component-functions'
|
||||
import FocusTrap from './util/focustrap'
|
||||
import {
|
||||
isRTL, isVisible, reflow
|
||||
} from './util/index'
|
||||
import { isRTL, isVisible, reflow } from './util/index'
|
||||
import ScrollBarHelper from './util/scrollbar'
|
||||
|
||||
/**
|
||||
@@ -58,13 +56,13 @@ interface ComponentConfigType {
|
||||
const Default: ComponentConfig = {
|
||||
backdrop: true,
|
||||
focus: true,
|
||||
keyboard: true
|
||||
keyboard: true,
|
||||
}
|
||||
|
||||
const DefaultType: ComponentConfigType = {
|
||||
backdrop: '(boolean|string)',
|
||||
focus: 'boolean',
|
||||
keyboard: 'boolean'
|
||||
keyboard: 'boolean',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +112,7 @@ class Modal extends BaseComponent {
|
||||
}
|
||||
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
|
||||
relatedTarget
|
||||
relatedTarget,
|
||||
})
|
||||
|
||||
if (showEvent.defaultPrevented) {
|
||||
@@ -170,13 +168,13 @@ class Modal extends BaseComponent {
|
||||
_initializeBackDrop(): Backdrop {
|
||||
return new Backdrop({
|
||||
isVisible: Boolean(this._config.backdrop),
|
||||
isAnimated: this._isAnimated()
|
||||
isAnimated: this._isAnimated(),
|
||||
})
|
||||
}
|
||||
|
||||
_initializeFocusTrap(): FocusTrap {
|
||||
return new FocusTrap({
|
||||
trapElement: this._element
|
||||
trapElement: this._element,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -207,7 +205,7 @@ class Modal extends BaseComponent {
|
||||
|
||||
this._isTransitioning = false
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN, {
|
||||
relatedTarget
|
||||
relatedTarget,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user