mirror of
https://github.com/tabler/tabler.git
synced 2026-10-13 21:57:00 +04:00
Merge branch 'dev' of https://github.com/tabler/tabler into v2-dev
This commit is contained in:
commit
0c878508fd
54 files changed
+617
-335
No files matched your search
@@ -1,7 +1,6 @@
|
||||
# Classes used in the rendered html that no stylesheet defines on purpose:
|
||||
# JavaScript hooks and demo-only markers. One per line; delete a line when the hook goes.
|
||||
callout-note
|
||||
checkbox-wrapper-mail
|
||||
datagrid-content
|
||||
datagrid-item
|
||||
datatable
|
||||
@@ -11,17 +10,7 @@ docs-search
|
||||
docs-theme-toggle
|
||||
dropzone-msg-desc
|
||||
dropzone-msg-title
|
||||
email-action-icons
|
||||
email-action-icons-item
|
||||
email-border
|
||||
email-content
|
||||
email-date
|
||||
email-list
|
||||
email-sender-info
|
||||
email-subject
|
||||
email-title
|
||||
fallback
|
||||
file-offcanvas
|
||||
github-dark
|
||||
icon-tabler
|
||||
icon-tabler-pencil
|
||||
@@ -47,7 +36,6 @@ sort-score
|
||||
sort-status
|
||||
sort-tags
|
||||
sort-type
|
||||
star-toggle
|
||||
table-tbody
|
||||
tblr-illustrations-computer-fix-a
|
||||
tblr-illustrations-computer-fix-b
|
||||
|
||||
@@ -31,11 +31,7 @@ const { version } = JSON.parse(readFileSync(join(repoRoot, 'core', 'package.json
|
||||
// The `## <version>` block of a changelog, without its heading.
|
||||
const versionBlock = (changelog: string): string => {
|
||||
const lines = changelog.split('\n')
|
||||
// `## 1.5.0`, or `## 1.5.0 - 2026-09-05` once the date has been stamped on it.
|
||||
const start = lines.findIndex((line) => {
|
||||
const heading = line.trim()
|
||||
return heading === `## ${version}` || heading.startsWith(`## ${version} - `)
|
||||
})
|
||||
const start = lines.findIndex((line) => line.trim() === `## ${version}`)
|
||||
if (start === -1) return ''
|
||||
|
||||
const rest = lines.slice(start + 1)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// Adds the release date to the heading `changeset version` has just written, so
|
||||
// every entry in a changelog reads `## 1.5.0 - 2026-09-05`.
|
||||
//
|
||||
// The date is the day the version pull request was last regenerated, which the
|
||||
// bot redoes on every push to dev. It can therefore be a day or two before the
|
||||
// release when nothing lands in between.
|
||||
//
|
||||
// Run: pnpm run stamp-changelog-dates (part of `pnpm run version`)
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const repoRoot = join(__dirname, '..')
|
||||
|
||||
const packages = ['core', 'preview', 'docs']
|
||||
|
||||
interface PackageJson {
|
||||
version: string
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
const stamped: string[] = []
|
||||
|
||||
for (const name of packages) {
|
||||
const changelog = join(repoRoot, name, 'CHANGELOG.md')
|
||||
if (!existsSync(changelog)) continue
|
||||
|
||||
const { version } = JSON.parse(readFileSync(join(repoRoot, name, 'package.json'), 'utf8')) as PackageJson
|
||||
const content = readFileSync(changelog, 'utf8')
|
||||
|
||||
// Only a heading without a date, so running this twice changes nothing.
|
||||
const lines = content.split('\n')
|
||||
const index = lines.findIndex((line) => line.trim() === `## ${version}`)
|
||||
if (index === -1) continue
|
||||
|
||||
lines[index] = `## ${version} - ${today}`
|
||||
writeFileSync(changelog, lines.join('\n'))
|
||||
stamped.push(`${name}/CHANGELOG.md: ## ${version} - ${today}`)
|
||||
}
|
||||
|
||||
console.log(stamped.length > 0 ? stamped.join('\n') : 'Nothing to stamp.')
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Updated the avatar list and brand card descriptions on the avatars preview page.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@tabler/docs': patch
|
||||
---
|
||||
|
||||
Added the color palette, flags, payments and social icons tables to the docs `.md` mirrors via a new `MarkdownSource` component.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed `.card-code` so the code block follows the card corners instead of drawing its own `border-radius`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed invisible box-shadows in dark mode with a new global `--tblr-shadow-color` `light-dark()` token.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Fixed the docs "On this page" list running past the viewport; the sticky rail now scrolls on its own.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed the `.dropdown-menu-arrow` border color and replaced the deprecated `clip` with `clip-path`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Fixed the unstyled message list on the `email-inbox` page: a hoverable list group with label dots, dates and row actions.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed the folded sidebar flashing the active submenu as a flyout on reload before `tabler.js` closed it.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
---
|
||||
|
||||
Fixed the invisible `.form-switch` knob in light mode by restoring a gray `$form-switch-color`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Fixed the preview icons page to render all icons in production builds, keeping the 20-icon limit only for the dev server.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Updated the preview card descriptions to name CSS classes and markup instead of Astro component props.
|
||||
@@ -44,6 +44,29 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# `changesets/action` fills the version pull request with one section per
|
||||
# package (`## @tabler/core@1.5.1`, …), so a changeset that touches several
|
||||
# packages shows up several times. Replace that body with the same notes the
|
||||
# GitHub release gets: the intro from `.github/release-notes/<version>.md`
|
||||
# and the changes grouped as core / demo / docs, each listed once. The
|
||||
# action rewrites the body on every push to dev, so this runs right after it.
|
||||
- name: Write the release notes into the version pull request
|
||||
if: steps.changesets.outputs.pr-number != ''
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.changesets.outputs.pr-number }}
|
||||
run: |
|
||||
git fetch --depth=1 origin changeset-release/dev
|
||||
git checkout --force --detach origin/changeset-release/dev
|
||||
version=$(node -p "require('./core/package.json').version")
|
||||
{
|
||||
echo "Merging this pull request publishes \`@tabler/core@${version}\` to npm and creates the GitHub release. It is regenerated on every push to \`dev\`."
|
||||
echo
|
||||
pnpm run --silent release-notes
|
||||
} > "${RUNNER_TEMP}/pr-body.md"
|
||||
gh pr edit "${PR_NUMBER}" --body-file "${RUNNER_TEMP}/pr-body.md"
|
||||
|
||||
# `pnpm run build` above zipped preview/dist into packages-zip/; attach it to
|
||||
# the GitHub release that changesets/action just created for @tabler/core.
|
||||
- name: Attach the preview zip to the GitHub release
|
||||
|
||||
+33
-33
@@ -1,6 +1,6 @@
|
||||
# @tabler/core
|
||||
|
||||
## 1.5.0 - 2026-09-05
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
- 70ec683: Updated `$card-status-size` default from `$border-width-wide` to `3px`.
|
||||
- 666ccd6: Updated shadow tokens (`--tblr-shadow-*`) to use the new `xs`–`2xl` and `overlay` values.
|
||||
|
||||
## 1.4.0 - 2025-07-13
|
||||
## 1.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -137,19 +137,19 @@
|
||||
- 666a296: Fix list group item hoverable only with `.list-group-hoverable` class
|
||||
- cfd4cb6: Fix `.pagination-link` hover styles to non-active items
|
||||
|
||||
## 1.3.2 - 2025-05-19
|
||||
## 1.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 446c34e: Fix README file in core package
|
||||
|
||||
## 1.3.1 - 2025-05-19
|
||||
## 1.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a7f73d7: Fix README file in core package
|
||||
|
||||
## 1.3.0 - 2025-05-19
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
- e675389: Fix missing border-radius to `.card-header-tabs`
|
||||
- 9007e73: Fix FAQ accordion structure
|
||||
|
||||
## 1.2.0 - 2025-04-16
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -214,13 +214,13 @@
|
||||
- 6d6d1bd: Add responsive font size for form controls on mobile devices
|
||||
- 6c566cf: Add new advanced table example
|
||||
|
||||
## 1.1.1 - 2025-03-01
|
||||
## 1.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f29c911: Fix Documentation structure
|
||||
|
||||
## 1.1.0 - 2025-03-01
|
||||
## 1.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -251,7 +251,7 @@
|
||||
- 24b944c: Fix `.avatar-upload` double borders
|
||||
- ca4ba14: Fixes navbar styles with new hover effects and color variables
|
||||
|
||||
## 1.0.0 - 2025-01-28
|
||||
## 1.0.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -373,14 +373,14 @@
|
||||
- 94b83d4: Add support for changeset tool for more efficient and organized code changes
|
||||
- c51ff28: Fix colors in date range datepicker
|
||||
|
||||
## 1.0.0-beta24 - 2025-01-11
|
||||
## 1.0.0-beta24
|
||||
|
||||
- Enhanced documentation.
|
||||
- Updated illustrations and improved SVG handling in HTML.
|
||||
- Updated copyright year in LICENSE file to 2025.
|
||||
- Added marketing pages plugin.
|
||||
|
||||
## 1.0.0-beta23 - 2025-01-07
|
||||
## 1.0.0-beta23
|
||||
|
||||
- Documentation improvements.
|
||||
- Added countup functionality and updated documentation example.
|
||||
@@ -417,7 +417,7 @@
|
||||
- Fixed icon issues.
|
||||
- Fixed URL in documentation.
|
||||
|
||||
## 1.0.0-beta22 - 2025-01-02
|
||||
## 1.0.0-beta22
|
||||
|
||||
- Fixed `@charset` CSS declaration in bundle.
|
||||
- Fixed cells with inline icons.
|
||||
@@ -440,7 +440,7 @@
|
||||
- Read changelog from `CHANGELOG.md` file.
|
||||
- Initialized VS Code configuration.
|
||||
|
||||
## 1.0.0-beta21 - 2024-09-08
|
||||
## 1.0.0-beta21
|
||||
|
||||
- Updated dependencies.
|
||||
- Updated Tabler Icons to v3.14.0 and the import script.
|
||||
@@ -477,7 +477,7 @@
|
||||
- Improved heights, scrolls, and layouts in Docs examples.
|
||||
- Fixed flags display in preview.
|
||||
|
||||
## 1.0.0-beta20 - 2023-08-24
|
||||
## 1.0.0-beta20
|
||||
|
||||
- Update `bootstrap` to v5.3.1
|
||||
- Add new `Chat` component
|
||||
@@ -503,7 +503,7 @@
|
||||
- `Dockerfile` fix
|
||||
- Switch from `npm` to `pnpm` for faster package installation
|
||||
|
||||
## 1.0.0-beta19 - 2023-05-15
|
||||
## 1.0.0-beta19
|
||||
|
||||
- Add customizable Star Ratings component using `star-rating.js` library (#1571)
|
||||
- Add new "Filled" section to Icons page (#1574)
|
||||
@@ -511,7 +511,7 @@
|
||||
- Fix text color in dark version of navbar (#1569)
|
||||
- Changelog update
|
||||
|
||||
## 1.0.0-beta18 - 2023-05-14
|
||||
## 1.0.0-beta18
|
||||
|
||||
- new page: Cookie banner
|
||||
- Unify dark mode with latest Bootstrap API and improve dark mode elements (#1561)
|
||||
@@ -534,7 +534,7 @@
|
||||
- Dark mode lite colors improvement
|
||||
- Fix non full width selects (#1392)
|
||||
|
||||
## 1.0.0-beta17 - 2023-01-28
|
||||
## 1.0.0-beta17
|
||||
|
||||
- update `bootstrap` to v5.3.0
|
||||
- update icons to v2.1.2
|
||||
@@ -556,7 +556,7 @@
|
||||
- move `@tabler/icons` to `dev-dependencies`
|
||||
- fix #1370: avatar stacked list is not stacked anymore
|
||||
|
||||
## 1.0.0-beta16 - 2022-11-12
|
||||
## 1.0.0-beta16
|
||||
|
||||
- new `Photogrid` page
|
||||
- `Steps` component improvements
|
||||
@@ -570,7 +570,7 @@
|
||||
- homepage navbar fix
|
||||
- fix #1262 - `.bg-opacity-xx` class is not functioning properly
|
||||
|
||||
## 1.0.0-beta15 - 2022-11-01
|
||||
## 1.0.0-beta15
|
||||
|
||||
- new `badges` page
|
||||
- `<kbd>` styling
|
||||
@@ -587,12 +587,12 @@
|
||||
- `btn-actions` fixes
|
||||
- replace `$text-muted` to css variable
|
||||
|
||||
## 1.0.0-beta14 - 2022-10-21
|
||||
## 1.0.0-beta14
|
||||
|
||||
- fix active items in dark mode
|
||||
- update Jekyll to newest version
|
||||
|
||||
## 1.0.0-beta13 - 2022-10-18
|
||||
## 1.0.0-beta13
|
||||
|
||||
- update Bootstrap to 5.2.1, update dependencies
|
||||
- new `tracking` component
|
||||
@@ -622,7 +622,7 @@
|
||||
- fix #1275 - remove last border-right on progress bar
|
||||
- fix #1261 - broken offcanvas bg
|
||||
|
||||
## 1.0.0-beta12 - 2022-09-19
|
||||
## 1.0.0-beta12
|
||||
|
||||
- new "Job listing" page
|
||||
- new "Sign in with cover" page
|
||||
@@ -647,7 +647,7 @@
|
||||
- fix form elements demo page radio buttons
|
||||
- replace `gulp-minify` with `gulp-terser`
|
||||
|
||||
## 1.0.0-beta11 - 2022-08-05
|
||||
## 1.0.0-beta11
|
||||
|
||||
- new `Dropzone` component
|
||||
- new `Lightbox` component
|
||||
@@ -662,7 +662,7 @@
|
||||
- fix: #1125 incorrect chart display in the mobile version
|
||||
- update Bootstrap to 5.2.0
|
||||
|
||||
## 1.0.0-beta10 - 2022-04-29
|
||||
## 1.0.0-beta10
|
||||
|
||||
- new `datatable` component
|
||||
- update Tabler Icons to v1.67
|
||||
@@ -671,7 +671,7 @@
|
||||
- replace !important modifier with more specific selectors (#1100)
|
||||
- new `FAQ` page
|
||||
|
||||
## 1.0.0-beta9 - 2022-02-26
|
||||
## 1.0.0-beta9
|
||||
|
||||
- fix: #1061 - list group item colors in light and dark modes
|
||||
- new `tasks` dashboard
|
||||
@@ -684,7 +684,7 @@
|
||||
- add `font-display: swap;` to improve font loading
|
||||
- new `Boxed` layout
|
||||
|
||||
## 1.0.0-beta8 - 2022-02-05
|
||||
## 1.0.0-beta8
|
||||
|
||||
- update dependencies
|
||||
- new vector maps demos
|
||||
@@ -693,7 +693,7 @@
|
||||
- replace `badge` with `status-dot` in `navbar-notifications.html`
|
||||
- map tooltip fixes
|
||||
|
||||
## 1.0.0-beta7 - 2022-02-05
|
||||
## 1.0.0-beta7
|
||||
|
||||
- fix: #1019 - project-overview.html link not working
|
||||
- fix: #1010 - card with bottom tabs has incorrect border radius
|
||||
@@ -706,7 +706,7 @@
|
||||
- RTL stylesheet fixes
|
||||
- new card action demos
|
||||
|
||||
## 1.0.0-beta6 - 2022-01-18
|
||||
## 1.0.0-beta6
|
||||
|
||||
- pricing cards fix
|
||||
- fix bug `fw-...`, `.fs-...` is missed (#987)
|
||||
@@ -719,7 +719,7 @@
|
||||
- fix #959 - `node-sass` does not properly compile nested media queries
|
||||
- update package dependencies to newest version
|
||||
|
||||
## 1.0.0-beta5 - 2021-12-07
|
||||
## 1.0.0-beta5
|
||||
|
||||
**Tabler has finally lived to see dark mode! 🌝🌚**
|
||||
|
||||
@@ -736,7 +736,7 @@
|
||||
- colors unify
|
||||
- add `tom-select` and remove `choices.js`
|
||||
|
||||
## 1.0.0-beta4 - 2021-10-24
|
||||
## 1.0.0-beta4
|
||||
|
||||
- upgrade required node.js version to 14
|
||||
- upgrade Bootstrap to 5.1
|
||||
@@ -744,14 +744,14 @@
|
||||
- fix #775 - litepicker not initializing
|
||||
- fix `nouislider` import in dev
|
||||
|
||||
## 1.0.0-beta3 - 2021-05-08
|
||||
## 1.0.0-beta3
|
||||
|
||||
- upgrade Bootstrap to 5.0
|
||||
- upgrade dependencies
|
||||
- change `$border-radius-pill` variable
|
||||
- badge vertical align fix
|
||||
|
||||
## 1.0.0-beta2 - 2021-03-29
|
||||
## 1.0.0-beta2
|
||||
|
||||
- update dependencies
|
||||
- `li` marker fix
|
||||
@@ -764,7 +764,7 @@
|
||||
- charts label fixes
|
||||
- charts docs
|
||||
|
||||
## 1.0.0-beta - 2021-02-17
|
||||
## 1.0.0-beta
|
||||
|
||||
**Initial beta release of Tabler v1.0! Lots more coming soon though 😁**
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
}
|
||||
|
||||
/** Shadows */
|
||||
--shadow-color: #{$box-shadow-color};
|
||||
@each $name, $value in $box-shadows {
|
||||
--shadow#{if(sass($name): '-#{$name}'; else: '')}: #{$value};
|
||||
}
|
||||
|
||||
+30
-19
@@ -949,30 +949,41 @@ $aspect-ratios: (
|
||||
) !default;
|
||||
|
||||
// Shadows
|
||||
$box-shadow-xs: 0 1px 2px 0 rgba(18, 18, 23, 0.05) !default;
|
||||
// Neutral drop-shadow tint. Emitted once as the global `--shadow-color` custom
|
||||
// property (see `_props.scss`); every shadow below only varies its opacity via
|
||||
// `color-transparent()`, never its colour.
|
||||
//
|
||||
// The light branch is already 60% transparent, so `color-transparent()` scales
|
||||
// each shadow down from the percentages below to the intended light-mode value
|
||||
// (e.g. `0.125 * 0.4 = 0.05`). The dark branch is fully opaque black, so the
|
||||
// same percentage lands 2.5x darker — otherwise a near-black shadow is
|
||||
// invisible against the dark surfaces. Keep the two in sync when tuning.
|
||||
$box-shadow-color: light-dark(rgba(18, 18, 23, 0.4), #000) !default;
|
||||
|
||||
$box-shadow-xs: 0 1px 2px 0 color-transparent(var(--shadow-color), 0.125) !default;
|
||||
$box-shadow-sm:
|
||||
0 1px 3px 0 rgba(18, 18, 23, 0.1),
|
||||
0 1px 2px 0 rgba(18, 18, 23, 0.06) !default;
|
||||
0 1px 3px 0 color-transparent(var(--shadow-color), 0.25),
|
||||
0 1px 2px 0 color-transparent(var(--shadow-color), 0.15) !default;
|
||||
$box-shadow-md:
|
||||
0px 2px 4px -1px rgba(18, 18, 23, 0.06),
|
||||
0px 4px 6px -1px rgba(18, 18, 23, 0.08) !default;
|
||||
0px 2px 4px -1px color-transparent(var(--shadow-color), 0.15),
|
||||
0px 4px 6px -1px color-transparent(var(--shadow-color), 0.2) !default;
|
||||
$box-shadow-lg:
|
||||
0px 4px 6px -2px rgba(18, 18, 23, 0.05),
|
||||
0px 10px 15px -3px rgba(18, 18, 23, 0.08) !default;
|
||||
0px 4px 6px -2px color-transparent(var(--shadow-color), 0.125),
|
||||
0px 10px 15px -3px color-transparent(var(--shadow-color), 0.2) !default;
|
||||
$box-shadow-xl:
|
||||
0px 10px 10px -5px rgba(18, 18, 23, 0.04),
|
||||
0px 20px 25px -5px rgba(18, 18, 23, 0.1) !default;
|
||||
$box-shadow-2xl: 0px 25px 50px -12px rgba(18, 18, 23, 0.25) !default;
|
||||
0px 10px 10px -5px color-transparent(var(--shadow-color), 0.1),
|
||||
0px 20px 25px -5px color-transparent(var(--shadow-color), 0.25) !default;
|
||||
$box-shadow-2xl: 0px 25px 50px -12px color-transparent(var(--shadow-color), 0.625) !default;
|
||||
$box-shadow-overlay:
|
||||
0px 2px 4px 0px rgba(18, 18, 23, 0.04),
|
||||
0px 5px 8px 0px rgba(18, 18, 23, 0.04),
|
||||
0px 10px 18px 0px rgba(18, 18, 23, 0.03),
|
||||
0px 24px 48px 0px rgba(18, 18, 23, 0.03),
|
||||
0px 0px 0px 1px rgba(18, 18, 23, 0.1) !default;
|
||||
0px 2px 4px 0px color-transparent(var(--shadow-color), 0.1),
|
||||
0px 5px 8px 0px color-transparent(var(--shadow-color), 0.1),
|
||||
0px 10px 18px 0px color-transparent(var(--shadow-color), 0.075),
|
||||
0px 24px 48px 0px color-transparent(var(--shadow-color), 0.075),
|
||||
0px 0px 0px 1px color-transparent(var(--shadow-color), 0.25) !default;
|
||||
|
||||
$box-shadow: $box-shadow-md !default;
|
||||
$box-shadow-transparent: 0 0 0 0 transparent !default;
|
||||
$box-shadow-border: 0px 0px 0px 1px rgba(18, 18, 23, 0.1) !default;
|
||||
$box-shadow-border: 0px 0px 0px 1px color-transparent(var(--shadow-color), 0.25) !default;
|
||||
$box-shadow-input: $box-shadow-xs !default;
|
||||
$box-shadow-card: $box-shadow-xs !default;
|
||||
$box-shadow-card-hover: $box-shadow-lg !default;
|
||||
@@ -995,7 +1006,7 @@ $box-shadows: (
|
||||
dropdown: $box-shadow-dropdown,
|
||||
) !default;
|
||||
|
||||
$box-shadow-inset: inset 0 1px 2px rgba($black, 0.075) !default;
|
||||
$box-shadow-inset: inset 0 1px 2px color-transparent(var(--shadow-color), 0.1875) !default;
|
||||
|
||||
// Component active
|
||||
$component-active-color: $white !default;
|
||||
@@ -1982,14 +1993,14 @@ $form-select-font-size-lg: $input-font-size-lg !default;
|
||||
$form-select-border-radius-lg: $input-border-radius-lg !default;
|
||||
$form-select-transition: $input-transition !default;
|
||||
|
||||
$form-switch-color: $white !default;
|
||||
$form-switch-color: $gray-200 !default;
|
||||
$form-switch-width: 2rem !default;
|
||||
$form-switch-height: 1.25rem !default;
|
||||
$form-switch-padding-start: $form-switch-width + 0.5rem !default;
|
||||
$form-switch-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color}'/></svg>") !default;
|
||||
$form-switch-border-radius: $form-switch-width !default;
|
||||
$form-switch-transition: background-position 0.15s ease-in-out !default;
|
||||
$form-switch-focus-color: $white !default;
|
||||
$form-switch-focus-color: $input-focus-border-color !default;
|
||||
$form-switch-focus-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-focus-color}'/></svg>") !default;
|
||||
$form-switch-checked-color: $white !default;
|
||||
$form-switch-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-checked-color}'/></svg>") !default;
|
||||
|
||||
@@ -622,6 +622,16 @@ Navbar vertical
|
||||
// a bare :where()/[…] selector would lose to it and never take effect.
|
||||
html[data-bs-sidebar^='folded'] {
|
||||
--sidebar-width: var(--sidebar-folded-width);
|
||||
|
||||
// The sidebar is rendered for the expanded state, so the active submenu can
|
||||
// arrive open (.show). Folded, it would paint as a flyout until sidebar.ts
|
||||
// closes it on DOMContentLoaded — a flicker on every reload. Menus opened by
|
||||
// the Dropdown plugin carry a popper data attribute (data-tblr-popper from
|
||||
// tabler.js, data-bs-popper from Bootstrap's own js), so only the
|
||||
// server-rendered ones hide.
|
||||
.navbar-vertical .dropdown-menu.show:not([data-bs-popper]):not([data-tblr-popper]) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-vertical:is(.navbar-folded, .navbar-folded-hover) {
|
||||
|
||||
@@ -568,6 +568,17 @@ Card code
|
||||
*/
|
||||
.card-code {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
// The code block is a dark box, so it follows the card's corners: rounded at
|
||||
// the card's top or bottom edge, square when another section sits next to it.
|
||||
&:first-child {
|
||||
@include border-top-radius(var(--card-border-radius));
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@include border-bottom-radius(var(--card-border-radius));
|
||||
}
|
||||
|
||||
.highlight {
|
||||
margin: 0;
|
||||
@@ -577,6 +588,7 @@ Card code
|
||||
pre {
|
||||
margin: 0 !important;
|
||||
border: 0 !important;
|
||||
@include border-radius(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,11 +100,12 @@ $dropdown-tokens: defaults(
|
||||
display: block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
clip: rect(0, 9px, 9px, 0);
|
||||
clip-path: inset(0 5px 5px 0);
|
||||
content: '';
|
||||
background: inherit;
|
||||
border: 1px solid;
|
||||
border-color: inherit;
|
||||
// The menu draws its edge with the 1px ring in --dropdown-box-shadow and has
|
||||
// no border, so `inherit` would give the arrow the text color.
|
||||
border: 1px solid var(--dropdown-border-color);
|
||||
|
||||
/* rtl:ignore */
|
||||
transform: rotate(45deg);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# @tabler/docs
|
||||
|
||||
## 1.5.0 - 2026-09-05
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
|
||||
@@ -26,11 +26,7 @@ export default defineConfig({
|
||||
},
|
||||
// Static output + the Vercel adapter: turns `redirects` below into real HTTP
|
||||
// redirects at Vercel's routing layer (no adapter = meta-refresh HTML pages).
|
||||
adapter: vercel({
|
||||
webAnalytics: {
|
||||
enabled: true,
|
||||
},
|
||||
}),
|
||||
adapter: vercel(),
|
||||
// renamed/moved pages, shared with middleware.ts
|
||||
redirects,
|
||||
// pages live at the package root (./pages) — content-first layout; all
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
---
|
||||
import MarkdownSource from './MarkdownSource.astro'
|
||||
import { markdownTable } from '@shared/lib/markdown-source'
|
||||
|
||||
interface Props {
|
||||
colors: Record<string, { prop: string; title: string }>
|
||||
colors: Record<string, { prop: string; title: string; hex?: string }>
|
||||
}
|
||||
|
||||
const { colors }: Props = Astro.props
|
||||
|
||||
// The .md mirror gets the palette as a table; hex values are listed only when
|
||||
// the data has real ones (the gray scale points at a CSS variable instead).
|
||||
const list = Object.values(colors)
|
||||
const withHex = list.some((color) => color.hex?.startsWith('#'))
|
||||
const markdown = markdownTable(
|
||||
['Color', 'CSS variable', ...(withHex ? ['Hex'] : [])],
|
||||
list.map((color) => [color.title, `\`${color.prop}\``, ...(withHex ? [color.hex?.startsWith('#') ? `\`${color.hex}\`` : ''] : [])]),
|
||||
)
|
||||
---
|
||||
|
||||
<div class="row row-cols-4 row-cols-md-6 g-3 g-md-4">
|
||||
{
|
||||
Object.values(colors).map((color) => {
|
||||
list.map((color) => {
|
||||
return (
|
||||
<div class="text-center">
|
||||
<div class="p-6 rounded border" style={{ backgroundColor: `var(${color.prop})` }} />
|
||||
@@ -18,3 +30,4 @@ const { colors }: Props = Astro.props
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<MarkdownSource name="Colors" markdown={markdown} />
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
// Fetches this page's Markdown mirror (its url with ".md" appended) and copies
|
||||
// its content to the clipboard, so the page can be pasted into an LLM directly.
|
||||
import Icon from '@ui/Icon.astro'
|
||||
|
||||
interface Props {
|
||||
/** Url of the page's `.md` mirror, fetched on click. */
|
||||
url: string
|
||||
/** Absolute url of the same `.md` mirror, for the "Ask ChatGPT" / "Ask Claude" prompts. */
|
||||
absoluteUrl?: string | undefined
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { url, absoluteUrl, class: className } = Astro.props
|
||||
|
||||
// Both ChatGPT and Claude open with this text already in the composer; the
|
||||
// reader still has to press send.
|
||||
const prompt = absoluteUrl && `Read ${absoluteUrl} so I can ask questions about it.`
|
||||
const askChatGptUrl = prompt && `https://chatgpt.com/?q=${encodeURIComponent(prompt)}`
|
||||
const askClaudeUrl = prompt && `https://claude.ai/new?q=${encodeURIComponent(prompt)}`
|
||||
---
|
||||
|
||||
<div class="btn-group">
|
||||
<button type="button" class:list={['btn btn-sm', className]} data-docs-copy-markdown-link={url}>
|
||||
<Icon name="clipboard" />
|
||||
<Icon name="check" class="d-none" />
|
||||
<span data-docs-copy-markdown-link-label>Copy to LLM</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<span class="visually-hidden">Toggle dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<a class="dropdown-item" href={url} target="_blank">
|
||||
<Icon name="external-link" />
|
||||
<span>View as markdown</span>
|
||||
</a>
|
||||
{
|
||||
askChatGptUrl && (
|
||||
<a class="dropdown-item" href={askChatGptUrl} target="_blank" rel="noopener noreferrer">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">
|
||||
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||
</svg>
|
||||
<span>Ask ChatGPT</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
askClaudeUrl && (
|
||||
<a class="dropdown-item" href={askClaudeUrl} target="_blank" rel="noopener noreferrer">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">
|
||||
<path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" />
|
||||
</svg>
|
||||
<span>Ask Claude</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Wire every instance on the page and read each one's url from its own attribute.
|
||||
const buttons = document.querySelectorAll<HTMLButtonElement>('[data-docs-copy-markdown-link]')
|
||||
|
||||
buttons.forEach((button) => {
|
||||
const label = button.querySelector<HTMLElement>('[data-docs-copy-markdown-link-label]')
|
||||
const [clipboardIcon, checkIcon] = button.querySelectorAll<HTMLElement>('svg')
|
||||
|
||||
button.addEventListener('click', async () => {
|
||||
const url = button.dataset.docsCopyMarkdownLink
|
||||
if (!url || !label) return
|
||||
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
await navigator.clipboard.writeText(await response.text())
|
||||
|
||||
const original = label.textContent
|
||||
label.textContent = 'Copied'
|
||||
clipboardIcon?.classList.add('d-none')
|
||||
checkIcon?.classList.remove('d-none')
|
||||
|
||||
setTimeout(() => {
|
||||
label.textContent = original
|
||||
clipboardIcon?.classList.remove('d-none')
|
||||
checkIcon?.classList.add('d-none')
|
||||
}, 2000)
|
||||
} catch {
|
||||
// clipboard permission refused — leave the label alone
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@@ -19,11 +19,9 @@ interface Props {
|
||||
editUrl?: string | undefined
|
||||
/** URL of the file implementing the component, from the page's `source` front matter */
|
||||
sourceUrl?: string | undefined
|
||||
/** url of this page's markdown mirror, offered as a copy button */
|
||||
markdownUrl?: string | undefined
|
||||
}
|
||||
|
||||
const { toc = [], editUrl, sourceUrl, markdownUrl } = Astro.props
|
||||
const { toc = [], editUrl, sourceUrl } = Astro.props
|
||||
|
||||
const illustrationsCount = illustrations.length
|
||||
---
|
||||
@@ -54,15 +52,8 @@ const illustrationsCount = illustrations.length
|
||||
</a>
|
||||
|
||||
{
|
||||
(markdownUrl || sourceUrl || editUrl) && (
|
||||
(sourceUrl || editUrl) && (
|
||||
<nav class="docs-page-links mt-6" aria-label="Page tools">
|
||||
{markdownUrl && (
|
||||
<button type="button" class="docs-page-link" data-docs-copy-markdown={markdownUrl}>
|
||||
<Icon name="clipboard" />
|
||||
<span data-docs-copy-label>Copy page as Markdown</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{sourceUrl && (
|
||||
<a href={sourceUrl} class="docs-page-link" target="_blank" rel="noopener noreferrer">
|
||||
<Icon name="code" />
|
||||
@@ -80,30 +71,3 @@ const illustrationsCount = illustrations.length
|
||||
)
|
||||
}
|
||||
<!-- END DOCS TOC -->
|
||||
|
||||
<script>
|
||||
// Fetches the .md mirror of the page and puts it on the clipboard, so the
|
||||
// whole page can be pasted into an assistant without hunting for the url.
|
||||
const button = document.querySelector<HTMLButtonElement>('[data-docs-copy-markdown]')
|
||||
|
||||
if (button) {
|
||||
const label = button.querySelector<HTMLElement>('[data-docs-copy-label]')
|
||||
|
||||
button.addEventListener('click', async () => {
|
||||
if (!label) return
|
||||
const url = button.dataset.docsCopyMarkdown
|
||||
if (!url) return
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
await navigator.clipboard.writeText(await response.text())
|
||||
const original = label.textContent
|
||||
label.textContent = 'Copied'
|
||||
setTimeout(() => {
|
||||
label.textContent = original
|
||||
}, 2000)
|
||||
} catch {
|
||||
// clipboard or network refused — leave the label alone
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,13 @@
|
||||
---
|
||||
import MarkdownSource from './MarkdownSource.astro'
|
||||
import { markdownTable } from '@shared/lib/markdown-source'
|
||||
import flags from '@data/flags.json'
|
||||
|
||||
// Same list as a table for the page's .md mirror
|
||||
const markdown = markdownTable(
|
||||
['Country', 'Code', 'CSS class'],
|
||||
flags.map((flag) => [flag.name, `\`${flag.flag}\``, `\`.flag-country-${flag.flag}\``]),
|
||||
)
|
||||
---
|
||||
|
||||
<table class="table table-vcenter">
|
||||
@@ -30,3 +38,4 @@ import flags from '@data/flags.json'
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<MarkdownSource name="Flags" markdown={markdown} />
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
// Ships a markdown version of a component's output for the page's .md mirror.
|
||||
// Render it next to the html; `name` is the tag the MDX uses for the component.
|
||||
// Only the mirror render (docs/lib/llms.ts, which sets the `markdownMirror`
|
||||
// local) gets the comment — a browser page never carries it.
|
||||
// See shared/lib/markdown-source.ts.
|
||||
import { markdownSource } from '@shared/lib/markdown-source'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
markdown: string
|
||||
}
|
||||
|
||||
const { name, markdown } = Astro.props
|
||||
---
|
||||
|
||||
{Astro.locals.markdownMirror && <Fragment set:html={markdownSource(name, markdown)} />}
|
||||
@@ -1,5 +1,13 @@
|
||||
---
|
||||
import MarkdownSource from './MarkdownSource.astro'
|
||||
import { markdownTable } from '@shared/lib/markdown-source'
|
||||
import payments from '@data/payments.json'
|
||||
|
||||
// Same list as a table for the page's .md mirror
|
||||
const markdown = markdownTable(
|
||||
['Provider', 'CSS class', 'Dark variant'],
|
||||
payments.map((payment) => [payment.name, `\`.payment-provider-${payment.logo}\``, `\`.payment-provider-${payment.logo}-dark\``]),
|
||||
)
|
||||
---
|
||||
|
||||
<table class="table table-vcenter">
|
||||
@@ -36,3 +44,4 @@ import payments from '@data/payments.json'
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<MarkdownSource name="Payments" markdown={markdown} />
|
||||
@@ -1,5 +1,13 @@
|
||||
---
|
||||
import MarkdownSource from './MarkdownSource.astro'
|
||||
import { markdownTable } from '@shared/lib/markdown-source'
|
||||
import socials from '@data/socials.json'
|
||||
|
||||
// Same list as a table for the page's .md mirror
|
||||
const markdown = markdownTable(
|
||||
['App', 'CSS class'],
|
||||
socials.map((social) => [social.name, `\`.social-app-${social.file}\``]),
|
||||
)
|
||||
---
|
||||
|
||||
<table class="table table-vcenter">
|
||||
@@ -29,3 +37,4 @@ import socials from '@data/socials.json'
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<MarkdownSource name="Socials" markdown={markdown} />
|
||||
Vendored
+7
@@ -3,3 +3,10 @@
|
||||
// clipboard.js, loaded via a page lib (libs.json) as a classic <script> global, not an
|
||||
// npm package — declared ambiently so `astro check` can resolve it in DocsLayout's copy script.
|
||||
declare var ClipboardJS: new (selector: Element, options: Record<string, unknown>) => { on(event: string, callback: (e: { clearSelection(): void; trigger: HTMLElement }) => void): void }
|
||||
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
/** set by docs/lib/llms.ts while rendering a page for its .md mirror; <MarkdownSource /> emits only then */
|
||||
markdownMirror?: boolean
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import DocsSearch from '@components/DocsSearch.astro';
|
||||
import DocsClassnames from '@components/DocsClassnames.astro';
|
||||
import DocsToc from '@components/DocsToc.astro';
|
||||
import type { TocItem } from '@components/DocsToc.astro';
|
||||
import DocsCopyMarkdownLink from '@components/DocsCopyMarkdownLink.astro';
|
||||
import DocsPagination from '@components/DocsPagination.astro';
|
||||
import DocsCard from '@components/DocsCard.astro';
|
||||
import { getDocsPage } from '@lib/docs-pages';
|
||||
@@ -114,6 +115,9 @@ const metaDescription = seoDescription ?? description;
|
||||
const siteName = pageSection ? `Tabler ${pageSection} Documentation` : 'Tabler Documentation';
|
||||
const canonicalUrl = new URL(docsUrl, Astro.site ?? site.docsUrl).href;
|
||||
const ogImageUrl = new URL('/static/og.png', Astro.site ?? site.docsUrl).href;
|
||||
// Absolute .md url: fetch() uses the relative markdownUrl (same-origin), but the
|
||||
// "Ask ChatGPT" link needs a url ChatGPT can actually resolve on its own.
|
||||
const canonicalMarkdownUrl = markdownUrl ? new URL(markdownUrl, Astro.site ?? site.docsUrl).href : undefined;
|
||||
|
||||
// Relative asset base: for /ui/components/alert/ -> "../../.."
|
||||
const depth = docsUrl.split('/').filter(Boolean).length;
|
||||
@@ -346,23 +350,30 @@ const relatedPages = await Promise.all(
|
||||
<div class="col d-flex flex-column">
|
||||
<div class="docs-article flex-fill">
|
||||
<div class="p-4 py-6 p-lg-6">
|
||||
{breadcrumbs && (
|
||||
<nav aria-label="Breadcrumb">
|
||||
<ol class="breadcrumb breadcrumb-arrows breadcrumb-muted mb-3">
|
||||
{breadcrumbs.map((item, index) =>
|
||||
index < breadcrumbs.length - 1 ? (
|
||||
<li class="breadcrumb-item">
|
||||
<a href={item.path}>{item.name}</a>
|
||||
</li>
|
||||
) : (
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
{item.name}
|
||||
</li>
|
||||
),
|
||||
{
|
||||
(breadcrumbs || markdownUrl) && (
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||
{breadcrumbs && (
|
||||
<nav aria-label="Breadcrumb">
|
||||
<ol class="breadcrumb breadcrumb-arrows breadcrumb-muted mb-0">
|
||||
{breadcrumbs.map((item, index) =>
|
||||
index < breadcrumbs.length - 1 ? (
|
||||
<li class="breadcrumb-item">
|
||||
<a href={item.path}>{item.name}</a>
|
||||
</li>
|
||||
) : (
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
{item.name}
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ol>
|
||||
</nav>
|
||||
)}
|
||||
</ol>
|
||||
</nav>
|
||||
)}
|
||||
{markdownUrl && <DocsCopyMarkdownLink url={markdownUrl} absoluteUrl={canonicalMarkdownUrl} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Prose
|
||||
data-bs-spy={toc.length > 0 ? 'scroll' : undefined}
|
||||
@@ -371,14 +382,14 @@ const relatedPages = await Promise.all(
|
||||
data-bs-smooth-scroll={toc.length > 0 ? 'true' : undefined}
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<h1 class="docs-page-title">
|
||||
<div class="d-flex flex-column flex-sm-row align-items-sm-center gap-2">
|
||||
<h1 class="docs-page-title mb-0">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
{
|
||||
addedIn && (
|
||||
<div class="ms-auto">
|
||||
<div class="ms-sm-auto">
|
||||
<span class="badge bg-primary-lt">Added in {addedIn}</span>
|
||||
</div>
|
||||
)
|
||||
@@ -470,7 +481,7 @@ const relatedPages = await Promise.all(
|
||||
<!-- BEGIN DOCS TOC -->
|
||||
<div class="col-2 d-none d-xxl-block">
|
||||
<div class="py-5 sticky-top docs-toc-sticky">
|
||||
<DocsToc toc={toc} editUrl={`${site.githubUrl}/edit/dev/${editFilePath}`} sourceUrl={sourcePath ? `${site.githubUrl}/blob/dev/${sourcePath}` : undefined} markdownUrl={markdownUrl} />
|
||||
<DocsToc toc={toc} editUrl={`${site.githubUrl}/edit/dev/${editFilePath}`} sourceUrl={sourcePath ? `${site.githubUrl}/blob/dev/${sourcePath}` : undefined} />
|
||||
</div>
|
||||
</div>
|
||||
<!-- END DOCS TOC -->
|
||||
|
||||
+39
-14
@@ -5,9 +5,11 @@
|
||||
// component props. Stripping components wholesale — the usual llms.txt recipe —
|
||||
// would delete exactly that. So the components that hold content are unwrapped
|
||||
// into fenced code blocks instead, and only the decorative ones are dropped.
|
||||
// A component whose content is data rather than markup (<Colors />, <Flags />)
|
||||
// renders its own markdown in a comment, see shared/lib/markdown-source.ts.
|
||||
//
|
||||
// Prose comes from the MDX source, but example markup comes from the *rendered*
|
||||
// page (see renderedExamples below): a third of the examples are written with
|
||||
// page (see renderedPage below): a third of the examples are written with
|
||||
// docs components — <Icon>, <AvatarList>, <Badge> — and the source form of those
|
||||
// is of no use to a reader who wants the html.
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
@@ -16,6 +18,7 @@ import { loadRenderers } from 'astro:container'
|
||||
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
|
||||
import { getContainerRenderer } from '@astrojs/mdx/container-renderer'
|
||||
import { beautifyHtml, extractMarkedSnippet } from '@shared/lib/code-example'
|
||||
import { extractMarkdownSources } from '@shared/lib/markdown-source'
|
||||
import { site } from '@shared/lib/site'
|
||||
import { callouts } from '@components/callouts/index.ts'
|
||||
import docs from '@data/docs.json'
|
||||
@@ -137,19 +140,29 @@ let container: Promise<AstroContainer> | undefined
|
||||
|
||||
const getContainer = () => (container ??= loadRenderers([getContainerRenderer()]).then((renderers) => AstroContainer.create({ renderers })))
|
||||
|
||||
type RenderedPage = {
|
||||
/** final markup of every <Example>, in document order; null when the example renders nothing */
|
||||
examples: (string | null)[]
|
||||
/** markdown shipped by components via <MarkdownSource />, per component name */
|
||||
sources: Map<string, string[]>
|
||||
}
|
||||
|
||||
const EMPTY_PAGE: RenderedPage = { examples: [], sources: new Map() }
|
||||
|
||||
/**
|
||||
* The final markup of every <Example> on a page, in document order — the same
|
||||
* html the page's copy button hands out, so components are already expanded.
|
||||
* An entry is null only when the example renders nothing, and the whole list is
|
||||
* empty when the page cannot be rendered.
|
||||
* What the rendered page carries for the markdown mirror: the final markup of
|
||||
* every <Example> — the same html the page's copy button hands out, so
|
||||
* components are already expanded — and the markdown sources of data
|
||||
* components. Both lists are empty when the page cannot be rendered.
|
||||
*/
|
||||
async function renderedExamples(entry: CollectionEntry<'docs'>): Promise<(string | null)[]> {
|
||||
async function renderedPage(entry: CollectionEntry<'docs'>): Promise<RenderedPage> {
|
||||
try {
|
||||
const { Content } = await render(entry)
|
||||
// Same components the page route passes, or every page using a callout throws.
|
||||
const html = await (await getContainer()).renderToString(Content, { props: { components: callouts } })
|
||||
// The local tells <MarkdownSource /> this render is for the mirror.
|
||||
const html = await (await getContainer()).renderToString(Content, { props: { components: callouts }, locals: { markdownMirror: true } })
|
||||
|
||||
return html
|
||||
const examples = html
|
||||
.split(EXAMPLE_MARKER)
|
||||
.slice(1)
|
||||
.map((block) => {
|
||||
@@ -157,18 +170,21 @@ async function renderedExamples(entry: CollectionEntry<'docs'>): Promise<(string
|
||||
const markup = block.match(/data-clipboard-text="([^"]*)"/) ?? block.match(/data-example-markup="([^"]*)"/)
|
||||
return markup ? beautifyHtml(decodeEntities(markup[1]!)) : null
|
||||
})
|
||||
|
||||
return { examples, sources: extractMarkdownSources(html) }
|
||||
} catch (error) {
|
||||
console.warn(`[llms] Rendering ${entry.id} failed, examples fall back to their MDX source:`, error)
|
||||
return []
|
||||
return EMPTY_PAGE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn one page's MDX body into plain markdown. `examples` comes from
|
||||
* renderedExamples() and replaces the source of each <Example> slot; it is
|
||||
* ignored unless it lines up one-to-one with the examples in the source.
|
||||
* Turn one page's MDX body into plain markdown. `examples` and `sources` come
|
||||
* from renderedPage(): the examples replace the source of each <Example> slot,
|
||||
* the sources replace the tags of the components that shipped them. Either is
|
||||
* ignored unless it lines up one-to-one with the tags in the source.
|
||||
*/
|
||||
export async function mdxToMarkdown(body: string, examples: (string | null)[] = []): Promise<string> {
|
||||
export async function mdxToMarkdown(body: string, examples: (string | null)[] = [], sources: Map<string, string[]> = new Map()): Promise<string> {
|
||||
// `<Code lang code={`…`} />` first: its template literal contains backticks, which
|
||||
// would otherwise be mistaken for markdown code spans by protectCode() below.
|
||||
let text = body.replace(/<Code\b[^>]*?code=\{`([\s\S]*?)`\}[\s\S]*?\/>/g, (match, snippet: string) => {
|
||||
@@ -210,6 +226,14 @@ export async function mdxToMarkdown(body: string, examples: (string | null)[] =
|
||||
return `\n${fence(cdnPluginSnippet(plugins))}\n`
|
||||
})
|
||||
|
||||
// data components that shipped their own markdown (<Colors />, <Flags />, …)
|
||||
for (const [name, markdowns] of sources) {
|
||||
const tagPattern = new RegExp(`<${name}\\b[^>]*/>`, 'g')
|
||||
if ((text.match(tagPattern)?.length ?? 0) !== markdowns.length) continue
|
||||
let sourceIndex = 0
|
||||
text = text.replace(tagPattern, () => `\n${markdowns[sourceIndex++]}\n`)
|
||||
}
|
||||
|
||||
// the blocks just produced must be protected too, for the same reason
|
||||
text = protectCode(text, code)
|
||||
|
||||
@@ -229,7 +253,8 @@ async function buildPageMarkdown(entry: CollectionEntry<'docs'>, url: string): P
|
||||
const { title, summary, description } = entry.data
|
||||
const header = [`# ${title}`, '', `> ${summary}`, '', description, '', `Source: ${url}`, '', '---', ''].join('\n')
|
||||
|
||||
return `${header}\n${await mdxToMarkdown(entry.body ?? '', await renderedExamples(entry))}\n`
|
||||
const { examples, sources } = await renderedPage(entry)
|
||||
return `${header}\n${await mdxToMarkdown(entry.body ?? '', examples, sources)}\n`
|
||||
}
|
||||
|
||||
// [...slug].md.ts and llms-full.txt.ts render the same pages in one build.
|
||||
|
||||
+7
-3
@@ -369,9 +369,9 @@ header > .navbar {
|
||||
}
|
||||
}
|
||||
|
||||
// Page tools under the table of contents: copy, source, edit. One shape for all
|
||||
// three, so the button does not read as a different kind of control than the
|
||||
// links next to it.
|
||||
// Page tools under the table of contents: source, edit. One shape for both
|
||||
// links (copying the page as markdown moved to the "Copy to LLM" button above
|
||||
// the title, see DocsCopyMarkdownLink).
|
||||
.docs-page-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -606,6 +606,10 @@ html {
|
||||
}
|
||||
|
||||
// The table of contents sticks too, so it has to clear the header (#2421).
|
||||
// A long list must not run past the viewport either: the rail is capped at the
|
||||
// space below the header and scrolls on its own (#2965).
|
||||
.docs-toc-sticky {
|
||||
top: var(--docs-header-height);
|
||||
max-height: calc(100vh - var(--docs-header-height));
|
||||
overflow-y: auto;
|
||||
}
|
||||
+1
-2
@@ -12,8 +12,7 @@
|
||||
"test": "turbo test",
|
||||
"test:watch": "turbo test:watch",
|
||||
"test:coverage": "turbo test:coverage",
|
||||
"version": "changeset version && pnpm run stamp-changelog-dates",
|
||||
"stamp-changelog-dates": "tsx .build/stamp-changelog-dates.ts",
|
||||
"version": "changeset version",
|
||||
"publish": "changeset publish",
|
||||
"format:md": "tsx .build/reformat-mdx.mts",
|
||||
"html-diff": "tsx .build/html-diff.ts check",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# @tabler/preview
|
||||
|
||||
## 1.5.0 - 2026-09-05
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Flush</CardTitle>
|
||||
<CardSubtitle>Add <code>flush</code> to remove the card border and rounded corners.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>accordion-flush</code> to remove the card border and rounded corners.</CardSubtitle>
|
||||
</CardBody>
|
||||
<Accordion type={['flush']} id="flush" />
|
||||
</Card>
|
||||
@@ -34,7 +34,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Tabs</CardTitle>
|
||||
<CardSubtitle>Add <code>tabs</code> for a tab-like header instead of the default style.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>accordion-tabs</code> for a tab-like header instead of the default style.</CardSubtitle>
|
||||
<Accordion type={['tabs']} id="tabs" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -43,7 +43,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Inverted</CardTitle>
|
||||
<CardSubtitle>Add <code>inverted</code> to flip the toggle icon to the start of the header.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>accordion-inverted</code> to flip the toggle icon to the start of the header.</CardSubtitle>
|
||||
<Accordion type={['inverted']} id="inverted" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -52,7 +52,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Inverted with plus icon</CardTitle>
|
||||
<CardSubtitle>Combine <code>inverted</code> with a custom <code>toggleIcon</code>.</CardSubtitle>
|
||||
<CardSubtitle>Combine <code>accordion-inverted</code> with <code>accordion-button-toggle-plus</code> and a custom icon in the toggle.</CardSubtitle>
|
||||
<Accordion id="inverted-plus" type={['inverted']} toggleIcon="plus" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -61,7 +61,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>With icons</CardTitle>
|
||||
<CardSubtitle>Add <code>showIcon</code> to show an icon next to each panel's title.</CardSubtitle>
|
||||
<CardSubtitle>Add an <code>accordion-button-icon</code> next to each panel's title.</CardSubtitle>
|
||||
<Accordion id="icons" showIcon />
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -28,7 +28,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>With action</CardTitle>
|
||||
<CardSubtitle>Add a link with the <code>action</code> prop to give users something to do next.</CardSubtitle>
|
||||
<CardSubtitle>Add an <code>alert-action</code> link to give users something to do next.</CardSubtitle>
|
||||
<Alert showClose action="Link" color="danger" title="An error occurred!" />
|
||||
<Alert showClose action="Link" color="warning" title="Some information is missing!" />
|
||||
<Alert showClose action="Link" color="success" title="Completed successfully!" />
|
||||
@@ -40,7 +40,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Dismissible</CardTitle>
|
||||
<CardSubtitle>Add <code>showClose</code> to let users close the alert.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>alert-dismissible</code> and a <code>btn-close</code> button to let users close the alert.</CardSubtitle>
|
||||
<Alert showClose color="danger" title="An error occurred!" />
|
||||
<Alert showClose color="warning" title="Some information is missing!" />
|
||||
<Alert showClose color="success" title="Completed successfully!" />
|
||||
@@ -52,7 +52,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>With a description</CardTitle>
|
||||
<CardSubtitle>Add a <code>description</code> or a <code>list</code> when the title alone isn't enough context.</CardSubtitle>
|
||||
<CardSubtitle>Add an <code>alert-description</code> or an <code>alert-list</code> when the title alone isn't enough context.</CardSubtitle>
|
||||
<Alert showClose color="danger" title="Password does not meet requirements:" list={['Minimum 8 characters', 'Include a special character']} />
|
||||
<Alert showClose color="warning" title="Some information is missing!" description="This is a custom alert box with a description." />
|
||||
<Alert showClose color="success" title="Completed successfully!" description="This is a custom alert box with a description." />
|
||||
@@ -64,7 +64,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Important</CardTitle>
|
||||
<CardSubtitle>Add <code>important</code> for a bolder style that stands out from the rest of the page.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>alert-important</code> for a bolder style that stands out from the rest of the page.</CardSubtitle>
|
||||
<Alert showClose variant="important" color="danger" title="Password does not meet requirements:" list={['Minimum 8 characters', 'Include a special character']} />
|
||||
<Alert showClose variant="important" color="success" title="This is a custom alert box!" description="This is a custom alert box with a description." />
|
||||
<Alert showClose variant="important" color="warning" title="This is a custom alert box!" description="This is a custom alert box with a description." />
|
||||
@@ -78,7 +78,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Minor</CardTitle>
|
||||
<CardSubtitle>Add <code>minor</code> for a quieter style that fits inline with other content.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>alert-minor</code> for a quieter style that fits inline with other content.</CardSubtitle>
|
||||
<Alert showClose variant="minor" color="danger" title="Password does not meet requirements:" list={['Minimum 8 characters', 'Include a special character']} />
|
||||
<Alert showClose variant="minor" color="success" title="This is a custom alert box!" description="This is a custom alert box with a description." />
|
||||
<Alert showClose variant="minor" color="warning" title="This is a custom alert box!" description="This is a custom alert box with a description." />
|
||||
|
||||
@@ -42,7 +42,7 @@ const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Avatar with icon</CardTitle>
|
||||
<CardSubtitle>Use an icon instead of a photo with the <code>icon</code> prop.</CardSubtitle>
|
||||
<CardSubtitle>Put an icon inside the <code>avatar</code> instead of a photo.</CardSubtitle>
|
||||
<div class="avatar-list">
|
||||
{iconIcons.map((icon) => <Avatar icon={icon} />)}
|
||||
</div>
|
||||
@@ -64,7 +64,7 @@ const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Simple avatar</CardTitle>
|
||||
<CardSubtitle>Pass a <code>person</code> object to render their photo.</CardSubtitle>
|
||||
<CardSubtitle>Show a photo by setting it as the <code>background-image</code> of the avatar.</CardSubtitle>
|
||||
<div class="avatar-list">
|
||||
{people8.map((person) => <Avatar person={person} />)}
|
||||
</div>
|
||||
@@ -75,7 +75,7 @@ const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Avatar placeholder</CardTitle>
|
||||
<CardSubtitle>Fall back to initials with the <code>placeholder</code> prop when there's no photo.</CardSubtitle>
|
||||
<CardSubtitle>Fall back to initials as the avatar text when there's no photo.</CardSubtitle>
|
||||
<div class="avatar-list">
|
||||
{people8.map((person) => <Avatar placeholder={firstLetters(person.full_name ?? '')} />)}
|
||||
</div>
|
||||
@@ -99,7 +99,7 @@ const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Avatar sizes</CardTitle>
|
||||
<CardSubtitle>Six sizes from <code>xxs</code> to <code>xl</code>, for both photos and placeholders.</CardSubtitle>
|
||||
<CardSubtitle>Six sizes from <code>avatar-xxs</code> to <code>avatar-xl</code>, for both photos and placeholders.</CardSubtitle>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="avatar-list">
|
||||
@@ -119,7 +119,7 @@ const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Avatar lists</CardTitle>
|
||||
<CardSubtitle>Stack avatars with <code><AvatarList></code> to show a group at a glance.</CardSubtitle>
|
||||
<CardSubtitle>Stack avatars inside an <code>.avatar-list</code> container to show a group at a glance.</CardSubtitle>
|
||||
<div class="row g-3">
|
||||
{
|
||||
listSizes.map((size) => (
|
||||
@@ -160,7 +160,7 @@ const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Avatar statuses</CardTitle>
|
||||
<CardSubtitle>Add a <code>status</code> dot to show online, away, or busy state.</CardSubtitle>
|
||||
<CardSubtitle>Add a <code>badge</code> inside the avatar for an online, away, or busy status dot.</CardSubtitle>
|
||||
{statusColors.map((color, i) => <Avatar person={personById(i + 1)} class="rounded-circle" status={color} />)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -169,7 +169,7 @@ const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Avatar brands</CardTitle>
|
||||
<CardSubtitle>Show a recognizable brand icon instead of a person.</CardSubtitle>
|
||||
<CardSubtitle>Add a recognizable brand icon on top of an avatar.</CardSubtitle>
|
||||
{brands.map((brand, i) => <Avatar person={personById(i + 1)} brand={brand} />)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -83,7 +83,7 @@ const sizes = ['sm', 'md', 'lg', 'xl'] as const
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Sizes</CardTitle>
|
||||
<CardSubtitle>Scale a button from <code>sm</code> to <code>xl</code> without changing its icon layout.</CardSubtitle>
|
||||
<CardSubtitle>Scale a button from <code>btn-sm</code> to <code>btn-xl</code> without changing its icon layout.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
{
|
||||
sizes.map((size) => (
|
||||
|
||||
@@ -16,7 +16,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Two y-axes</CardTitle>
|
||||
<CardSubtitle>Plot values with different units together. Bind each axis to a serie with <code>seriesName</code> and move the second one to the right with <code>opposite</code>.</CardSubtitle>
|
||||
<CardSubtitle>Plot values with different units together. Bind each axis to a series with the <code>yaxis.seriesName</code> chart option and move the second one to the right with <code>opposite</code>.</CardSubtitle>
|
||||
<Chart chartId="revenue-vs-orders" height={16} label="Revenue in dollars and order count per month" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -10,27 +10,33 @@ import Progress from '@ui/Progress.astro'
|
||||
import Modal from '@shared/components/modals/Modal.astro'
|
||||
import CaptureModal from '@shared/components/CaptureModal.astro'
|
||||
import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import { formatShortDate } from '@shared/lib/date-format'
|
||||
import mails from '@data/mails.json'
|
||||
|
||||
// Label colors shared by the folder sidebar and the dots in the message list.
|
||||
const labelColors: Record<string, string> = { Updates: 'info', Friends: 'warning', Family: 'success', Social: 'primary', Important: 'danger', Promotions: 'purple' }
|
||||
---
|
||||
|
||||
<DefaultLayout title="Email inbox" pageHeader="Inbox" pageMenu="extra.email-inbox" pageLibs={['hugerte']}>
|
||||
<Card>
|
||||
<div class="row g-0">
|
||||
<div class="col-xxl-3 email-border border-end">
|
||||
<Offcanvas responsive="xxl" direction="start" class="h-100 file-offcanvas" tabindex="-1" id="emailSidebaroffcanvas">
|
||||
<div class="col-xxl-3 border-end">
|
||||
<Offcanvas responsive="xxl" direction="start" class="h-100" tabindex="-1" id="emailSidebaroffcanvas">
|
||||
<div class="card-body h-100">
|
||||
<div>
|
||||
<Button icon="pencil" text="Compose" color="primary" class="d-none d-sm-block" modalId="new-email" />
|
||||
<Button icon="pencil" text="Compose" color="primary" block class="d-none d-sm-block" modalId="new-email" />
|
||||
</div>
|
||||
|
||||
<div class="mt-3 nav nav-vertical">
|
||||
<a href="#" class="nav-link text-danger fw-bold">
|
||||
<div class="mt-3 nav nav-vertical nav-pills">
|
||||
<a href="#" class="nav-link active">
|
||||
<Icon name="inbox" class="me-2" />
|
||||
Inbox<span class="badge bg-danger ms-auto">{mails.length}</span>
|
||||
Inbox<span class="badge bg-primary-lt ms-auto">{mails.length}</span>
|
||||
</a>
|
||||
<a href="#" class="nav-link"><Icon name="star" class="me-2" />Starred</a>
|
||||
<a href="#" class="nav-link"><Icon name="clock" class="me-2" />Snoozed</a>
|
||||
<a href="#" class="nav-link"><Icon name="file" class="me-2" />Draft<span class="badge bg-info ms-auto">32</span></a>
|
||||
<a href="#" class="nav-link"><Icon name="file" class="me-2" />Draft<span class="badge bg-info-lt ms-auto">32</span></a>
|
||||
<a href="#" class="nav-link"><Icon name="mail-up" class="me-2" />Sent Mail</a>
|
||||
<a href="#" class="nav-link"><Icon name="trash" class="me-2" />Trash</a>
|
||||
<a href="#" class="nav-link"><Icon name="tag" class="me-2" />Important</a>
|
||||
@@ -39,25 +45,14 @@ import mails from '@data/mails.json'
|
||||
|
||||
<div class="mt-4">
|
||||
<Subheader as="h6">Labels</Subheader>
|
||||
<div class="mt-2 nav nav-vertical">
|
||||
<a href="#" class="nav-link">
|
||||
<div class="badge bg-info me-2"></div> Updates
|
||||
</a>
|
||||
<a href="#" class="nav-link">
|
||||
<div class="badge bg-warning me-2"></div> Friends
|
||||
</a>
|
||||
<a href="#" class="nav-link">
|
||||
<div class="badge bg-success me-2"></div> Family
|
||||
</a>
|
||||
<a href="#" class="nav-link">
|
||||
<div class="badge bg-primary me-2"></div> Social
|
||||
</a>
|
||||
<a href="#" class="nav-link">
|
||||
<div class="badge bg-danger me-2"></div> Important
|
||||
</a>
|
||||
<a href="#" class="nav-link">
|
||||
<div class="badge bg-purple me-2"></div> Promotions
|
||||
</a>
|
||||
<div class="mt-2 nav nav-vertical nav-pills">
|
||||
{
|
||||
Object.entries(labelColors).map(([label, color]) => (
|
||||
<a href="#" class="nav-link">
|
||||
<span class={`badge bg-${color} me-2`} /> {label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -112,12 +107,11 @@ import mails from '@data/mails.json'
|
||||
</div>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup>
|
||||
<ButtonGroup class="ms-auto">
|
||||
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<Icon name="dots" /> More
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<span class="dropdown-header">More Options :</span>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<a class="dropdown-item" href="#">Mark as Unread</a>
|
||||
<a class="dropdown-item" href="#">Add to Tasks</a>
|
||||
<a class="dropdown-item" href="#">Add Star</a>
|
||||
@@ -125,70 +119,66 @@ import mails from '@data/mails.json'
|
||||
</div>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<ul class="email-list">
|
||||
{
|
||||
mails && mails.length > 0 ? (
|
||||
mails.map((mail) => (
|
||||
<li>
|
||||
<div class="email-sender-info">
|
||||
<div class="checkbox-wrapper-mail">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id={`mail-${mail.id}`} />
|
||||
<label class="form-check-label" for={`mail-${mail.id}`} />
|
||||
</div>
|
||||
</div>
|
||||
<span class="star-toggle">
|
||||
<Icon name="star" />
|
||||
</span>
|
||||
<a href="#" class="email-title">
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<ListGroup flush hoverable>
|
||||
{
|
||||
mails.length > 0 ? (
|
||||
mails.map((mail) => (
|
||||
<ListGroupItem>
|
||||
<div class="row align-items-center g-2">
|
||||
<div class="col-auto">
|
||||
<input type="checkbox" class="form-check-input m-0" aria-label={`Select "${mail.subject}"`} />
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class:list={['list-group-item-actions', mail.starred && 'show']} aria-label={mail.starred ? 'Remove star' : 'Add star'}>
|
||||
<Icon name="star" color={mail.starred ? 'yellow' : 'muted'} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-3 col-lg-2 text-truncate">
|
||||
<a href="#" class="text-body">
|
||||
{mail.sender}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="email-content">
|
||||
<a href="#" class="email-subject">
|
||||
{mail.subject} –
|
||||
<span>{mail.preview}</span>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-body">
|
||||
{mail.subject}
|
||||
</a>
|
||||
<div class="email-date">{mail.date}</div>
|
||||
<span class="text-secondary"> – {mail.preview}</span>
|
||||
</div>
|
||||
|
||||
<div class="email-action-icons">
|
||||
<ul class="list-inline">
|
||||
<li class="list-inline-item">
|
||||
<a href="#" aria-label="Archive">
|
||||
<Icon name="archive" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#" aria-label="Delete">
|
||||
<Icon name="trash" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#" aria-label="Mark as read">
|
||||
<Icon name="mail-opened" class="email-action-icons-item" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#" aria-label="Snooze">
|
||||
<Icon name="clock" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="col-auto d-none d-lg-block">
|
||||
{mail.labels.map((label) => (
|
||||
<span class={`badge bg-${labelColors[label] ?? 'secondary'}`} title={label} aria-label={label} />
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li class="text-muted">No emails</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-auto d-none d-md-block">
|
||||
<div class="list-group-item-actions d-flex gap-2">
|
||||
<a href="#" class="text-secondary" aria-label="Archive">
|
||||
<Icon name="archive" />
|
||||
</a>
|
||||
<a href="#" class="text-secondary" aria-label="Delete">
|
||||
<Icon name="trash" />
|
||||
</a>
|
||||
<a href="#" class="text-secondary" aria-label="Mark as read">
|
||||
<Icon name="mail-opened" />
|
||||
</a>
|
||||
<a href="#" class="text-secondary" aria-label="Snooze">
|
||||
<Icon name="clock" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto text-secondary text-end">{formatShortDate(new Date(mail.date))}</div>
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))
|
||||
) : (
|
||||
<ListGroupItem class="text-secondary">No emails</ListGroupItem>
|
||||
)
|
||||
}
|
||||
</ListGroup>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-7 mt-1">
|
||||
Showing 1 - {mails.length} of {mails.length}
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Numbered</CardTitle>
|
||||
<CardSubtitle>Page numbers, with or without <code>text</code> Prev/Next labels.</CardSubtitle>
|
||||
<CardSubtitle>Page numbers, with or without Prev/Next text labels (<code>page-text</code>).</CardSubtitle>
|
||||
<Pagination count={5} activeItem={3} />
|
||||
<Pagination count={5} activeItem={3} text />
|
||||
</CardBody>
|
||||
@@ -26,7 +26,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>With descriptions</CardTitle>
|
||||
<CardSubtitle>Add <code>prevDescription</code>/<code>nextDescription</code> to name the adjacent page.</CardSubtitle>
|
||||
<CardSubtitle>Name the adjacent page with <code>page-item-subtitle</code> for the direction and <code>page-item-title</code> for the page name.</CardSubtitle>
|
||||
<Pagination count={0} prevDescription="Getting started" nextDescription="Breadcrumbs" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -59,7 +59,7 @@ const patterns = ['diagonal', 'diagonal-2', 'dots', 'rectangles', 'lines', 'line
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Pattern sizes</CardTitle>
|
||||
<CardSubtitle>Scale the pattern from <code>sm</code> to <code>xl</code>.</CardSubtitle>
|
||||
<CardSubtitle>Scale the pattern from <code>bg-pattern-sm</code> to <code>bg-pattern-xl</code>.</CardSubtitle>
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 row-cols-xl-6 g-3">
|
||||
{
|
||||
patternSizes.map((size) => (
|
||||
@@ -77,7 +77,7 @@ const patterns = ['diagonal', 'diagonal-2', 'dots', 'rectangles', 'lines', 'line
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Pattern opacity</CardTitle>
|
||||
<CardSubtitle>Scale the pattern strength with <code>lighter</code>, <code>light</code>, <code>dark</code>, or <code>darker</code>.</CardSubtitle>
|
||||
<CardSubtitle>Scale the pattern strength with the <code>bg-pattern-opacity-*</code> utilities.</CardSubtitle>
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 row-cols-xl-6 g-3">
|
||||
{
|
||||
patternOpacities.map((opacity) => (
|
||||
|
||||
@@ -34,7 +34,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>With value</CardTitle>
|
||||
<CardSubtitle>Show the exact percentage inside a larger bar with <code>showValue</code>.</CardSubtitle>
|
||||
<CardSubtitle>Show the exact percentage as the <code>progress-bar</code> text inside a larger bar.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Progress value={10} showValue size="lg" />
|
||||
<Progress value={20} showValue size="lg" />
|
||||
@@ -61,7 +61,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Sizes</CardTitle>
|
||||
<CardSubtitle>Scale a bar from <code>sm</code> to <code>xl</code>.</CardSubtitle>
|
||||
<CardSubtitle>Scale a bar from <code>progress-sm</code> to <code>progress-xl</code>.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Progress value={20} size="sm" />
|
||||
<Progress value={40} />
|
||||
@@ -75,7 +75,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Indeterminate</CardTitle>
|
||||
<CardSubtitle>Animate a bar with no known end point using <code>indeterminate</code>.</CardSubtitle>
|
||||
<CardSubtitle>Animate a bar with no known end point with <code>progress-bar-indeterminate</code>.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Progress indeterminate />
|
||||
</div>
|
||||
@@ -98,7 +98,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Striped</CardTitle>
|
||||
<CardSubtitle>Add diagonal stripes with <code>striped</code> for a textured look.</CardSubtitle>
|
||||
<CardSubtitle>Add diagonal stripes with <code>progress-bar-striped</code> for a textured look.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Progress color="blue" value={20} striped />
|
||||
<Progress color="green" value={40} striped />
|
||||
@@ -112,7 +112,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Animated</CardTitle>
|
||||
<CardSubtitle>Combine <code>striped</code> with <code>animated</code> to keep the stripes moving.</CardSubtitle>
|
||||
<CardSubtitle>Combine <code>progress-bar-striped</code> with <code>progress-bar-animated</code> to keep the stripes moving.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Progress value={20} striped animated />
|
||||
<Progress value={40} color="green" striped animated />
|
||||
@@ -241,7 +241,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle> Progress Description Sizes </CardTitle>
|
||||
<CardSubtitle>Scale the labeled progress bar from <code>sm</code> to <code>xl</code>.</CardSubtitle>
|
||||
<CardSubtitle>Scale the labeled progress bar from <code>progress-sm</code> to <code>progress-xl</code>.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<ProgressDescription label="Small progress" value={60} size="sm" color="blue" />
|
||||
<ProgressDescription label="Default progress" value={70} color="green" />
|
||||
|
||||
@@ -16,7 +16,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Basic</CardTitle>
|
||||
<CardSubtitle>A list of <code>items</code> is enough for a plain control.</CardSubtitle>
|
||||
<CardSubtitle>A <code>nav-segmented</code> with text <code>nav-link</code> items is enough for a plain control.</CardSubtitle>
|
||||
<NavSegmented items={['1', '2', '3', '4']} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -34,7 +34,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Icons only</CardTitle>
|
||||
<CardSubtitle>Pass <code>icons</code> without <code>items</code> for an icon-only control.</CardSubtitle>
|
||||
<CardSubtitle>Put only an icon in each <code>nav-link</code> for an icon-only control.</CardSubtitle>
|
||||
<NavSegmented icons={['home', 'star', 'clock', 'ghost', 'bold', 'italic', 'underline']} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -43,7 +43,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>With icons</CardTitle>
|
||||
<CardSubtitle>Combine <code>items</code> and <code>icons</code> to label each option.</CardSubtitle>
|
||||
<CardSubtitle>Combine an icon and text in each <code>nav-link</code> to label each option.</CardSubtitle>
|
||||
<NavSegmented items={['List', 'Kanban', 'Calendar', 'Files']} icons={['list', 'layout', 'calendar', 'files']} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -52,7 +52,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Vertical</CardTitle>
|
||||
<CardSubtitle>Add <code>vertical</code> to stack the options in a column.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>nav-segmented-vertical</code> to stack the options in a column.</CardSubtitle>
|
||||
<NavSegmented items={['List', 'Kanban', 'Calendar', 'Files']} icons={['list', 'layout', 'calendar', 'files']} vertical />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -61,7 +61,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Vertical with disabled items</CardTitle>
|
||||
<CardSubtitle>Pass indexes to <code>disabled</code> to make specific options unselectable.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>disabled</code> to a <code>nav-link</code> to make that option unselectable.</CardSubtitle>
|
||||
<NavSegmented icons={['home', 'star', 'clock', 'ghost', 'bold', 'italic', 'underline']} vertical disabled={[3]} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -88,7 +88,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Independent group</CardTitle>
|
||||
<CardSubtitle>Set a unique <code>name</code> so this control doesn't share selection state with others using the same items.</CardSubtitle>
|
||||
<CardSubtitle>Use radio inputs with a unique <code>name</code> so this control doesn't share selection state with others using the same items.</CardSubtitle>
|
||||
<NavSegmented items={['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly']} name="checkbox" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -97,7 +97,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Full width</CardTitle>
|
||||
<CardSubtitle>Add <code>block</code> to stretch the control across its container.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>w-100</code> to stretch the control across its container.</CardSubtitle>
|
||||
<NavSegmented items={['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Yearly']} block={true} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -118,7 +118,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Sizes</CardTitle>
|
||||
<CardSubtitle>Set <code>size</code> to <code>sm</code> or <code>lg</code> — with text, icons, or both.</CardSubtitle>
|
||||
<CardSubtitle>Add <code>nav-sm</code> or <code>nav-lg</code> to change the size — with text, icons, or both.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<div><NavSegmented items={['List', 'Kanban', 'Calendar', 'Files']} disabled={[3, 5]} size="sm" /></div>
|
||||
<div><NavSegmented items={['List', 'Kanban', 'Calendar', 'Files']} disabled={[3, 5]} /></div>
|
||||
|
||||
@@ -25,7 +25,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Icons</CardTitle>
|
||||
<CardSubtitle>Swap the star for any icon with the <code>icon</code> prop.</CardSubtitle>
|
||||
<CardSubtitle>Swap the star for any icon by passing a different SVG to the <code>stars</code> option.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Rating id="icon-star" value={4} />
|
||||
<Rating id="icon-heart" icon="heart" value={4} color="red" />
|
||||
@@ -39,7 +39,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Sizes</CardTitle>
|
||||
<CardSubtitle>Match the rating to surrounding text with <code>size</code>.</CardSubtitle>
|
||||
<CardSubtitle>Match the rating to surrounding text with an <code>icon-sm</code> or <code>icon-lg</code> class on the star.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Rating id="size-sm" value={3} size="sm" />
|
||||
<Rating id="size-md" value={3} size="md" />
|
||||
@@ -52,7 +52,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Colors</CardTitle>
|
||||
<CardSubtitle>Set <code>color</code> to any theme or extended color to match the rest of the page.</CardSubtitle>
|
||||
<CardSubtitle>Color the star with a <code>text-*</code> class, any theme or extended color, to match the rest of the page.</CardSubtitle>
|
||||
<div class="space-y">
|
||||
<Rating id="color-default" value={3} />
|
||||
<Rating id="color-primary" color="primary" value={3} />
|
||||
|
||||
@@ -35,7 +35,7 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
<CardHeader>
|
||||
<div>
|
||||
<CardTitle>Striped rows</CardTitle>
|
||||
<CardSubtitle>Alternate row shading with <code>striped</code> to make wide tables easier to scan.</CardSubtitle>
|
||||
<CardSubtitle>Alternate row shading with <code>table-striped</code> to make wide tables easier to scan.</CardSubtitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<Table card={true} striped={true} offset={5} />
|
||||
|
||||
@@ -101,7 +101,7 @@ const colors = Object.values(siteData.colors) as { class: string; title: string
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Selectable tags</CardTitle>
|
||||
<CardSubtitle>Add <code>checkbox</code> to let users toggle a tag on and off.</CardSubtitle>
|
||||
<CardSubtitle>Add a <code>tag-check</code> checkbox to let users toggle a tag on and off.</CardSubtitle>
|
||||
<TagList>
|
||||
{range(1, 6).map((i) => <Tag text={`Label ${i}`} checkbox={true} />)}
|
||||
{range(7, 12).map((i) => <Tag text={`Label ${i}`} checkbox={true} checked={true} />)}
|
||||
@@ -114,7 +114,7 @@ const colors = Object.values(siteData.colors) as { class: string; title: string
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Tags with badge</CardTitle>
|
||||
<CardSubtitle>Add a <code>badge</code> count to the end of the tag.</CardSubtitle>
|
||||
<CardSubtitle>Add a <code>tag-badge</code> count to the end of the tag.</CardSubtitle>
|
||||
<TagList>
|
||||
{range(1, 12).map((i) => <Tag text="Label" badge={i} />)}
|
||||
</TagList>
|
||||
|
||||
@@ -10,8 +10,9 @@ interface Props {
|
||||
|
||||
const { title, type = 'outline' } = Astro.props
|
||||
|
||||
// Dev preview limits icons to 20 (see BaseLayout).
|
||||
const limit = 20
|
||||
// The dev server renders only the first 20 icons so the page stays fast;
|
||||
// `astro build` (the zip package and production site) renders all of them.
|
||||
const limit = import.meta.env.DEV ? 20 : Infinity
|
||||
|
||||
const entries = Object.entries(icons as Record<string, { svg?: Record<string, string | null> }>)
|
||||
.filter(([, icon]) => icon.svg?.[type])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { formatCommitDate, formatLongDate, formatUtcTimestamp, toUnixSeconds } from './date-format'
|
||||
import { formatCommitDate, formatShortDate, formatLongDate, formatUtcTimestamp, toUnixSeconds } from './date-format'
|
||||
|
||||
describe('formatCommitDate', () => {
|
||||
it('reformats a full timestamp to "D Mon YYYY"', () => {
|
||||
@@ -36,3 +36,10 @@ describe('formatUtcTimestamp', () => {
|
||||
expect(formatUtcTimestamp(date)).toBe('2025-11-28 08:05 +0000')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatShortDate', () => {
|
||||
it('formats the UTC day and short month', () => {
|
||||
expect(formatShortDate(new Date('2025-08-27T11:49:00Z'))).toBe('27 Aug')
|
||||
expect(formatShortDate(new Date('2025-01-01T00:30:00Z'))).toBe('1 Jan')
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
// Demo data date formatting quirks:
|
||||
// - unix seconds are shifted by local timezone offset (not the true epoch)
|
||||
// - long-date format uses UTC wall-clock components
|
||||
const SHORT_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
||||
|
||||
/** Unix seconds shifted by the local timezone offset (not the true epoch). */
|
||||
@@ -13,6 +14,11 @@ export function formatLongDate(date: Date): string {
|
||||
return `${MONTHS[date.getUTCMonth()]} ${String(date.getUTCDate()).padStart(2, '0')}, ${date.getUTCFullYear()}`
|
||||
}
|
||||
|
||||
/** Formats a date as "D Mon" using its UTC components, e.g. "27 Aug". */
|
||||
export function formatShortDate(date: Date): string {
|
||||
return `${date.getUTCDate()} ${SHORT_MONTHS[date.getUTCMonth()]}`
|
||||
}
|
||||
|
||||
/** Reformats a commit-style timestamp ("Thu Nov 28 08:48:33 2025 +0100") to "28 Nov 2025". */
|
||||
export function formatCommitDate(date: string): string {
|
||||
const m = date.match(/^\w+ (\w+) (\d+) [\d:]+ (\d+)/)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { extractMarkdownSources, markdownSource, markdownTable } from './markdown-source'
|
||||
|
||||
describe('markdownSource', () => {
|
||||
it('wraps the markdown in a named comment', () => {
|
||||
expect(markdownSource('Colors', '| a |\n')).toBe('<!--MARKDOWN:Colors-->\n| a |\n<!--/MARKDOWN-->')
|
||||
})
|
||||
|
||||
it('keeps a "-->" in the markdown from closing the comment', () => {
|
||||
const html = markdownSource('Arrows', 'a --> b')
|
||||
expect(html.indexOf('-->')).toBe(html.indexOf('<!--MARKDOWN:Arrows-->') + '<!--MARKDOWN:Arrows'.length)
|
||||
expect(extractMarkdownSources(html).get('Arrows')).toEqual(['a --> b'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractMarkdownSources', () => {
|
||||
it('collects sources per component name in document order', () => {
|
||||
const html = `<p>x</p>${markdownSource('Colors', 'first')}<div>${markdownSource('Flags', 'flags')}</div>${markdownSource('Colors', 'second')}`
|
||||
const sources = extractMarkdownSources(html)
|
||||
expect(sources.get('Colors')).toEqual(['first', 'second'])
|
||||
expect(sources.get('Flags')).toEqual(['flags'])
|
||||
})
|
||||
|
||||
it('returns an empty map for a page without sources', () => {
|
||||
expect(extractMarkdownSources('<p>nothing</p>').size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('markdownTable', () => {
|
||||
it('renders a header row, a separator and the rows', () => {
|
||||
expect(markdownTable(['Name', 'Class'], [['Blue', '`.bg-blue`']])).toBe('| Name | Class |\n| --- | --- |\n| Blue | `.bg-blue` |')
|
||||
})
|
||||
|
||||
it('escapes pipes inside cells', () => {
|
||||
expect(markdownTable(['a'], [['x | y']])).toContain('| x \\| y |')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
// A docs component whose output no MDX-to-markdown conversion could rebuild
|
||||
// (a colour palette, a table built from a data file) can ship its own markdown
|
||||
// next to its html, inside an html comment. docs/lib/llms.ts reads those
|
||||
// comments out of the rendered page and puts each one where the component
|
||||
// sits in the MDX source, so the .md mirror keeps the content instead of a gap.
|
||||
// The component renders the comment only for that mirror render (it checks
|
||||
// Astro.locals.markdownMirror), so the html a browser gets never carries it.
|
||||
|
||||
const OPEN = '<!--MARKDOWN:'
|
||||
const CLOSE = '<!--/MARKDOWN-->'
|
||||
const SOURCE_PATTERN = /<!--MARKDOWN:([A-Za-z]+)-->\n?([\s\S]*?)\n?<!--\/MARKDOWN-->/g
|
||||
|
||||
/** The comment a component renders; `name` must match the tag used in the MDX (`<Colors />` → "Colors"). */
|
||||
export function markdownSource(name: string, markdown: string): string {
|
||||
// "-->" inside the markdown would end the comment early
|
||||
return `${OPEN}${name}-->\n${markdown.trim().replaceAll('-->', '-->')}\n${CLOSE}`
|
||||
}
|
||||
|
||||
/** Every markdown source in a rendered page, per component name, in document order. */
|
||||
export function extractMarkdownSources(html: string): Map<string, string[]> {
|
||||
const sources = new Map<string, string[]>()
|
||||
for (const [, name, markdown] of html.matchAll(SOURCE_PATTERN)) {
|
||||
const list = sources.get(name!) ?? []
|
||||
list.push(markdown!.replaceAll('-->', '-->'))
|
||||
sources.set(name!, list)
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
/** A GitHub-flavoured markdown table; pipes inside cells are escaped. */
|
||||
export function markdownTable(headers: string[], rows: string[][]): string {
|
||||
const line = (cells: string[]) => `| ${cells.map((cell) => cell.replaceAll('|', '\\|')).join(' | ')} |`
|
||||
return [line(headers), line(headers.map(() => '---')), ...rows.map(line)].join('\n')
|
||||
}
|
||||
Reference in new issue
Block a user