mirror of
https://github.com/tabler/tabler.git
synced 2026-08-30 13:51:28 +04:00
Render Modal as real markup; drain scripts via CaptureScript (#2742)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -65,16 +65,14 @@ additively instead of creating parallel variants.
|
||||
|
||||
## Page scripts and modals
|
||||
|
||||
- Register per-page scripts with `addPageScript()` and modals with
|
||||
`addPageModal()` (`@shared/lib/page-scripts.ts` / `page-modals.ts`).
|
||||
- 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`).
|
||||
- Script-emitting components also render `<InlineScript code={script} />`.
|
||||
Its behavior is chosen per package by the vite define
|
||||
`import.meta.env.INLINE_PAGE_SCRIPTS`: preview drains registered scripts at
|
||||
the end of the page; docs inlines them next to the example.
|
||||
`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>`).
|
||||
|
||||
@@ -34,7 +34,7 @@ export default defineConfig({
|
||||
},
|
||||
integrations: [mdx()],
|
||||
markdown: {
|
||||
// markdown-it in Eleventy did not produce typographic quotes — neither do we
|
||||
// No typographic quote rewriting.
|
||||
processor: satteri({ features: { smartPunctuation: false } }),
|
||||
shikiConfig: {
|
||||
theme: 'github-dark',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
// Equivalent of docs/logo.html — the Tabler logo with wordmark (120x32), pasted 1:1.
|
||||
// Tabler logo with wordmark (120x32).
|
||||
---
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="32" fill="none">
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
---
|
||||
// Equivalent of docs/menu.html. In Eleventy the menu is built from `collections.docs |
|
||||
// collection-tree` (the tree of content/**/*.md files of the @tabler/docs app); in the PoC
|
||||
// the tree is frozen in shared/data/docs.json (extracted from the reference
|
||||
// development docs build).
|
||||
|
||||
import Subheader from '@ui/Subheader.astro'
|
||||
import docs from '@data/docs.json'
|
||||
import { pathSlug as slug } from '@shared/lib/string-format'
|
||||
// Menu tree from shared/data/docs.json.
|
||||
import Subheader from '@ui/Subheader.astro';
|
||||
import docs from '@data/docs.json';
|
||||
import { pathSlug as slug } from '@shared/lib/string-format';
|
||||
|
||||
interface MenuLeaf {
|
||||
title: string
|
||||
url: string
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface MenuGroup extends MenuLeaf {
|
||||
children?: MenuLeaf[]
|
||||
children?: MenuLeaf[];
|
||||
}
|
||||
|
||||
interface MenuSection {
|
||||
title: string
|
||||
children?: MenuGroup[]
|
||||
title: string;
|
||||
children?: MenuGroup[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** Page URL in the docs namespace (equivalent of page.url), e.g. "/ui/components/alert/". */
|
||||
url: string
|
||||
url: string;
|
||||
}
|
||||
|
||||
const { url } = Astro.props
|
||||
const menu = docs.menu as MenuSection[]
|
||||
const { url } = Astro.props;
|
||||
const menu = docs.menu as MenuSection[];
|
||||
---
|
||||
|
||||
<!-- BEGIN DOCS MENU -->
|
||||
@@ -40,15 +36,21 @@ const menu = docs.menu as MenuSection[]
|
||||
{level1.children && level1.children.length > 0 && (
|
||||
<nav class="nav nav-vertical">
|
||||
{level1.children.map((level2) => {
|
||||
const hasChildren = Boolean(level2.children && level2.children.length > 0)
|
||||
const hasChildren = Boolean(level2.children && level2.children.length > 0);
|
||||
// Expanded only for the group's own page or one of its direct children —
|
||||
// a prefix check would also expand "Introduction" on /ui/getting-started/frameworks/* pages.
|
||||
const expanded = url === level2.url || Boolean(level2.children?.some((child) => child.url === url))
|
||||
const expanded = url === level2.url || Boolean(level2.children?.some((child) => child.url === url));
|
||||
return (
|
||||
<div>
|
||||
{/* as in Liquid: href/data-bs-* only when the element has children */}
|
||||
{/* href/data-bs-* only when the element has children */}
|
||||
{hasChildren ? (
|
||||
<a class={`nav-link${expanded ? ' active' : ''}`} href={level2.url} data-bs-toggle="collapse" data-bs-target={`#collapse-${slug(level2.url)}`} aria-expanded={expanded ? 'true' : 'false'}>
|
||||
<a
|
||||
class={`nav-link${expanded ? ' active' : ''}`}
|
||||
href={level2.url}
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target={`#collapse-${slug(level2.url)}`}
|
||||
aria-expanded={expanded ? 'true' : 'false'}
|
||||
>
|
||||
{level2.title} <span class="nav-link-toggle" />
|
||||
</a>
|
||||
) : (
|
||||
@@ -56,10 +58,16 @@ const menu = docs.menu as MenuSection[]
|
||||
)}
|
||||
|
||||
{hasChildren && (
|
||||
<nav class={`nav nav-vertical collapse${expanded ? ' show' : ''}`} id={`collapse-${slug(level2.url)}`}>
|
||||
<nav
|
||||
class={`nav nav-vertical collapse${expanded ? ' show' : ''}`}
|
||||
id={`collapse-${slug(level2.url)}`}
|
||||
>
|
||||
{level2.children!.map((level3) => (
|
||||
<div>
|
||||
<a class={`nav-link${url === level3.url ? ' active' : ''}`} href={level3.url}>
|
||||
<a
|
||||
class={`nav-link${url === level3.url ? ' active' : ''}`}
|
||||
href={level3.url}
|
||||
>
|
||||
{level3.title}
|
||||
</a>
|
||||
</div>
|
||||
@@ -67,7 +75,7 @@ const menu = docs.menu as MenuSection[]
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
---
|
||||
// Equivalent of docs/navbar.html.
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import DocsLogo from './DocsLogo.astro'
|
||||
import { site } from '@shared/lib/site'
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import DocsLogo from './DocsLogo.astro';
|
||||
import { site } from '@shared/lib/site';
|
||||
|
||||
// Eventually belongs in src/lib/site.ts (out of scope for this task).
|
||||
const previewUrl = 'https://preview.tabler.io'
|
||||
const changelogUrl = 'https://tabler.io/changelog'
|
||||
const previewUrl = 'https://preview.tabler.io';
|
||||
const changelogUrl = 'https://tabler.io/changelog';
|
||||
---
|
||||
|
||||
<!-- BEGIN DOCS NAVBAR -->
|
||||
@@ -15,7 +14,7 @@ const changelogUrl = 'https://tabler.io/changelog'
|
||||
<div class="row flex-fill align-items-md-center">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-center gap-4">
|
||||
{/* href=".": Liquid `page.url | relative` receives a string, so the filter always returns "." */}
|
||||
{/* href="." — relative link for docs root */}
|
||||
<a href="." class="navbar-brand navbar-brand-autodark gap-4">
|
||||
<DocsLogo />
|
||||
</a>
|
||||
|
||||
@@ -1,72 +1,77 @@
|
||||
---
|
||||
// Equivalent of docs/pagination.html: child-page cards or next/prev siblings.
|
||||
import DocsCard from './DocsCard.astro'
|
||||
import docs from '@data/docs.json'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import { getDocsChildren } from '@shared/lib/docs-children'
|
||||
import type { DocsChildPage } from '@shared/lib/docs-children'
|
||||
import DocsCard from './DocsCard.astro';
|
||||
import docs from '@data/docs.json';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import { getDocsChildren } from '@shared/lib/docs-children';
|
||||
import type { DocsChildPage } from '@shared/lib/docs-children';
|
||||
|
||||
interface MenuLeaf {
|
||||
title: string
|
||||
url: string
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface MenuNode {
|
||||
title: string
|
||||
url?: string
|
||||
children?: MenuNode[]
|
||||
title: string;
|
||||
url?: string;
|
||||
children?: MenuNode[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** Page URL in the docs namespace (equivalent of page.url), e.g. "/ui/components/alert/". */
|
||||
url: string
|
||||
url: string;
|
||||
}
|
||||
|
||||
const { url } = Astro.props
|
||||
const { url } = Astro.props;
|
||||
|
||||
const children = getDocsChildren(url)
|
||||
const children = getDocsChildren(url);
|
||||
|
||||
type Pagination = {
|
||||
prev: MenuLeaf | null
|
||||
next: MenuLeaf | null
|
||||
found: boolean
|
||||
}
|
||||
prev: MenuLeaf | null;
|
||||
next: MenuLeaf | null;
|
||||
found: boolean;
|
||||
};
|
||||
|
||||
function findPage(nodes: MenuNode[]): Pagination | null {
|
||||
const index = nodes.findIndex((item) => item.url === url)
|
||||
const index = nodes.findIndex((item) => item.url === url);
|
||||
|
||||
if (index !== -1) {
|
||||
const previousNode = index > 0 ? nodes[index - 1] : null
|
||||
const nextNode = index < nodes.length - 1 ? nodes[index + 1] : null
|
||||
const previousNode = index > 0 ? nodes[index - 1] : null;
|
||||
const nextNode = index < nodes.length - 1 ? nodes[index + 1] : null;
|
||||
|
||||
return {
|
||||
prev: previousNode?.url ? { title: previousNode.title, url: previousNode.url } : null,
|
||||
next: nextNode?.url ? { title: nextNode.title, url: nextNode.url } : null,
|
||||
found: true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
const result = node.children ? findPage(node.children) : null
|
||||
if (result) return result
|
||||
const result = node.children ? findPage(node.children) : null;
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const { prev, next, found } = findPage(docs.menu as MenuNode[]) ?? {
|
||||
prev: null,
|
||||
next: null,
|
||||
found: false,
|
||||
}
|
||||
};
|
||||
---
|
||||
|
||||
<!-- BEGIN DOCS PAGINATION -->{
|
||||
<!-- BEGIN DOCS PAGINATION -->
|
||||
{
|
||||
children.length > 0 && (
|
||||
<div class="mt-6 pt-6">
|
||||
<div class="row row-deck row-cards">
|
||||
{children.map((child: DocsChildPage) => (
|
||||
<DocsCard href={child.url} title={child.title} description={child.description} icon={child.icon} />
|
||||
<DocsCard
|
||||
href={child.url}
|
||||
title={child.title}
|
||||
description={child.description}
|
||||
icon={child.icon}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
---
|
||||
// Equivalent of docs/toc.html. In Eleventy the TOC is computed by the `content | toc`
|
||||
// filter (h2/h3 headings from the markdown content, excluding <!--EXAMPLE-->...<!--/EXAMPLE--> blocks);
|
||||
// here it comes in as a prop from the consuming page.
|
||||
// TOC comes in as a prop from the consuming page (h2/h3 headings from the
|
||||
// markdown content, excluding <!--EXAMPLE-->...<!--/EXAMPLE--> blocks).
|
||||
|
||||
export interface TocItem {
|
||||
/** 2 or 3 (h2/h3); h3 gets the ms-3 indent */
|
||||
level: number
|
||||
text: string
|
||||
level: number;
|
||||
text: string;
|
||||
/** anchor without '#', e.g. "default-markup" */
|
||||
id: string
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
toc?: TocItem[]
|
||||
toc?: TocItem[];
|
||||
}
|
||||
|
||||
const { toc = [] } = Astro.props
|
||||
const { toc = [] } = Astro.props;
|
||||
|
||||
// Equivalent of `illustrations | size` (shared/data/illustrations.json has 100 entries).
|
||||
const illustrationsCount = 100
|
||||
// shared/data/illustrations.json has 100 entries.
|
||||
const illustrationsCount = 100;
|
||||
---
|
||||
|
||||
<!-- BEGIN DOCS TOC -->{
|
||||
<!-- BEGIN DOCS TOC -->
|
||||
{
|
||||
toc.length > 0 && (
|
||||
<Fragment>
|
||||
<h3>Table of Contents</h3>
|
||||
|
||||
@@ -1,54 +1,84 @@
|
||||
---
|
||||
// copy button with data-clipboard-text (equivalent of escape_attribute).
|
||||
// The example code comes from the `html` prop or from the rendered slot.
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import { beautifyHtml, highlightCode, removeHref } from '@shared/lib/code-example'
|
||||
// Example code from the `html` prop or the rendered slot; copy button uses data-clipboard-text.
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import { beautifyHtml, highlightCode, removeHref } from '@shared/lib/code-example';
|
||||
|
||||
interface Props {
|
||||
/** raw HTML of the example; when absent — the slot is rendered */
|
||||
html?: string
|
||||
/** code shown in the panel instead of html (the code param in Liquid) */
|
||||
code?: string
|
||||
raw?: boolean
|
||||
overflow?: string
|
||||
bg?: string
|
||||
class?: string
|
||||
height?: string
|
||||
column?: boolean
|
||||
centered?: boolean
|
||||
vertical?: boolean
|
||||
columnFullWidth?: boolean
|
||||
hideCode?: boolean
|
||||
codeOnly?: boolean
|
||||
html?: string;
|
||||
/** code shown in the panel instead of html */
|
||||
code?: string;
|
||||
raw?: boolean;
|
||||
overflow?: string;
|
||||
bg?: string;
|
||||
class?: string;
|
||||
height?: string;
|
||||
column?: boolean;
|
||||
centered?: boolean;
|
||||
vertical?: boolean;
|
||||
columnFullWidth?: boolean;
|
||||
hideCode?: boolean;
|
||||
codeOnly?: boolean;
|
||||
}
|
||||
|
||||
const { html: htmlProp, code, raw, overflow = 'auto', bg, class: className, height, column, centered, vertical, columnFullWidth, hideCode, codeOnly } = Astro.props
|
||||
const {
|
||||
html: htmlProp,
|
||||
code,
|
||||
raw,
|
||||
overflow = 'auto',
|
||||
bg,
|
||||
class: className,
|
||||
height,
|
||||
column,
|
||||
centered,
|
||||
vertical,
|
||||
columnFullWidth,
|
||||
hideCode,
|
||||
codeOnly,
|
||||
} = Astro.props;
|
||||
|
||||
// equivalent of {% removeemptylines %} around include output (e.g. ui/alert.html)
|
||||
let html = (htmlProp ?? (await Astro.slots.render('default'))).replace(/^\s*[\r\n]/gm, '')
|
||||
// Strip empty lines from the example HTML.
|
||||
let html = (htmlProp ?? (await Astro.slots.render('default'))).replace(/^\s*[\r\n]/gm, '');
|
||||
|
||||
// JSX reserializes the slot markup: empty SVG elements get explicit closing
|
||||
// tags, and apostrophes in text are escaped. We restore the source form so the
|
||||
// code in the panel looks like hand-written HTML.
|
||||
html = html.replace(/<(path|circle|line|polyline|polygon|rect|ellipse)([^>]*?)\s*><\/\1>/g, '<$1$2 />').replace(/'/g, "'")
|
||||
html = html
|
||||
.replace(/<(path|circle|line|polyline|polygon|rect|ellipse)([^>]*?)\s*><\/\1>/g, '<$1$2 />')
|
||||
.replace(/'/g, "'");
|
||||
|
||||
const exampleClasses = ['example fs-base border rounded my-5', !raw && 'd-flex flex-wrap justify-content-center', `overflow-${overflow}`, 'position-relative', bg ? `bg-${bg}` : 'bg-pattern-rectangles', className]
|
||||
const exampleClasses = [
|
||||
'example fs-base border rounded my-5',
|
||||
!raw && 'd-flex flex-wrap justify-content-center',
|
||||
`overflow-${overflow}`,
|
||||
'position-relative',
|
||||
bg ? `bg-${bg}` : 'bg-pattern-rectangles',
|
||||
className,
|
||||
]
|
||||
|
||||
const innerClasses = ['p-6 w-full', column && 'd-flex gap-3 flex-column', !column && centered && `d-flex flex-fill flex-wrap gap-2 justify-content-center${vertical ? ' align-items-center flex-column' : ' justify-content-center'}`, columnFullWidth && 'd-flex flex-fill flex-column gap-2']
|
||||
const innerClasses = [
|
||||
'p-6 w-full',
|
||||
column && 'd-flex gap-3 flex-column',
|
||||
!column && centered && `d-flex flex-fill flex-wrap gap-2 justify-content-center${vertical ? ' align-items-center flex-column' : ' justify-content-center'}`,
|
||||
columnFullWidth && 'd-flex flex-fill flex-column gap-2',
|
||||
]
|
||||
|
||||
const highlighted = hideCode ? '' : await highlightCode(beautifyHtml(code ?? html), 'html')
|
||||
const highlighted = hideCode ? '' : await highlightCode(beautifyHtml(code ?? html), 'html');
|
||||
---
|
||||
|
||||
<!--EXAMPLE-->{
|
||||
<!--EXAMPLE-->
|
||||
{
|
||||
!codeOnly && (
|
||||
<div class:list={exampleClasses} style={height ? `height: ${height}` : undefined}>
|
||||
{raw ? (
|
||||
{
|
||||
raw ? (
|
||||
<Fragment set:html={removeHref(html)} />
|
||||
) : (
|
||||
<div class:list={innerClasses} style={column ? 'max-width: 25rem;' : undefined}>
|
||||
<Fragment set:html={removeHref(html)} />
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -56,7 +86,6 @@ const highlighted = hideCode ? '' : await highlightCode(beautifyHtml(code ?? htm
|
||||
{
|
||||
!hideCode && (
|
||||
<div class="position-relative">
|
||||
{/* Astro escapes the attribute value itself (equivalent of escape_attribute) */}
|
||||
<a class="btn btn-icon btn-dark position-absolute m-2 top-0 end-0 z-3" data-clipboard-text={html}>
|
||||
<Icon name="clipboard" />
|
||||
<Icon name="check" class="d-none" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
// Docs layout — port of shared/layouts/docs/default.html (the @tabler/docs app).
|
||||
// Docs layout for the @tabler/docs app.
|
||||
// Standalone layout with its own <head>; does NOT use BaseLayout.astro.
|
||||
// Development uses the unminified assets; production additionally emits the SEO metadata.
|
||||
import { site } from '@shared/lib/site';
|
||||
@@ -12,6 +12,7 @@ import DocsToc from '@components/DocsToc.astro';
|
||||
import type { TocItem } from '@components/DocsToc.astro';
|
||||
import DocsPagination from '@components/DocsPagination.astro';
|
||||
import Prose from '@ui/Prose.astro';
|
||||
import PageScripts from '@shared/components/PageScripts.astro';
|
||||
|
||||
interface Props {
|
||||
/** front matter: the page's h1 heading */
|
||||
@@ -26,16 +27,16 @@ interface Props {
|
||||
seoDescription?: string;
|
||||
/** front matter `added-in`: renders the "Added in X" badge next to the h1 */
|
||||
addedIn?: string;
|
||||
/** table of contents (h2/h3 from the markdown content) — see DocsToc.astro */
|
||||
/** Table of contents (h2/h3 from the markdown content) — see DocsToc.astro */
|
||||
toc?: TocItem[];
|
||||
/**
|
||||
* Page URL in the docs namespace (equivalent of Eleventy's page.url), e.g.
|
||||
* "/ui/components/alert/". By default derived from Astro.url.pathname by
|
||||
* stripping the /docs prefix. Drives the active menu entry, pagination,
|
||||
* the "Edit this page" link, and the relative favicon path.
|
||||
* Page URL in the docs namespace, e.g. "/ui/components/alert/".
|
||||
* By default derived from Astro.url.pathname by stripping the /docs prefix.
|
||||
* Drives the active menu entry, pagination, the "Edit this page" link, and
|
||||
* the relative favicon path.
|
||||
*/
|
||||
url?: string;
|
||||
/** front matter `docs-libs`: extra libraries from src/data/libs.json (css+js) */
|
||||
/** Extra libraries from libs.json (css+js) */
|
||||
docsLibs?: string[];
|
||||
/** hides child and previous/next page navigation */
|
||||
hidePagination?: boolean;
|
||||
@@ -57,7 +58,7 @@ const {
|
||||
} = Astro.props;
|
||||
const environment = process.env.NODE_ENV || 'production';
|
||||
|
||||
// URL in the docs namespace: /docs/ui/components/alert(.html) -> /ui/components/alert/
|
||||
// Strip /docs prefix (and optional .html) for the docs namespace URL.
|
||||
const docsUrl =
|
||||
Astro.props.url ??
|
||||
(() => {
|
||||
@@ -80,7 +81,7 @@ const metaDescription = seoDescription ?? description;
|
||||
const siteName = pageSection ? `Tabler ${pageSection} Documentation` : 'Tabler Documentation';
|
||||
const canonicalUrl = new URL(docsUrl, Astro.site ?? site.docsUrl).href;
|
||||
|
||||
// Equivalent of `{{ page | relative }}`: for /ui/components/alert/ -> "../../.."
|
||||
// Relative asset base: for /ui/components/alert/ -> "../../.."
|
||||
const depth = docsUrl.split('/').filter(Boolean).length;
|
||||
const relative = depth === 0 ? '.' : '../'.repeat(depth).slice(0, -1);
|
||||
|
||||
@@ -88,8 +89,8 @@ const relative = depth === 0 ? '.' : '../'.repeat(depth).slice(0, -1);
|
||||
// MDX layout; fall back to the flat-page convention (docs/pages/<url>.mdx).
|
||||
const editFilePath = editPath ?? `docs/pages${docsUrl.replace(/\/$/, '')}.mdx`;
|
||||
|
||||
// JS/CSS libraries as in Eleventy: docs-libs + clipboard (always included).
|
||||
// In dev file names stay unchanged (e.g. dist/clipboard.min.js keeps .min — the reference has it so).
|
||||
// docs-libs + clipboard (always included). In dev, file names stay unchanged
|
||||
// (e.g. dist/clipboard.min.js keeps .min).
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[] };
|
||||
const libEntries = Object.entries(libs as Record<string, Lib>);
|
||||
const libUrl = (lib: Lib, file: string) =>
|
||||
@@ -234,7 +235,6 @@ const docsLinks = docs.links as DocsLink[];
|
||||
{
|
||||
addedIn && (
|
||||
<div class="ms-auto">
|
||||
{/* equivalent of ui/badge.html color="primary" light=true */}
|
||||
<span class="badge bg-primary-lt text-primary-lt-fg">Added in {addedIn}</span>
|
||||
</div>
|
||||
)
|
||||
@@ -243,8 +243,7 @@ const docsLinks = docs.links as DocsLink[];
|
||||
|
||||
<p class="text-secondary fs-3 lh-3">{summary}</p>
|
||||
|
||||
{/* markdown content; equivalent of {{ content | headings-id }} — the consuming
|
||||
page must supply headings with ids (MDX does this by default) */}
|
||||
{/* Page must supply headings with ids (MDX does this by default). */}
|
||||
<slot />
|
||||
|
||||
{!hidePagination && <DocsPagination url={docsUrl} />}
|
||||
@@ -340,6 +339,8 @@ const docsLinks = docs.links as DocsLink[];
|
||||
<script is:inline src="/dist/js/tabler.js"></script>
|
||||
<!-- END GLOBAL MANDATORY SCRIPTS -->
|
||||
|
||||
<PageScripts />
|
||||
|
||||
<!-- BEGIN DOCS SCRIPTS -->
|
||||
<script is:inline src="/js/docs.js" defer></script>
|
||||
<!-- END DOCS SCRIPTS -->
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
---
|
||||
// docs/content/ui/base/markdown.md sets `layout: redirect` + `redirect.to: ui/base/prose/`,
|
||||
// but shared/includes/redirect.html reads `page.redirect.to` — undefined in Eleventy —
|
||||
// so the include gets an empty url and the target collapses to `{{ page | relative }}`.
|
||||
// For this 3-deep page that is "../../..", which is exactly what the reference
|
||||
// build (ui/base/markdown/index.html) redirects to.
|
||||
import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
|
||||
import RedirectLayout from '@shared/layouts/RedirectLayout.astro';
|
||||
---
|
||||
|
||||
<RedirectLayout base="../../.." />
|
||||
|
||||
@@ -111,7 +111,7 @@ To edit settings, press <kbd>ctrl</kbd> + <kbd>,</kbd> or <kbd>ctrl</kbd> + <kbd
|
||||
|
||||
## Prose
|
||||
|
||||
If you can't use the CSS classes you want, or you just want to use HTML tags, use the `.prose` class in a container. It will apply the default styles for markdown elements. The `.markdown` class is an alias and will be removed in a future release. The `.wysiwyg` integration (`ui/wysiwyg.html`) is deprecated and will be removed in a future release. See the Prose page for full examples.
|
||||
If you can't use the CSS classes you want, or you just want to use HTML tags, use the `.prose` class in a container. It will apply the default styles for markdown elements. The `.markdown` class is an alias and will be removed in a future release. The `.wysiwyg` integration is deprecated and will be removed in a future release. See the Prose page for full examples.
|
||||
|
||||
<Example>
|
||||
<Prose> <h1>Hello World</h1> <p> Lorem ipsum<sup>[1]</sup> dolor sit amet, consectetur adipiscing elit. Nulla accumsan, metus ultrices eleifend gravida, nulla nunc varius lectus, nec rutrum justo nibh eu lectus. Ut vulputate semper dui. Fusce erat odio, sollicitudin vel erat vel, interdum mattis neque. Sub<sub >script</sub > works as well! </p> <h2>Second level</h2> <p> Curabitur accumsan turpis pharetra <strong>augue tincidunt</strong> blandit. Quisque condimentum maximus mi, sit amet commodo arcu rutrum id. Proin pretium urna vel cursus venenatis. Suspendisse potenti. Etiam mattis sem rhoncus lacus dapibus facilisis. Donec at dignissim dui. Ut et neque nisl. </p> <ul> <li>In fermentum leo eu lectus mollis, quis dictum mi aliquet.</li> <li>Morbi eu nulla lobortis, lobortis est in, fringilla felis.</li> <li>Aliquam nec felis in sapien venenatis viverra fermentum nec lectus.</li> <li>Ut non enim metus.</li> </ul> </Prose>
|
||||
|
||||
+31
-39
@@ -1,16 +1,14 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config'
|
||||
import mdx from '@astrojs/mdx'
|
||||
import { satteri } from '@astrojs/markdown-satteri'
|
||||
import beautify from 'js-beautify'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { devNull } from 'node:os'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig } from 'astro/config';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import { satteri } from '@astrojs/markdown-satteri';
|
||||
import beautify from 'js-beautify';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { devNull } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/**
|
||||
* Equivalent of the Eleventy "html-prettify" step (@tabler/preview): the
|
||||
* generated HTML is the product (users copy it 1:1), so after the build we
|
||||
* format it with prettier per .prettierrc.
|
||||
* After build, format page HTML with prettier (users copy it 1:1).
|
||||
* @returns {import('astro').AstroIntegration}
|
||||
*/
|
||||
function prettifyHtml() {
|
||||
@@ -18,20 +16,19 @@ function prettifyHtml() {
|
||||
name: 'prettify-html',
|
||||
hooks: {
|
||||
'astro:build:done': async ({ dir, logger }) => {
|
||||
const outDir = fileURLToPath(dir)
|
||||
const outDir = fileURLToPath(dir);
|
||||
// dist/preview/ and dist/dist/ are copy-assets.mjs's copies of public/{preview,dist}
|
||||
// (demo css/js and @tabler/core's dist, including vendored libs) — not pages, and
|
||||
// some vendored libs ship their own malformed docs/*.html that trips the parser below.
|
||||
/** @param {string} file */
|
||||
const isVendorCopy = (file) => file.includes(`${outDir}preview/`) || file.includes(`${outDir}dist/`)
|
||||
// Astro appends "overflow-x: auto" to the shiki <pre> style — the
|
||||
// Eleventy pipeline doesn't have it and HTML is the product: restore 1:1.
|
||||
const { globSync } = await import('node:fs')
|
||||
const { readFileSync, writeFileSync } = await import('node:fs')
|
||||
const isVendorCopy = (file) => file.includes(`${outDir}preview/`) || file.includes(`${outDir}dist/`);
|
||||
// Strip Astro's extra "overflow-x: auto" on shiki <pre> styles.
|
||||
const { globSync } = await import('node:fs');
|
||||
const { readFileSync, writeFileSync } = await import('node:fs');
|
||||
for (const file of globSync(`${outDir}**/*.html`, { exclude: isVendorCopy })) {
|
||||
const content = readFileSync(file, 'utf8')
|
||||
const cleaned = content.replaceAll('; overflow-x: auto;', '')
|
||||
if (cleaned !== content) writeFileSync(file, cleaned)
|
||||
const content = readFileSync(file, 'utf8');
|
||||
const cleaned = content.replaceAll('; overflow-x: auto;', '');
|
||||
if (cleaned !== content) writeFileSync(file, cleaned);
|
||||
}
|
||||
execFileSync(
|
||||
'npx',
|
||||
@@ -51,11 +48,11 @@ function prettifyHtml() {
|
||||
`!${outDir}dist/**`,
|
||||
],
|
||||
{ stdio: 'inherit' },
|
||||
)
|
||||
logger.info('HTML formatted with prettier')
|
||||
);
|
||||
logger.info('HTML formatted with prettier');
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// https://astro.build/config
|
||||
@@ -72,49 +69,44 @@ export default defineConfig({
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
// demo data lives in the monorepo's shared/data — single source of
|
||||
// truth shared with the Eleventy packages (no copies in src/data)
|
||||
// Demo data in shared/data (single source of truth).
|
||||
'@data': fileURLToPath(new URL('../shared/data', import.meta.url)),
|
||||
// Astro components/lib shared with docs (single source of truth)
|
||||
// Components/lib shared with docs.
|
||||
'@shared': fileURLToPath(new URL('../shared', import.meta.url)),
|
||||
'@ui': fileURLToPath(new URL('../shared/ui', import.meta.url)),
|
||||
'@components': fileURLToPath(new URL('../shared/components', import.meta.url)),
|
||||
// this package's pages dir — used by @shared/lib/docs-children's glob
|
||||
// Used by @shared/lib/docs-children's glob.
|
||||
'@pages': fileURLToPath(new URL('./pages', import.meta.url)),
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
// emit sign-in.html instead of sign-in/index.html — matches the Eleventy
|
||||
// preview package layout, where the HTML files are the distributed product
|
||||
// Emit sign-in.html instead of sign-in/index.html (HTML is the product).
|
||||
format: 'file',
|
||||
},
|
||||
// Do not collapse whitespace in the output — the HTML must stay readable
|
||||
// (like the Eleventy build); prettier finalizes formatting after the build.
|
||||
// Keep readable HTML; prettier formats after the build.
|
||||
compressHTML: false,
|
||||
integrations: [mdx(), prettifyHtml()],
|
||||
markdown: {
|
||||
// markdown-it in Eleventy does not produce typographic quotes — neither do we
|
||||
// No typographic quote rewriting.
|
||||
processor: satteri({ features: { smartPunctuation: false } }),
|
||||
shikiConfig: {
|
||||
theme: 'github-dark',
|
||||
transformers: [
|
||||
{
|
||||
// The Eleventy docs pipeline beautifies html fences before highlighting
|
||||
// Beautify html fences before highlighting.
|
||||
preprocess(code) {
|
||||
if (this.options.lang === 'html') {
|
||||
return beautify.html(code, { indent_size: 2, wrap_line_length: 80 })
|
||||
return beautify.html(code, { indent_size: 2, wrap_line_length: 80 });
|
||||
}
|
||||
},
|
||||
// Eleventy docs emits raw shiki output: <pre class="shiki github-dark">.
|
||||
// Astro adds its own astro-code class and data-language — restore the
|
||||
// exact markdown-it + shiki pipeline markup.
|
||||
// Keep shiki classes only (drop Astro's astro-code / data-language).
|
||||
pre(node) {
|
||||
node.properties.class = 'shiki github-dark'
|
||||
delete node.properties.dataLanguage
|
||||
node.properties.class = 'shiki github-dark';
|
||||
delete node.properties.dataLanguage;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
import FormFooter from '@ui/FormFooter.astro'
|
||||
import SingleLayout from '@shared/layouts/SingleLayout.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import FormFooter from '@ui/FormFooter.astro';
|
||||
import SingleLayout from '@shared/layouts/SingleLayout.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import CaptureScript from '@shared/components/CaptureScript.astro';
|
||||
---
|
||||
|
||||
<SingleLayout title="2-Step Verification">
|
||||
@@ -11,7 +12,9 @@ import ButtonList from '@ui/ButtonList.astro'
|
||||
<h2 class="card-title card-title-lg text-center mb-4">Authenticate Your Account</h2>
|
||||
|
||||
<p class="my-4 text-center">
|
||||
Please confirm your account by entering the authorization code sent to <strong>+1 856-672-8552</strong>.
|
||||
Please confirm your account by entering the authorization code sent to <strong
|
||||
>+1 856-672-8552</strong
|
||||
>.
|
||||
</p>
|
||||
|
||||
<div class="my-5">
|
||||
@@ -22,7 +25,14 @@ import ButtonList from '@ui/ButtonList.astro'
|
||||
<div class="row g-2">
|
||||
{Array.from({ length: 3 }).map(() => (
|
||||
<div class="col">
|
||||
<input type="text" class="form-control form-control-lg text-center px-3 py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
|
||||
<input
|
||||
type="text"
|
||||
class="form-control form-control-lg text-center px-3 py-3"
|
||||
maxlength="1"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
data-code-input
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -48,32 +58,36 @@ import ButtonList from '@ui/ButtonList.astro'
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<CaptureScript>
|
||||
<!-- BEGIN CODE INPUT SCRIPT -->
|
||||
<script>
|
||||
const inputs = document.querySelectorAll<HTMLInputElement>('[data-code-input]')
|
||||
<script is:inline>
|
||||
const inputs = document.querySelectorAll('[data-code-input]');
|
||||
|
||||
// Attach an event listener to each input element
|
||||
inputs.forEach((input, i) => {
|
||||
input.addEventListener('input', (e) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
const target = e.target;
|
||||
// If the input field has a character, and there is a next input field, focus it
|
||||
if (target.value.length === target.maxLength && i + 1 < inputs.length) {
|
||||
inputs[i + 1].focus()
|
||||
inputs[i + 1].focus();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
input.addEventListener('keydown', (e) => {
|
||||
const target = e.target;
|
||||
// If the input field is empty and Backspace is pressed, and there is a previous input field, focus it
|
||||
if (target.value.length === 0 && e.key === 'Backspace' && i > 0) {
|
||||
inputs[i - 1].focus()
|
||||
inputs[i - 1].focus();
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- END CODE INPUT SCRIPT -->
|
||||
</CaptureScript>
|
||||
|
||||
<div class="text-center text-secondary mt-3">
|
||||
It may take a minute to receive your code. Haven't received it? <a href="./">Resend a new code.</a>
|
||||
It may take a minute to receive your code. Haven't received it? <a href="./"
|
||||
>Resend a new code.</a
|
||||
>
|
||||
</div>
|
||||
</SingleLayout>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
---
|
||||
// Liquid: {% if country.code == 'US' %} selected{% endif %} — flags.json entries
|
||||
// have no `code` property, so the reference renders value="" and never `selected`.
|
||||
// We mirror that output exactly.
|
||||
// flags.json entries have no `code` property — value="" and never `selected`.
|
||||
|
||||
import FormFooter from '@ui/FormFooter.astro'
|
||||
import SingleLayout from '@shared/layouts/SingleLayout.astro'
|
||||
|
||||
@@ -1,54 +1,60 @@
|
||||
---
|
||||
|
||||
// Code-block content lives in the frontmatter so Astro does not try to evaluate
|
||||
// its `${name}` / `{ }` as expressions; injected verbatim via set:html.
|
||||
|
||||
import ButtonGroup from '@ui/ButtonGroup.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardHeader from '@ui/CardHeader.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardFooter from '@ui/CardFooter.astro'
|
||||
import Alert from '@ui/Alert.astro'
|
||||
import Badge from '@ui/Badge.astro'
|
||||
import Progress from '@ui/Progress.astro'
|
||||
import Select from '@ui/Select.astro'
|
||||
import Check from '@ui/form/Check.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
import Nav from '@ui/Nav.astro'
|
||||
import Breadcrumb from '@ui/Breadcrumb.astro'
|
||||
import Pagination from '@ui/Pagination.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Dropdown from '@ui/Dropdown.astro'
|
||||
import Accordion from '@ui/Accordion.astro'
|
||||
import Spinner from '@ui/Spinner.astro'
|
||||
import Rating from '@ui/Rating.astro'
|
||||
import Steps from '@ui/Steps.astro'
|
||||
import StatusDot from '@ui/StatusDot.astro'
|
||||
import Toast from '@ui/Toast.astro'
|
||||
import InputIcon from '@ui/form/InputIcon.astro'
|
||||
import InputGroup from '@ui/InputGroup.astro'
|
||||
import Range from '@ui/Range.astro'
|
||||
import Tag from '@ui/Tag.astro'
|
||||
import Ribbon from '@ui/Ribbon.astro'
|
||||
import Flag from '@ui/Flag.astro'
|
||||
import Payment from '@ui/Payment.astro'
|
||||
import Timeline from '@ui/Timeline.astro'
|
||||
import Empty from '@ui/Empty.astro'
|
||||
import NavSegmented from '@ui/NavSegmented.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import ButtonGroup from '@ui/ButtonGroup.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardHeader from '@ui/CardHeader.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardFooter from '@ui/CardFooter.astro';
|
||||
import Alert from '@ui/Alert.astro';
|
||||
import Badge from '@ui/Badge.astro';
|
||||
import Progress from '@ui/Progress.astro';
|
||||
import Select from '@ui/Select.astro';
|
||||
import Check from '@ui/form/Check.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
import Nav from '@ui/Nav.astro';
|
||||
import Breadcrumb from '@ui/Breadcrumb.astro';
|
||||
import Pagination from '@ui/Pagination.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Dropdown from '@ui/Dropdown.astro';
|
||||
import Accordion from '@ui/Accordion.astro';
|
||||
import Spinner from '@ui/Spinner.astro';
|
||||
import Rating from '@ui/Rating.astro';
|
||||
import Steps from '@ui/Steps.astro';
|
||||
import StatusDot from '@ui/StatusDot.astro';
|
||||
import Toast from '@ui/Toast.astro';
|
||||
import InputIcon from '@ui/form/InputIcon.astro';
|
||||
import InputGroup from '@ui/InputGroup.astro';
|
||||
import Range from '@ui/Range.astro';
|
||||
import Tag from '@ui/Tag.astro';
|
||||
import Ribbon from '@ui/Ribbon.astro';
|
||||
import Flag from '@ui/Flag.astro';
|
||||
import Payment from '@ui/Payment.astro';
|
||||
import Timeline from '@ui/Timeline.astro';
|
||||
import Empty from '@ui/Empty.astro';
|
||||
import NavSegmented from '@ui/NavSegmented.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
|
||||
const codeExample = `// JavaScript example
|
||||
function greetUser(name) {
|
||||
console.log(\`Hello, \${name}!\`);
|
||||
return true;
|
||||
}`
|
||||
}`;
|
||||
---
|
||||
|
||||
<DefaultLayout title="All Elements - Kitchen Sink" pageHeader="All Elements" pageMenu="base.all-elements" pageLibs={['nouislider', 'star-rating.js', 'tabler-flags', 'tabler-payments', 'tom-select']}>
|
||||
<DefaultLayout
|
||||
title="All Elements - Kitchen Sink"
|
||||
pageHeader="All Elements"
|
||||
pageMenu="base.all-elements"
|
||||
pageLibs={['nouislider', 'star-rating.js', 'tabler-flags', 'tabler-payments', 'tom-select']}
|
||||
>
|
||||
<div class="row row-cards">
|
||||
<!-- Typography Section -->
|
||||
<div class="col-12">
|
||||
@@ -66,10 +72,8 @@ function greetUser(name) {
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="lead">This is a lead paragraph with larger text.</p>
|
||||
<p>
|
||||
This is a regular paragraph with <strong>bold text</strong>, <em>italic text</em>, and
|
||||
<u>underlined text</u>.
|
||||
</p>
|
||||
<p>This is a regular paragraph with <strong>bold text</strong>, <em>italic text</em>, and
|
||||
<u>underlined text</u>.</p>
|
||||
<p><small class="text-muted">This is small muted text.</small></p>
|
||||
<p class="text-primary">Primary text color</p>
|
||||
<p class="text-success">Success text color</p>
|
||||
@@ -210,13 +214,13 @@ function greetUser(name) {
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<FormGroup label="Text Input">
|
||||
<input type="text" class="form-control" placeholder="Enter text" />
|
||||
<input type="text" class="form-control" placeholder="Enter text">
|
||||
</FormGroup>
|
||||
<FormGroup label="Email Input">
|
||||
<input type="email" class="form-control" placeholder="Enter email" />
|
||||
<input type="email" class="form-control" placeholder="Enter email">
|
||||
</FormGroup>
|
||||
<FormGroup label="Password Input">
|
||||
<input type="password" class="form-control" placeholder="Enter password" />
|
||||
<input type="password" class="form-control" placeholder="Enter password">
|
||||
</FormGroup>
|
||||
<FormGroup label="Select Dropdown">
|
||||
<Select id="demo-select" values={['Option 1', 'Option 2', 'Option 3']} />
|
||||
@@ -265,10 +269,7 @@ function greetUser(name) {
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Pagination</h4>
|
||||
{
|
||||
/* active-item="2" is a STRING in Liquid; `i == "2"` never matches the
|
||||
integer index, so no page renders active — reproduced by passing a string. */
|
||||
}
|
||||
{/* activeItem="2" is a string — strict equality never matches integer index, so no page is active */}
|
||||
<Pagination count={5} activeItem="2" class="mb-4" />
|
||||
|
||||
<h4>Pagination with Text</h4>
|
||||
@@ -596,7 +597,13 @@ function greetUser(name) {
|
||||
<Card>
|
||||
<CardHeader title="Empty State" />
|
||||
<CardBody>
|
||||
<Empty title="No data found" subtitle="Try adjusting your search or filter to find what you're looking for." illustration="boy-girl.svg" buttonText="Add new item" buttonIcon="plus" />
|
||||
<Empty
|
||||
title="No data found"
|
||||
subtitle="Try adjusting your search or filter to find what you're looking for."
|
||||
illustration="boy-girl.svg"
|
||||
buttonText="Add new item"
|
||||
buttonIcon="plus"
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -607,8 +614,12 @@ function greetUser(name) {
|
||||
<CardHeader title="Modals" />
|
||||
<CardBody>
|
||||
<ButtonList>
|
||||
<button class="btn" data-bs-toggle="modal" data-bs-target="#modal-demo"> Open Modal </button>
|
||||
<button class="btn" data-bs-toggle="modal" data-bs-target="#modal-success"> Success Modal </button>
|
||||
<button class="btn" data-bs-toggle="modal" data-bs-target="#modal-demo">
|
||||
Open Modal
|
||||
</button>
|
||||
<button class="btn" data-bs-toggle="modal" data-bs-target="#modal-success">
|
||||
Success Modal
|
||||
</button>
|
||||
</ButtonList>
|
||||
|
||||
<!-- Demo Modal -->
|
||||
@@ -729,22 +740,34 @@ function greetUser(name) {
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4>Basic Collapse</h4>
|
||||
<button class="btn" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample"> Toggle Collapse </button>
|
||||
<button class="btn" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample">
|
||||
Toggle Collapse
|
||||
</button>
|
||||
<div class="collapse" id="collapseExample">
|
||||
<div class="card card-body">This is collapsed content that can be toggled. It's hidden by default and shown when the button is clicked.</div>
|
||||
<div class="card card-body">
|
||||
This is collapsed content that can be toggled. It's hidden by default and shown when the button is clicked.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Multiple Targets</h4>
|
||||
<ButtonList>
|
||||
<button class="btn" type="button" data-bs-toggle="collapse" data-bs-target="#multiCollapseExample1"> Toggle First </button>
|
||||
<button class="btn" type="button" data-bs-toggle="collapse" data-bs-target="#multiCollapseExample2"> Toggle Second </button>
|
||||
<button class="btn" type="button" data-bs-toggle="collapse" data-bs-target="#multiCollapseExample1">
|
||||
Toggle First
|
||||
</button>
|
||||
<button class="btn" type="button" data-bs-toggle="collapse" data-bs-target="#multiCollapseExample2">
|
||||
Toggle Second
|
||||
</button>
|
||||
</ButtonList>
|
||||
<div class="collapse" id="multiCollapseExample1">
|
||||
<div class="card card-body mb-2">First collapsible content.</div>
|
||||
<div class="card card-body mb-2">
|
||||
First collapsible content.
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse" id="multiCollapseExample2">
|
||||
<div class="card card-body">Second collapsible content.</div>
|
||||
<div class="card card-body">
|
||||
Second collapsible content.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -761,17 +784,29 @@ function greetUser(name) {
|
||||
<div class="col-md-6">
|
||||
<h4>Tooltips</h4>
|
||||
<ButtonList>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top"> Top </button>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="right" title="Tooltip on right"> Right </button>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tooltip on bottom"> Bottom </button>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="left" title="Tooltip on left"> Left </button>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">
|
||||
Top
|
||||
</button>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="right" title="Tooltip on right">
|
||||
Right
|
||||
</button>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tooltip on bottom">
|
||||
Bottom
|
||||
</button>
|
||||
<button class="btn" data-bs-toggle="tooltip" data-bs-placement="left" title="Tooltip on left">
|
||||
Left
|
||||
</button>
|
||||
</ButtonList>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Popovers</h4>
|
||||
<ButtonList>
|
||||
<button class="btn" data-bs-toggle="popover" data-bs-placement="top" title="Popover Title" data-bs-content="This is popover content on top"> Top Popover </button>
|
||||
<button class="btn" data-bs-toggle="popover" data-bs-placement="right" title="Popover Title" data-bs-content="This is popover content on right"> Right Popover </button>
|
||||
<button class="btn" data-bs-toggle="popover" data-bs-placement="top" title="Popover Title" data-bs-content="This is popover content on top">
|
||||
Top Popover
|
||||
</button>
|
||||
<button class="btn" data-bs-toggle="popover" data-bs-placement="right" title="Popover Title" data-bs-content="This is popover content on right">
|
||||
Right Popover
|
||||
</button>
|
||||
</ButtonList>
|
||||
</div>
|
||||
</div>
|
||||
@@ -824,7 +859,7 @@ function greetUser(name) {
|
||||
</blockquote>
|
||||
|
||||
<h4>Code Block</h4>
|
||||
<pre class="mb-3"><code set:html={codeExample} /></pre>
|
||||
<pre class="mb-3"><code set:html={codeExample}></code></pre>
|
||||
|
||||
<h4>Inline Elements</h4>
|
||||
<p>This paragraph contains <code>inline code</code>, <kbd>Ctrl + S</kbd> keyboard shortcut, and <mark>highlighted text</mark>.</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
// title really is "Forgot password" in the Liquid source)
|
||||
import SingleLayout from '@shared/layouts/SingleLayout.astro'
|
||||
import AuthLockCard from '@shared/components/cards/AuthLockCard.astro'
|
||||
// Title is "Forgot password" (matches auth-lock card, not page name).
|
||||
import SingleLayout from '@shared/layouts/SingleLayout.astro';
|
||||
import AuthLockCard from '@shared/components/cards/AuthLockCard.astro';
|
||||
---
|
||||
|
||||
<SingleLayout title="Forgot password">
|
||||
|
||||
+20
-23
@@ -1,30 +1,27 @@
|
||||
---
|
||||
// Note: the `description` front matter key is inert in the dev build (no
|
||||
// <meta name="description"> in the reference output), so it is not ported.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import AvatarUpload from '@ui/AvatarUpload.astro'
|
||||
import AvatarList from '@ui/AvatarList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import { site } from '@shared/lib/site'
|
||||
import { firstLetters } from '@shared/lib/string-format'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import AvatarUpload from '@ui/AvatarUpload.astro';
|
||||
import AvatarList from '@ui/AvatarList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import { site } from '@shared/lib/site';
|
||||
import { firstLetters } from '@shared/lib/string-format';
|
||||
|
||||
const iconIcons = ['user', 'settings', 'car', 'balloon', 'users', 'users-group', 'apps', 'ghost']
|
||||
const iconIcons = ['user', 'settings', 'car', 'balloon', 'users', 'users-group', 'apps', 'ghost'];
|
||||
|
||||
// Liquid: {% for color in site.colors %} — the iterated keys match site.themeColors
|
||||
// (blue..cyan), confirmed against the reference output.
|
||||
const colors = site.themeColors
|
||||
// themeColors keys (blue..cyan).
|
||||
const colors = site.themeColors;
|
||||
|
||||
const people8 = people.slice(0, 8)
|
||||
const people5 = people.slice(0, 5)
|
||||
const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl']
|
||||
const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg']
|
||||
const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']
|
||||
const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary']
|
||||
const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
|
||||
const people8 = people.slice(0, 8);
|
||||
const people5 = people.slice(0, 5);
|
||||
const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl'];
|
||||
const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg'];
|
||||
const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
||||
const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary'];
|
||||
const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch'];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Avatars" pageMenu="base.avatars" pageHeader="Avatars">
|
||||
|
||||
+17
-16
@@ -1,19 +1,20 @@
|
||||
---
|
||||
// Liquid: colors = ['default'] + site.colors keys + ['dark', 'light']
|
||||
|
||||
import BadgesList from '@ui/BadgesList.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro'
|
||||
import site from '@data/site.json'
|
||||
import { ucFirst } from '@shared/lib/string-format'
|
||||
// colors = ['default'] + site.colors keys + ['dark', 'light']
|
||||
|
||||
const colors = ['default', ...Object.keys(site.colors), 'dark', 'light']
|
||||
const sizes = ['sm', 'md', 'lg']
|
||||
import BadgesList from '@ui/BadgesList.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro';
|
||||
import site from '@data/site.json';
|
||||
import { ucFirst } from '@shared/lib/string-format';
|
||||
|
||||
const colors = ['default', ...Object.keys(site.colors), 'dark', 'light'];
|
||||
const sizes = ['sm', 'md', 'lg'];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Badges" pageHeader="Badges" pageMenu="base.badges">
|
||||
@@ -46,8 +47,7 @@ const sizes = ['sm', 'md', 'lg']
|
||||
<Icon name="check" /> Left icon
|
||||
</span>
|
||||
<span class={`badge${size !== 'md' ? ` badge-${size}` : ''}`}>
|
||||
Right icon
|
||||
<Icon name="arrow-right" />
|
||||
Right icon<Icon name="arrow-right" />
|
||||
</span>
|
||||
<span class={`badge badge-icononly${size !== 'md' ? ` badge-${size}` : ''}`}>
|
||||
<Icon name="star" type="filled" />
|
||||
@@ -176,7 +176,8 @@ const sizes = ['sm', 'md', 'lg']
|
||||
{
|
||||
colors.map((color, index) => (
|
||||
<button class="btn position-relative">
|
||||
{ucFirst(color)} badge <span class={`badge bg-${color} text-${color}-fg badge-notification badge-pill`}>{index + 1}</span>
|
||||
{ucFirst(color)} badge{' '}
|
||||
<span class={`badge bg-${color} text-${color}-fg badge-notification badge-pill`}>{index + 1}</span>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
// front matter is inert in the Liquid templates (never referenced) — the
|
||||
// empty page header comes from the missing `page-header` key.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Empty from '@ui/Empty.astro'
|
||||
// front matter keys unused — empty page header comes from missing `page-header` key.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Empty from '@ui/Empty.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Blank page" pageMenu="base.blank" containerCentered>
|
||||
|
||||
+28
-38
@@ -1,23 +1,23 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardHeader from '@ui/CardHeader.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import site from '@data/site.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardHeader from '@ui/CardHeader.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import site from '@data/site.json';
|
||||
|
||||
type ColorEntry = [string, { title: string; icon?: string }]
|
||||
type ColorEntry = [string, { title: string; icon?: string }];
|
||||
|
||||
// Liquid iterates the site.json objects as [key, value] pairs.
|
||||
// themeColors/colors entries have no icon — ui/icon.html renders nothing there.
|
||||
const themeColors = Object.entries(site.themeColors) as ColorEntry[]
|
||||
const colors = Object.entries(site.colors) as ColorEntry[]
|
||||
const socialColors = Object.entries(site.socialColors) as ColorEntry[]
|
||||
// site.json objects iterated as [key, value] pairs.
|
||||
// themeColors/colors entries have no icon — Icon renders nothing there.
|
||||
const themeColors = Object.entries(site.themeColors) as ColorEntry[];
|
||||
const colors = Object.entries(site.colors) as ColorEntry[];
|
||||
const socialColors = Object.entries(site.socialColors) as ColorEntry[];
|
||||
|
||||
const actions = ['edit', 'copy', 'settings', 'clipboard', 'x']
|
||||
const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
const actions = ['edit', 'copy', 'settings', 'clipboard', 'x'];
|
||||
const sizes = ['sm', 'md', 'lg', 'xl'];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Buttons" pageHeader="Buttons" pageMenu="base.buttons">
|
||||
@@ -29,9 +29,7 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<ButtonList>
|
||||
{
|
||||
themeColors.map(([name, color]) => (
|
||||
<a class={`btn btn-${name}`}>
|
||||
{color.icon && <Icon name={color.icon} />} {color.title}
|
||||
</a>
|
||||
<a class={`btn btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
@@ -45,9 +43,7 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<ButtonList>
|
||||
{
|
||||
themeColors.map(([name, color]) => (
|
||||
<a class={`btn btn-outline btn-${name}`}>
|
||||
{color.icon && <Icon name={color.icon} />} {color.title}
|
||||
</a>
|
||||
<a class={`btn btn-outline btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
@@ -61,9 +57,7 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<ButtonList>
|
||||
{
|
||||
themeColors.map(([name, color]) => (
|
||||
<a class={`btn btn-ghost btn-${name}`}>
|
||||
{color.icon && <Icon name={color.icon} />} {color.title}
|
||||
</a>
|
||||
<a class={`btn btn-ghost btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
@@ -77,9 +71,7 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<ButtonList>
|
||||
{
|
||||
themeColors.map(([name, color]) => (
|
||||
<a class={`btn btn-square btn-${name}`}>
|
||||
{color.icon && <Icon name={color.icon} />} {color.title}
|
||||
</a>
|
||||
<a class={`btn btn-square btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
@@ -93,9 +85,7 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<ButtonList>
|
||||
{
|
||||
themeColors.map(([name, color]) => (
|
||||
<a class={`btn btn-pill btn-${name}`}>
|
||||
{color.icon && <Icon name={color.icon} />} {color.title}
|
||||
</a>
|
||||
<a class={`btn btn-pill btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
@@ -109,9 +99,7 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<ButtonList>
|
||||
{
|
||||
colors.map(([name, color]) => (
|
||||
<a class={`btn btn-${name}`}>
|
||||
{color.icon && <Icon name={color.icon} />} {color.title}
|
||||
</a>
|
||||
<a class={`btn btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
@@ -123,7 +111,11 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<CardHeader title="Icon buttons" />
|
||||
<CardBody>
|
||||
<ButtonList>
|
||||
{socialColors.map(([name, app]) => <a class={`btn btn-icon btn-${name}`}>{app.icon && <Icon name={app.icon} />}</a>)}
|
||||
{
|
||||
socialColors.map(([name, app]) => (
|
||||
<a class={`btn btn-icon btn-${name}`}>{app.icon && <Icon name={app.icon} />}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -135,9 +127,7 @@ const sizes = ['sm', 'md', 'lg', 'xl']
|
||||
<ButtonList>
|
||||
{
|
||||
socialColors.map(([name, app]) => (
|
||||
<a class={`btn btn-${name}`}>
|
||||
{app.icon && <Icon name={app.icon} />} {app.title}
|
||||
</a>
|
||||
<a class={`btn btn-${name}`}>{app.icon && <Icon name={app.icon} />} {app.title}</a>
|
||||
))
|
||||
}
|
||||
</ButtonList>
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
---
|
||||
import ProseLayout from '@shared/layouts/ProseLayout.astro'
|
||||
import { renderMarkdown } from '@shared/lib/render-markdown'
|
||||
// Liquid: {{ changelog | renderContent: "md" }} — `changelog` is the raw
|
||||
// content of core/CHANGELOG.md (shared/e11ty/data.mjs), rendered by
|
||||
// Eleventy's markdown-it. renderMarkdown() mirrors that engine exactly
|
||||
// (markdown-it 14.3.0, { html: true }, indented code blocks disabled) —
|
||||
// output verified byte-identical to the reference build. Injected via
|
||||
// set:html: the fragment contains entities and a literal `{$prefix}` token
|
||||
// that must not be re-escaped or parsed as JSX.
|
||||
import changelog from '../../core/CHANGELOG.md?raw'
|
||||
import ProseLayout from '@shared/layouts/ProseLayout.astro';
|
||||
import { renderMarkdown } from '@shared/lib/render-markdown';
|
||||
import changelog from '../../core/CHANGELOG.md?raw';
|
||||
|
||||
const changelogHtml = renderMarkdown(changelog)
|
||||
const changelogHtml = renderMarkdown(changelog);
|
||||
---
|
||||
|
||||
<ProseLayout title="Changelog" pageMenu="changelog">
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
---
|
||||
// Liquid: {% for color in site.colors %} — forloop.index (1-based) → id,
|
||||
// color[1].hex → value.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import Colorpicker from '@ui/Colorpicker.astro'
|
||||
import site from '@data/site.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import Colorpicker from '@ui/Colorpicker.astro';
|
||||
import site from '@data/site.json';
|
||||
|
||||
const colors = Object.values(site.colors) as { hex: string }[]
|
||||
const colors = Object.values(site.colors) as { hex: string }[];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Color picker" pageHeader="Color picker" pageMenu="plugins.colorpicker" pageLibs={['coloris.js']}>
|
||||
<DefaultLayout
|
||||
title="Color picker"
|
||||
pageHeader="Color picker"
|
||||
pageMenu="plugins.colorpicker"
|
||||
pageLibs={['coloris.js']}
|
||||
>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Basic</CardTitle>
|
||||
|
||||
+49
-37
@@ -1,22 +1,21 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import site from '@data/site.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import CaptureScript from '@shared/components/CaptureScript.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import site from '@data/site.json';
|
||||
|
||||
type ColorEntry = [string, { title: string; hex: string; abbr?: string; icon?: string }]
|
||||
type ColorEntry = [string, { title: string; hex: string; abbr?: string; icon?: string }];
|
||||
|
||||
// Liquid iterates the site.json objects as [key, value] pairs.
|
||||
const colors = Object.entries(site.colors) as ColorEntry[]
|
||||
const lightColors = Object.entries(site.lightColors) as ColorEntry[]
|
||||
const grayColors = Object.entries(site.grayColors) as ColorEntry[]
|
||||
const socialColors = Object.entries(site.socialColors) as ColorEntry[]
|
||||
const colors = Object.entries(site.colors) as ColorEntry[];
|
||||
const lightColors = Object.entries(site.lightColors) as ColorEntry[];
|
||||
const grayColors = Object.entries(site.grayColors) as ColorEntry[];
|
||||
const socialColors = Object.entries(site.socialColors) as ColorEntry[];
|
||||
|
||||
// Liquid: colors keys + inverted, white, transparent (pushed before the gradient loops).
|
||||
const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'transparent']
|
||||
const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'transparent'];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Colors" pageHeader="Colors" pageMenu="base.colors">
|
||||
@@ -165,7 +164,10 @@ const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'trans
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="border rounded bg-pattern-transparent overflow-hidden">
|
||||
<div id="gradient-preview" class="border rounded bg-gradient bg-gradient-from-primary bg-gradient-to-transparent">
|
||||
<div
|
||||
id="gradient-preview"
|
||||
class="border rounded bg-gradient bg-gradient-from-primary bg-gradient-to-transparent"
|
||||
>
|
||||
<div class=" px-4 py-5"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,12 +183,20 @@ const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'trans
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="space-y">
|
||||
{gradientColors.map((color) => <div class={`border rounded bg-gradient bg-gradient-from-${color} bg-gradient-to-transparent px-4 py-2`} />)}
|
||||
{
|
||||
gradientColors.map((color) => (
|
||||
<div class={`border rounded bg-gradient bg-gradient-from-${color} bg-gradient-to-transparent px-4 py-2`} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="space-y">
|
||||
{gradientColors.map((color) => <div class={`border rounded bg-gradient bg-gradient-to-${color} bg-gradient-from-transparent px-4 py-2`} />)}
|
||||
{
|
||||
gradientColors.map((color) => (
|
||||
<div class={`border rounded bg-gradient bg-gradient-to-${color} bg-gradient-from-transparent px-4 py-2`} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,39 +207,41 @@ const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'trans
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CaptureScript>
|
||||
<!-- BEGIN GRADIENT SCRIPT -->
|
||||
<script is:inline>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var gradientPreview = document.getElementById('gradient-preview')
|
||||
var colorFrom = document.querySelector('[name="color-from"]')
|
||||
var colorTo = document.querySelector('[name="color-to"]')
|
||||
var colorVia = document.querySelector('[name="color-via"]')
|
||||
var colorDirection = document.querySelector('[name="color-direction"]')
|
||||
var gradientPreview = document.getElementById('gradient-preview');
|
||||
var colorFrom = document.querySelector('[name="color-from"]');
|
||||
var colorTo = document.querySelector('[name="color-to"]');
|
||||
var colorVia = document.querySelector('[name="color-via"]');
|
||||
var colorDirection = document.querySelector('[name="color-direction"]');
|
||||
|
||||
function updateGradient() {
|
||||
var from = colorFrom.value
|
||||
var to = colorTo.value
|
||||
var via = colorVia.value
|
||||
var direction = colorDirection.value
|
||||
var from = colorFrom.value;
|
||||
var to = colorTo.value;
|
||||
var via = colorVia.value;
|
||||
var direction = colorDirection.value;
|
||||
|
||||
var gradient = 'bg-gradient bg-gradient-from-' + from + ' bg-gradient-to-' + to
|
||||
var gradient = 'bg-gradient bg-gradient-from-' + from + ' bg-gradient-to-' + to;
|
||||
|
||||
if (via) {
|
||||
gradient += ' bg-gradient-via-' + via
|
||||
gradient += ' bg-gradient-via-' + via;
|
||||
}
|
||||
|
||||
gradient += ' bg-gradient-' + direction
|
||||
gradient += ' bg-gradient-' + direction;
|
||||
|
||||
gradientPreview.className = gradient
|
||||
gradientPreview.className = gradient;
|
||||
}
|
||||
|
||||
colorFrom.addEventListener('change', updateGradient)
|
||||
colorTo.addEventListener('change', updateGradient)
|
||||
colorVia.addEventListener('change', updateGradient)
|
||||
colorDirection.addEventListener('change', updateGradient)
|
||||
colorFrom.addEventListener('change', updateGradient);
|
||||
colorTo.addEventListener('change', updateGradient);
|
||||
colorVia.addEventListener('change', updateGradient);
|
||||
colorDirection.addEventListener('change', updateGradient);
|
||||
|
||||
updateGradient()
|
||||
})
|
||||
updateGradient();
|
||||
});
|
||||
</script>
|
||||
<!-- END GRADIENT SCRIPT -->
|
||||
</CaptureScript>
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -1,48 +1,53 @@
|
||||
---
|
||||
import CardActions from '@ui/CardActions.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Trending from '@ui/Trending.astro'
|
||||
import CardDropdown from '@ui/CardDropdown.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardHeader from '@ui/CardHeader.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import NavSegmented from '@ui/NavSegmented.astro'
|
||||
import Chart from '@ui/Chart.astro'
|
||||
import SwitchIcon from '@ui/SwitchIcon.astro'
|
||||
import cryptoCurrencies from '@data/crypto-currencies.json'
|
||||
import cryptoMarkets from '@data/crypto-markets.json'
|
||||
import cryptoOrders from '@data/crypto-orders.json'
|
||||
import { parseCurrency, roundTo } from '@shared/lib/string-format'
|
||||
import CardActions from '@ui/CardActions.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Trending from '@ui/Trending.astro';
|
||||
import CardDropdown from '@ui/CardDropdown.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardHeader from '@ui/CardHeader.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import NavSegmented from '@ui/NavSegmented.astro';
|
||||
import Chart from '@ui/Chart.astro';
|
||||
import SwitchIcon from '@ui/SwitchIcon.astro';
|
||||
import cryptoCurrencies from '@data/crypto-currencies.json';
|
||||
import cryptoMarkets from '@data/crypto-markets.json';
|
||||
import cryptoOrders from '@data/crypto-orders.json';
|
||||
import { parseCurrency, roundTo } from '@shared/lib/string-format';
|
||||
|
||||
interface Currency {
|
||||
'symbol': string
|
||||
'price': string
|
||||
'p24h': number
|
||||
'volume-24h': string
|
||||
symbol: string;
|
||||
price: string;
|
||||
p24h: number;
|
||||
'volume-24h': string;
|
||||
}
|
||||
|
||||
const currencies = cryptoCurrencies as Currency[]
|
||||
const find = (symbol: string) => currencies.find((c) => c.symbol === symbol)!
|
||||
const btc = find('BTC')
|
||||
const ltc = find('LTC')
|
||||
const eth = find('ETH')
|
||||
const xmr = find('XMR')
|
||||
const currencies = cryptoCurrencies as Currency[];
|
||||
const find = (symbol: string) => currencies.find((c) => c.symbol === symbol)!;
|
||||
const btc = find('BTC');
|
||||
const ltc = find('LTC');
|
||||
const eth = find('ETH');
|
||||
const xmr = find('XMR');
|
||||
|
||||
const btcBalance = 2.3
|
||||
const btcPriceNum = parseCurrency(btc.price)
|
||||
const totalUsd = Math.round(btcPriceNum * btcBalance).toLocaleString('en-US')
|
||||
// Liquid `divided_by` then `round: 8` (trailing zeros dropped by number output)
|
||||
const ltcBtc = roundTo(parseCurrency(ltc.price) / btcPriceNum)
|
||||
const ethBtc = roundTo(parseCurrency(eth.price) / btcPriceNum)
|
||||
const xmrBtc = roundTo(parseCurrency(xmr.price) / btcPriceNum)
|
||||
const btcBalance = 2.3;
|
||||
const btcPriceNum = parseCurrency(btc.price);
|
||||
const totalUsd = Math.round(btcPriceNum * btcBalance).toLocaleString('en-US');
|
||||
// divided_by then round to 8 decimals (trailing zeros dropped in output)
|
||||
const ltcBtc = roundTo(parseCurrency(ltc.price) / btcPriceNum);
|
||||
const ethBtc = roundTo(parseCurrency(eth.price) / btcPriceNum);
|
||||
const xmrBtc = roundTo(parseCurrency(xmr.price) / btcPriceNum);
|
||||
|
||||
const markets = (cryptoMarkets as { coin: string; price: string; volume: string; change: string }[]).slice(0, 10)
|
||||
const orders = cryptoOrders as { sell_orders: { price: string; btc: string; sum: string }[]; buy_orders: { price: string; btc: string; sum: string }[] }
|
||||
const operationCurrencies = currencies.slice(0, 20)
|
||||
const markets = (cryptoMarkets as { coin: string; price: string; volume: string; change: string }[]).slice(0, 10);
|
||||
const orders = cryptoOrders as { sell_orders: { price: string; btc: string; sum: string }[]; buy_orders: { price: string; btc: string; sum: string }[] };
|
||||
const operationCurrencies = currencies.slice(0, 20);
|
||||
---
|
||||
|
||||
<DefaultLayout title="Crypto Dashboard" pageHeader="Crypto Dashboard" pageMenu="dashboards.crypto" pageLibs={['apexcharts']}>
|
||||
<DefaultLayout
|
||||
title="Crypto Dashboard"
|
||||
pageHeader="Crypto Dashboard"
|
||||
pageMenu="dashboards.crypto"
|
||||
pageLibs={['apexcharts']}
|
||||
>
|
||||
<div class="row row-cards">
|
||||
<div class="col-12">
|
||||
<div class="row row-cards">
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
---
|
||||
import Subheader from '@ui/Subheader.astro'
|
||||
import ButtonGroup from '@ui/ButtonGroup.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import Offcanvas from '@ui/Offcanvas.astro'
|
||||
import Progress from '@ui/Progress.astro'
|
||||
import Modal from '@shared/components/modals/Modal.astro'
|
||||
import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro'
|
||||
import mails from '@data/mails.json'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Email inbox" pageHeader="Inbox" pageMenu="extra.email-inbox" pageLibs={['hugerte']}>
|
||||
import Subheader from '@ui/Subheader.astro';
|
||||
import ButtonGroup from '@ui/ButtonGroup.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import Offcanvas from '@ui/Offcanvas.astro';
|
||||
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 mails from '@data/mails.json';
|
||||
---
|
||||
|
||||
<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">
|
||||
@@ -66,6 +73,8 @@ import mails from '@data/mails.json'
|
||||
|
||||
<p class="text-muted font-13 mb-0">7.02 GB (46%) of 15 GB used</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</Offcanvas>
|
||||
</div>
|
||||
@@ -74,7 +83,8 @@ import mails from '@data/mails.json'
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
<div class="d-xxl-none d-inline-flex">
|
||||
<button class="btn btn-icon" type="button" data-bs-toggle="offcanvas" data-bs-target="#emailSidebaroffcanvas" aria-controls="emailSidebaroffcanvas">
|
||||
<button class="btn btn-icon" type="button" data-bs-toggle="offcanvas"
|
||||
data-bs-target="#emailSidebaroffcanvas" aria-controls="emailSidebaroffcanvas">
|
||||
<Icon name="menu-2" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -127,28 +137,22 @@ import mails from '@data/mails.json'
|
||||
|
||||
<div class="mt-3">
|
||||
<ul class="email-list">
|
||||
{
|
||||
mails && mails.length > 0 ? (
|
||||
{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}`} />
|
||||
<label class="form-check-label" for={`mail-${mail.id}`}></label>
|
||||
</div>
|
||||
</div>
|
||||
<span class="star-toggle">
|
||||
<Icon name="star" />
|
||||
</span>
|
||||
<a href="#" class="email-title">
|
||||
{mail.sender}
|
||||
</a>
|
||||
<span class="star-toggle"><Icon name="star" /></span>
|
||||
<a href="#" class="email-title">{mail.sender}</a>
|
||||
</div>
|
||||
|
||||
<div class="email-content">
|
||||
<a href="#" class="email-subject">
|
||||
{mail.subject} –
|
||||
<a href="#" class="email-subject">{mail.subject} –
|
||||
<span>{mail.preview}</span>
|
||||
</a>
|
||||
<div class="email-date">{mail.date}</div>
|
||||
@@ -157,14 +161,10 @@ import mails from '@data/mails.json'
|
||||
<div class="email-action-icons">
|
||||
<ul class="list-inline">
|
||||
<li class="list-inline-item">
|
||||
<a href="#">
|
||||
<Icon name="archive" />
|
||||
</a>
|
||||
<a href="#"><Icon name="archive" /></a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#">
|
||||
<Icon name="trash" />
|
||||
</a>
|
||||
<a href="#"><Icon name="trash" /></a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#">
|
||||
@@ -172,9 +172,7 @@ import mails from '@data/mails.json'
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a href="#">
|
||||
<Icon name="clock" />
|
||||
</a>
|
||||
<a href="#"><Icon name="clock" /></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -182,31 +180,29 @@ import mails from '@data/mails.json'
|
||||
))
|
||||
) : (
|
||||
<li class="text-muted">No emails</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-7 mt-1">
|
||||
Showing 1 - {mails.length} of {mails.length}
|
||||
</div>
|
||||
<!-- end col-->
|
||||
</div> <!-- end col-->
|
||||
<div class="col-5">
|
||||
<ButtonGroup class="float-end">
|
||||
<button type="button" class="btn btn-icon"><Icon name="chevron-left" /></button>
|
||||
<button type="button" class="btn btn-icon"><Icon name="chevron-right" /></button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<!-- end col-->
|
||||
</div>
|
||||
<!-- end row-->
|
||||
</div> <!-- end col-->
|
||||
</div> <!-- end row-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<CaptureModal>
|
||||
<Modal modalId="new-email">
|
||||
<NewEmailModalContent />
|
||||
</Modal>
|
||||
</CaptureModal>
|
||||
</DefaultLayout>
|
||||
|
||||
+33
-21
@@ -1,16 +1,21 @@
|
||||
---
|
||||
// Masonry auto-inits from the data-masonry attribute (page-lib, no init script,
|
||||
// like cards-masonry.astro); fslightbox is a pure page-lib (no init script).
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import CardStamp from '@ui/CardStamp.astro'
|
||||
import Prose from '@ui/Prose.astro'
|
||||
import { site } from '@shared/lib/site'
|
||||
import emails from '@data/emails.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import CardStamp from '@ui/CardStamp.astro';
|
||||
import Prose from '@ui/Prose.astro';
|
||||
import CaptureScript from '@shared/components/CaptureScript.astro';
|
||||
import { site } from '@shared/lib/site';
|
||||
import emails from '@data/emails.json';
|
||||
|
||||
const emailEntries = Object.entries(emails)
|
||||
const emailEntries = Object.entries(emails);
|
||||
---
|
||||
|
||||
<DefaultLayout pageHeader="Email templates" pageMenu="addons.emails" pageLibs={['masonry', 'fslightbox']}>
|
||||
<DefaultLayout
|
||||
pageHeader="Email templates"
|
||||
pageMenu="addons.emails"
|
||||
pageLibs={['masonry', 'fslightbox']}
|
||||
>
|
||||
<div class="row row-cards">
|
||||
<div class="col-12">
|
||||
<div class="card card-md">
|
||||
@@ -32,15 +37,20 @@ const emailEntries = Object.entries(emails)
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="row row-cards" data-masonry={'{"percentPosition": true }'}>
|
||||
{
|
||||
emailEntries.map(([key, email]) => (
|
||||
{emailEntries.map(([key, email]) => (
|
||||
<div class="col-4">
|
||||
<a href={`./static/emails/${key}.jpg`} data-bs-toggle="modal" data-bs-target="#email-modal" data-bs-title={email.descriptionShort} data-bs-description={email.descriptionLong} data-bs-image={`./static/emails/${key}.jpg`}>
|
||||
<a
|
||||
href={`./static/emails/${key}.jpg`}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#email-modal"
|
||||
data-bs-title={email.descriptionShort}
|
||||
data-bs-description={email.descriptionLong}
|
||||
data-bs-image={`./static/emails/${key}.jpg`}
|
||||
>
|
||||
<img src={`./static/emails/${key}.jpg`} class="img-fluid rounded" alt={email.descriptionShort} width={email.width} height={email.height} />
|
||||
</a>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,23 +83,25 @@ const emailEntries = Object.entries(emails)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaptureScript>
|
||||
<!-- BEGIN EMAIL MODAL SCRIPT -->
|
||||
<script>
|
||||
const emailModal = document.getElementById('email-modal')
|
||||
<script is:inline>
|
||||
const emailModal = document.getElementById('email-modal');
|
||||
if (emailModal) {
|
||||
emailModal.addEventListener('show.bs.modal', function (e) {
|
||||
const button = (e as Event & { relatedTarget?: EventTarget | null }).relatedTarget
|
||||
if (!(button instanceof HTMLElement)) return
|
||||
const button = e.relatedTarget;
|
||||
if (!(button instanceof HTMLElement)) return;
|
||||
|
||||
const image = button.getAttribute('data-bs-image'),
|
||||
title = button.getAttribute('data-bs-title'),
|
||||
description = button.getAttribute('data-bs-description')
|
||||
description = button.getAttribute('data-bs-description');
|
||||
|
||||
emailModal.querySelector('[data-email-title]')!.textContent = title
|
||||
emailModal.querySelector('[data-email-description]')!.textContent = description
|
||||
;(emailModal.querySelector('[data-email-image]') as HTMLImageElement).src = image ?? ''
|
||||
})
|
||||
emailModal.querySelector('[data-email-title]').textContent = title;
|
||||
emailModal.querySelector('[data-email-description]').textContent = description;
|
||||
emailModal.querySelector('[data-email-image]').src = image;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<!-- END EMAIL MODAL SCRIPT -->
|
||||
</CaptureScript>
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardHeader from '@ui/CardHeader.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import flags from '@data/flags.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardHeader from '@ui/CardHeader.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import flags from '@data/flags.json';
|
||||
|
||||
// Liquid: {% for icon in (0..20) %}<div></div>{% endfor %} — 21 filler divs
|
||||
const fillers = Array.from({ length: 21 })
|
||||
// 21 filler divs for flexbox layout.
|
||||
const fillers = Array.from({ length: 21 });
|
||||
---
|
||||
|
||||
<DefaultLayout title="Flags" pageHeader="Flags" pageMenu="addons.flags">
|
||||
@@ -19,12 +19,17 @@ const fillers = Array.from({ length: 21 })
|
||||
<div class="demo-icons-list">
|
||||
{
|
||||
flags.map((country) => (
|
||||
<span class="demo-icons-list-item" title={country.name} data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<span
|
||||
class="demo-icons-list-item"
|
||||
title={country.name}
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
>
|
||||
<span class={`flag flag-country-${country.flag.toLowerCase()}`} />
|
||||
</span>
|
||||
))
|
||||
}
|
||||
{fillers.map(() => <div />)}
|
||||
{fillers.map(() => <div></div>)}
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import GalleryPhoto from '@shared/components/cards/GalleryPhoto.astro'
|
||||
import Pagination from '@ui/Pagination.astro'
|
||||
import photos from '@data/photos.json'
|
||||
import people from '@data/people.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import GalleryPhoto from '@shared/components/cards/GalleryPhoto.astro';
|
||||
import Pagination from '@ui/Pagination.astro';
|
||||
import photos from '@data/photos.json';
|
||||
import people from '@data/people.json';
|
||||
|
||||
// Liquid: photos | where: "horizontal", true — limit: 15; person = people[forloop.index0]
|
||||
const galleryPhotos = photos.filter((photo) => photo.horizontal).slice(0, 15)
|
||||
// Horizontal photos, limit 15; person = people[loop index].
|
||||
const galleryPhotos = photos.filter((photo) => photo.horizontal).slice(0, 15);
|
||||
---
|
||||
|
||||
<DefaultLayout title="Gallery" pageHeader="Gallery" description="1-15 of 241 photos" actions="photos" pageMenu="extra.gallery">
|
||||
<DefaultLayout
|
||||
title="Gallery"
|
||||
pageHeader="Gallery"
|
||||
description="1-15 of 241 photos"
|
||||
actions="photos"
|
||||
pageMenu="extra.gallery"
|
||||
>
|
||||
<div class="row row-cards">
|
||||
{
|
||||
galleryPhotos.map((photo, index) => (
|
||||
|
||||
@@ -1,42 +1,50 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardStamp from '@ui/CardStamp.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import Prose from '@ui/Prose.astro'
|
||||
import freeIllustrations from '@data/free-illustrations.json'
|
||||
import illustrationsList from '@data/illustrations.json'
|
||||
import siteData from '@data/site.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import CardStamp from '@ui/CardStamp.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import Prose from '@ui/Prose.astro';
|
||||
import CaptureScript from '@shared/components/CaptureScript.astro';
|
||||
import freeIllustrations from '@data/free-illustrations.json';
|
||||
import illustrationsList from '@data/illustrations.json';
|
||||
import siteData from '@data/site.json';
|
||||
|
||||
const autodark = (freeIllustrations as { autodark: Record<string, string> }).autodark
|
||||
const autodarkEntries = Object.entries(autodark)
|
||||
const autodark = (freeIllustrations as { autodark: Record<string, string> }).autodark;
|
||||
const autodarkEntries = Object.entries(autodark);
|
||||
|
||||
// first-illustration: the Liquid loop overwrites each pass → last value wins.
|
||||
const firstIllustration = autodarkEntries.length ? autodarkEntries[autodarkEntries.length - 1][1] : ''
|
||||
// Last autodark entry (loop overwrites each pass).
|
||||
const firstIllustration = autodarkEntries.length
|
||||
? autodarkEntries[autodarkEntries.length - 1][1]
|
||||
: '';
|
||||
|
||||
// Page-local transform: replace: '<svg ' , '<svg class="w-100 h-auto" '
|
||||
const withClass = (svg: string) => svg.replaceAll('<svg ', '<svg class="w-100 h-auto" ')
|
||||
const withClass = (svg: string) => svg.replaceAll('<svg ', '<svg class="w-100 h-auto" ');
|
||||
|
||||
const colors = siteData.colors as Record<string, { hex: string; class: string; prop: string }>
|
||||
const skinColors = siteData.skinColors as Record<string, { hex: string; class: string }>
|
||||
const colorEntries = Object.values(colors)
|
||||
const skinEntries = Object.values(skinColors)
|
||||
const colors = siteData.colors as Record<string, { hex: string; class: string; prop: string }>;
|
||||
const skinColors = siteData.skinColors as Record<string, { hex: string; class: string }>;
|
||||
const colorEntries = Object.values(colors);
|
||||
const skinEntries = Object.values(skinColors);
|
||||
|
||||
// skinColor = site.skinColors | first → the first value object (Rose).
|
||||
const skinFirst = skinEntries[0]
|
||||
const buyLink = (siteData.illustrations as { buy_link: string }).buy_link
|
||||
const skinFirst = skinEntries[0];
|
||||
const buyLink = (siteData.illustrations as { buy_link: string }).buy_link;
|
||||
|
||||
// {{ illustrations | size | minus: 4 }}
|
||||
const moreCount = illustrationsList.length - 4
|
||||
const moreCount = illustrationsList.length - 4;
|
||||
|
||||
// {% capture_script %} — build the illustrations JS map from the same data.
|
||||
// skin_color / color[1].prop resolve to empty in Liquid → literal "var()".
|
||||
const illustrationsData = Object.fromEntries(autodarkEntries.map(([key, svg]) => [key, { svg: withClass(svg) }]))
|
||||
const illustrationsData = Object.fromEntries(
|
||||
autodarkEntries.map(([key, svg]) => [key, { svg: withClass(svg) }]),
|
||||
);
|
||||
---
|
||||
|
||||
<DefaultLayout title="SVG Illustrations" pageHeader="SVG Illustrations" pageMenu="addons.illustrations">
|
||||
<div class="mb-7" style={`--tblr-illustrations-primary: var(--tblr-color-primary); --tblr-illustrations-skin: ${skinFirst.hex};`} id="current-illustration-style">
|
||||
<DefaultLayout
|
||||
title="SVG Illustrations"
|
||||
pageHeader="SVG Illustrations"
|
||||
pageMenu="addons.illustrations"
|
||||
>
|
||||
<div
|
||||
class="mb-7"
|
||||
style={`--tblr-illustrations-primary: var(--tblr-color-primary); --tblr-illustrations-skin: ${skinFirst.hex};`}
|
||||
id="current-illustration-style"
|
||||
>
|
||||
<div class="row row-cards">
|
||||
<div class="col-12">
|
||||
<div class="row row-cards row-deck g-4">
|
||||
@@ -129,7 +137,9 @@ const illustrationsData = Object.fromEntries(autodarkEntries.map(([key, svg]) =>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-10">
|
||||
<h3 class="h1">Tabler Illustrations</h3>
|
||||
<Prose class="text-secondary"> Access a wide range of SVG illustrations for various projects. Effortlessly customize any illustration to align perfectly with your chosen color scheme! </Prose>
|
||||
<Prose class="text-secondary">
|
||||
Access a wide range of SVG illustrations for various projects. Effortlessly customize any illustration to align perfectly with your chosen color scheme!
|
||||
</Prose>
|
||||
<div class="mt-3">
|
||||
<a href={buyLink} class="btn btn-primary" target="_blank" rel="noopener">
|
||||
<Icon name="download" />
|
||||
@@ -160,36 +170,38 @@ const illustrationsData = Object.fromEntries(autodarkEntries.map(([key, svg]) =>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaptureScript>
|
||||
<!-- BEGIN ILLUSTRATIONS SCRIPT -->
|
||||
<script is:inline define:vars={{ illustrationsData }}>
|
||||
// @ts-nocheck — illustrationsData is injected at runtime by define:vars, which
|
||||
// the type checker can't see since is:inline skips TS processing of this script.
|
||||
let skinColor = 'var()',
|
||||
primaryColor = 'var()'
|
||||
primaryColor = 'var()';
|
||||
|
||||
const illustrations = illustrationsData
|
||||
const currentIllustration = document.getElementById('current-illustration')
|
||||
const illustrations = illustrationsData;
|
||||
const currentIllustration = document.getElementById('current-illustration');
|
||||
|
||||
document.querySelectorAll('.js-select-illustration').forEach((elem) => {
|
||||
elem.addEventListener('change', (e) => {
|
||||
const selectedIllustration = illustrations[e.target.value]
|
||||
currentIllustration.innerHTML = selectedIllustration.svg
|
||||
})
|
||||
})
|
||||
const selectedIllustration = illustrations[e.target.value];
|
||||
currentIllustration.innerHTML = selectedIllustration.svg;
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-select-color').forEach((elem) => {
|
||||
elem.addEventListener('change', (e) => {
|
||||
primaryColor = e.target.value
|
||||
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-primary', primaryColor)
|
||||
})
|
||||
})
|
||||
primaryColor = e.target.value;
|
||||
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-primary', primaryColor);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.js-select-skin-color').forEach((elem) => {
|
||||
elem.addEventListener('change', (e) => {
|
||||
skinColor = e.target.value
|
||||
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-skin', skinColor)
|
||||
})
|
||||
})
|
||||
skinColor = e.target.value;
|
||||
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-skin', skinColor);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- END ILLUSTRATIONS SCRIPT -->
|
||||
</CaptureScript>
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
---
|
||||
// Liquid: {% for provider in inline-players %} → one card per provider.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import InlinePlayer from '@ui/InlinePlayer.astro'
|
||||
import players from '@data/inline-players.json'
|
||||
// One card per inline-player provider.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import InlinePlayer from '@ui/InlinePlayer.astro';
|
||||
import players from '@data/inline-players.json';
|
||||
|
||||
type Provider = { 'title': string; 'type': string; 'id': string; 'embed-id': string | number }
|
||||
type Provider = { title: string; type: string; id: string; 'embed-id': string | number };
|
||||
---
|
||||
|
||||
<DefaultLayout title="Inline Player" pageHeader="Inline Player" pageMenu="plugins.plyr" pageLibs={['plyr']}>
|
||||
<DefaultLayout
|
||||
title="Inline Player"
|
||||
pageHeader="Inline Player"
|
||||
pageMenu="plugins.plyr"
|
||||
pageLibs={['plyr']}
|
||||
>
|
||||
<div class="row row-cards">
|
||||
{
|
||||
(players as Provider[]).map((provider) => (
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
---
|
||||
// NOTE: page-header-actions "add-job" requires PageHeader.astro to dispatch it
|
||||
// to HeaderActionsAddJob — see the migration report (PageHeader.astro is a
|
||||
// shared component and was not modified here).
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Check from '@ui/form/Check.astro'
|
||||
import jobsData from '@data/jobs.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Check from '@ui/form/Check.astro';
|
||||
import jobsData from '@data/jobs.json';
|
||||
|
||||
interface Job {
|
||||
company: string
|
||||
location: string
|
||||
title: string
|
||||
type: string
|
||||
image: string
|
||||
salary?: string
|
||||
tags: string[]
|
||||
company: string;
|
||||
location: string;
|
||||
title: string;
|
||||
type: string;
|
||||
image: string;
|
||||
salary?: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const jobs = jobsData as Job[]
|
||||
const jobs = jobsData as Job[];
|
||||
|
||||
const types = ['Programming', 'Design', 'Management / Finance', 'Customer Support', 'Sales / Marketing']
|
||||
const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting']
|
||||
const types = ['Programming', 'Design', 'Management / Finance', 'Customer Support', 'Sales / Marketing'];
|
||||
const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting'];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Search for Jobs" pageHeader="Search for Jobs" actions="add-job" pageMenu="extra.job-listing">
|
||||
@@ -77,8 +74,12 @@ const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting'
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<button class="btn btn-primary w-100"> Confirm changes </button>
|
||||
<a href="#" class="btn btn-link w-100"> Reset to defaults </a>
|
||||
<button class="btn btn-primary w-100">
|
||||
Confirm changes
|
||||
</button>
|
||||
<a href="#" class="btn btn-link w-100">
|
||||
Reset to defaults
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -132,9 +133,7 @@ const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting'
|
||||
<div class="col-md-auto">
|
||||
<div class="mt-3 badges">
|
||||
{job.tags.map((tag) => (
|
||||
<a href="#" class="badge badge-outline text-secondary fw-normal badge-pill">
|
||||
{tag}
|
||||
</a>
|
||||
<a href="#" class="badge badge-outline text-secondary fw-normal badge-pill">{tag}</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+14
-14
@@ -1,16 +1,10 @@
|
||||
---
|
||||
// The prose is shared/includes/license.md rendered via `renderContent: "md"`
|
||||
// (markdown-it). The rendered HTML is inlined verbatim from the reference build
|
||||
// to guarantee a 1:1 DOM match — Astro's markdown pipeline (remark + smartypants)
|
||||
// would produce different quote characters and structure.
|
||||
// TODO: source of truth remains shared/includes/license.md; regenerate this
|
||||
// block if the markdown changes.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardFooter from '@ui/CardFooter.astro'
|
||||
import Prose from '@ui/Prose.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardFooter from '@ui/CardFooter.astro';
|
||||
import Prose from '@ui/Prose.astro';
|
||||
|
||||
const licenseHtml = `
|
||||
<p>This is a legal agreement between you, the Purchaser, and Tabler. Purchasing or downloading of any Tabler product (Tabler Free, Tabler PRO, Tabler Email), constitutes your acceptance of the terms of this license, <a href="https://tabler.io/terms-of-service.html">Tabler terms of service</a> and <a href="https://tabler.io/privacy-policy.html">Tabler private policy</a>.</p>
|
||||
@@ -37,7 +31,7 @@ const licenseHtml = `
|
||||
<li>You cannot add our source code to any open source repository.</li>
|
||||
<li>The source code may not be placed on any website in a complete or archived downloadable format.</li>
|
||||
</ol>
|
||||
`
|
||||
`;
|
||||
---
|
||||
|
||||
<DefaultLayout title="License" pageHeader="Tabler License" pageMenu="extra.license">
|
||||
@@ -64,7 +58,12 @@ const licenseHtml = `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-secondary mb-3">A short and simple permissive license with conditions only requiring preservation of copyright and license notices. Licensed works, modifications, and larger works may be distributed under different terms and without source code.</div>
|
||||
<div class="text-secondary mb-3">
|
||||
A short and simple permissive license with conditions only requiring preservation of copyright and
|
||||
license notices. Licensed works, modifications, and larger works may be distributed under different terms
|
||||
and without source code.
|
||||
</div>
|
||||
|
||||
|
||||
<h4>Permissions</h4>
|
||||
|
||||
@@ -75,6 +74,7 @@ const licenseHtml = `
|
||||
<li><Icon name="check" class="text-green" /> Private use</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h4>Limitations</h4>
|
||||
<ul class="list-unstyled space-y-1">
|
||||
<li><Icon name="x" class="text-red" /> Liability</li>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
---
|
||||
// Liquid: photos | where: "horizontal", true — the gallery iterates the filtered
|
||||
// list for both the fslightbox href and the Photo include.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Photo from '@ui/Photo.astro'
|
||||
import photos from '@data/photos.json'
|
||||
// Gallery uses horizontal photos only.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Photo from '@ui/Photo.astro';
|
||||
import photos from '@data/photos.json';
|
||||
|
||||
const filteredPhotos = photos.filter((photo) => photo.horizontal)
|
||||
const filteredPhotos = photos.filter((photo) => photo.horizontal);
|
||||
---
|
||||
|
||||
<DefaultLayout title="Lightbox" pageHeader="Lightbox" pageMenu="plugins.lightbox" pageLibs={['fslightbox']}>
|
||||
<DefaultLayout
|
||||
title="Lightbox"
|
||||
pageHeader="Lightbox"
|
||||
pageMenu="plugins.lightbox"
|
||||
pageLibs={['fslightbox']}
|
||||
>
|
||||
<div class="row row-cols-3 row-cols-md-4 row-cols-lg-6 g-3">
|
||||
{
|
||||
filteredPhotos.map((photo) => (
|
||||
|
||||
@@ -1,40 +1,37 @@
|
||||
---
|
||||
// Front matter: layout-wrapper-full + layout-sidebar + layout-hide-topbar,
|
||||
// page-libs: [google-maps], page-menu: plugins.map-fullsize. No title / no
|
||||
// page-header (the page-header block renders empty in the reference).
|
||||
//
|
||||
// {% assign map-id = 'google' %} → id="map-google".
|
||||
// The {% capture_script %} block is registered synchronously (before any await).
|
||||
// We mirror the development build (environment == 'development'), so the
|
||||
// `window.tabler_map` bookkeeping lines are emitted (as in the reference).
|
||||
//
|
||||
// REQUIRES DefaultLayout `wrapperFull` support (see report): page-wrapper-full
|
||||
// class + slot rendered WITHOUT the .container-xl wrapper. Not implemented in
|
||||
// the shared DefaultLayout yet — flagged rather than modified here.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import CaptureScript from '@shared/components/CaptureScript.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout pageMenu="plugins.map-fullsize" pageLibs={['google-maps']} sidebar hideTopbar wrapperFull>
|
||||
<DefaultLayout
|
||||
pageMenu="plugins.map-fullsize"
|
||||
pageLibs={['google-maps']}
|
||||
sidebar
|
||||
hideTopbar
|
||||
wrapperFull
|
||||
>
|
||||
<div class="map flex-fill" id="map-google"></div>
|
||||
<!--
|
||||
is:inline: google-maps loads via a deferred page-lib <script>; see
|
||||
FormElements1.astro for why a processed (module) script here would run too
|
||||
early and find window.google undefined.
|
||||
-->
|
||||
<CaptureScript>
|
||||
<!-- BEGIN MAP SCRIPT -->
|
||||
<script is:inline>
|
||||
window.tabler_map ??= {}
|
||||
window.tabler_map ??= {};
|
||||
|
||||
function initMap() {
|
||||
const map = new google.maps.Map(document.getElementById('map-google'), {
|
||||
center: { lat: -34.397, lng: 150.644 },
|
||||
zoom: 8,
|
||||
})
|
||||
});
|
||||
|
||||
window.tabler_map['map-google'] = map
|
||||
window.tabler_map['map-google'] = map;
|
||||
}
|
||||
|
||||
document.readyState !== 'loading' ? initMap() : document.addEventListener('DOMContentLoaded', initMap, { once: true })
|
||||
document.readyState !== 'loading' ? initMap() : document.addEventListener('DOMContentLoaded', initMap, { once: true });
|
||||
</script>
|
||||
<!-- END MAP SCRIPT -->
|
||||
</CaptureScript>
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
---
|
||||
// Liquid: {% for map in maps %} — card maps span a full-width column with no
|
||||
// card-body; non-card maps get a card-body with a title.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Map from '@ui/Map.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import maps from '@data/maps.json'
|
||||
// Card maps span full-width column with no card-body; non-card maps get card-body with title.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Map from '@ui/Map.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import maps from '@data/maps.json';
|
||||
|
||||
type MapData = { title?: string; card?: boolean }
|
||||
const mapEntries = Object.entries(maps as Record<string, MapData>)
|
||||
type MapData = { title?: string; card?: boolean };
|
||||
const mapEntries = Object.entries(maps as Record<string, MapData>);
|
||||
---
|
||||
|
||||
<DefaultLayout title="Maps" pageHeader="Maps" pageMenu="plugins.maps" pageLibs={['mapbox']}>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
---
|
||||
import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
|
||||
|
||||
// Front matter in preview/pages/markdown.html sets `redirect.to: prose.html`,
|
||||
// but shared/includes/redirect.html reads `page.redirect.to` — and in Eleventy
|
||||
// `page` is the built-in page object (no front matter), so the include gets an
|
||||
// empty url. The reference build (markdown.html) therefore redirects to the
|
||||
// relative root ".". We reproduce that exactly by passing no url.
|
||||
import RedirectLayout from '@shared/layouts/RedirectLayout.astro';
|
||||
---
|
||||
|
||||
<RedirectLayout />
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
---
|
||||
// The `description` front matter is not emitted by the base layout (no
|
||||
// page-specific <meta name="description"> in the reference build) — no prop.
|
||||
import MarketingLayout from '@shared/layouts/MarketingLayout.astro'
|
||||
import Pricing from '@shared/components/marketing/sections/Pricing.astro'
|
||||
import PricingBanner from '@shared/components/marketing/sections/PricingBanner.astro'
|
||||
import Faq from '@shared/components/marketing/sections/Faq.astro'
|
||||
import MarketingLayout from '@shared/layouts/MarketingLayout.astro';
|
||||
import Pricing from '@shared/components/marketing/sections/Pricing.astro';
|
||||
import PricingBanner from '@shared/components/marketing/sections/PricingBanner.astro';
|
||||
import Faq from '@shared/components/marketing/sections/Faq.astro';
|
||||
---
|
||||
|
||||
<MarketingLayout title="Pricing">
|
||||
|
||||
+31
-26
@@ -1,34 +1,39 @@
|
||||
---
|
||||
// Every modal is rendered inline (`{% include "ui/modal.html" ... inline show %}`)
|
||||
// via ModalInline (renders in place, unlike Modal.astro which drains to <body>).
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import ModalInline from '@shared/components/modals/ModalInline.astro'
|
||||
import SimpleModalContent from '@shared/components/modals/SimpleModalContent.astro'
|
||||
import LargeModalContent from '@shared/components/modals/LargeModalContent.astro'
|
||||
import SmallModalContent from '@shared/components/modals/SmallModalContent.astro'
|
||||
import FullWidthModalContent from '@shared/components/modals/FullWidthModalContent.astro'
|
||||
import ScrollableModalContent from '@shared/components/modals/ScrollableModalContent.astro'
|
||||
import ReportModalContent from '@shared/components/modals/ReportModalContent.astro'
|
||||
import SuccessModalContent from '@shared/components/modals/SuccessModalContent.astro'
|
||||
import DangerModalContent from '@shared/components/modals/DangerModalContent.astro'
|
||||
import TeamModalContent from '@shared/components/modals/TeamModalContent.astro'
|
||||
import SignatureModalContent from '@shared/components/modals/SignatureModalContent.astro'
|
||||
import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro'
|
||||
import NewEventModalContent from '@shared/components/modals/NewEventModalContent.astro'
|
||||
import NewTaskModalContent from '@shared/components/modals/NewTaskModalContent.astro'
|
||||
import EditProfileModalContent from '@shared/components/modals/EditProfileModalContent.astro'
|
||||
import ConfirmDeleteModalContent from '@shared/components/modals/ConfirmDeleteModalContent.astro'
|
||||
import ChangePasswordModalContent from '@shared/components/modals/ChangePasswordModalContent.astro'
|
||||
import AddTaskModalContent from '@shared/components/modals/AddTaskModalContent.astro'
|
||||
// Every modal is rendered inline via ModalInline (in place, unlike Modal.astro
|
||||
// which drains to <body>).
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import ModalInline from '@shared/components/modals/ModalInline.astro';
|
||||
import SimpleModalContent from '@shared/components/modals/SimpleModalContent.astro';
|
||||
import LargeModalContent from '@shared/components/modals/LargeModalContent.astro';
|
||||
import SmallModalContent from '@shared/components/modals/SmallModalContent.astro';
|
||||
import FullWidthModalContent from '@shared/components/modals/FullWidthModalContent.astro';
|
||||
import ScrollableModalContent from '@shared/components/modals/ScrollableModalContent.astro';
|
||||
import ReportModalContent from '@shared/components/modals/ReportModalContent.astro';
|
||||
import SuccessModalContent from '@shared/components/modals/SuccessModalContent.astro';
|
||||
import DangerModalContent from '@shared/components/modals/DangerModalContent.astro';
|
||||
import TeamModalContent from '@shared/components/modals/TeamModalContent.astro';
|
||||
import SignatureModalContent from '@shared/components/modals/SignatureModalContent.astro';
|
||||
import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro';
|
||||
import NewEventModalContent from '@shared/components/modals/NewEventModalContent.astro';
|
||||
import NewTaskModalContent from '@shared/components/modals/NewTaskModalContent.astro';
|
||||
import EditProfileModalContent from '@shared/components/modals/EditProfileModalContent.astro';
|
||||
import ConfirmDeleteModalContent from '@shared/components/modals/ConfirmDeleteModalContent.astro';
|
||||
import ChangePasswordModalContent from '@shared/components/modals/ChangePasswordModalContent.astro';
|
||||
import AddTaskModalContent from '@shared/components/modals/AddTaskModalContent.astro';
|
||||
|
||||
const cardClass = 'position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0'
|
||||
const cardClass = 'position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0';
|
||||
// add-task bakes `show` into the class string (no `show` flag) → aria-hidden="true".
|
||||
const addTaskClass = 'position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0'
|
||||
const addTaskClass = 'position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Modals" pageHeader="Modals" pageMenu="base.modals" pageLibs={['signature_pad', 'hugerte', 'litepicker']}>
|
||||
<DefaultLayout
|
||||
title="Modals"
|
||||
pageHeader="Modals"
|
||||
pageMenu="base.modals"
|
||||
pageLibs={['signature_pad', 'hugerte', 'litepicker']}
|
||||
>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<div class="row g-5">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import TracksList from '@shared/components/cards/music/TracksList.astro'
|
||||
import TrackInfo from '@shared/components/cards/music/TrackInfo.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import TracksList from '@shared/components/cards/music/TracksList.astro';
|
||||
import TrackInfo from '@shared/components/cards/music/TrackInfo.astro';
|
||||
|
||||
// Liquid: {% for i in (8..13) %}
|
||||
const topTrackIds = [8, 9, 10, 11, 12, 13]
|
||||
const topTrackIds = [8, 9, 10, 11, 12, 13];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Music components" pageHeader="Music components" pageMenu="extra.music">
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
// with variant params, rendered with the shared Navbar component.
|
||||
//
|
||||
// Note: fluid-search (variant 5) is a no-op — the search block in the Liquid
|
||||
// condensed branch is dead code (`unless condensed` inside `if condensed`);
|
||||
// the prop exists on Navbar only to mirror the include signature.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Navbar from '@shared/components/navbar/Navbar.astro'
|
||||
// fluid-search (variant 5) is a no-op — search block in condensed branch is dead code.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Navbar from '@shared/components/navbar/Navbar.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Navigation" pageHeader="Navigation" pageMenu="base.navigation">
|
||||
@@ -27,7 +25,17 @@ import Navbar from '@shared/components/navbar/Navbar.astro'
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<Navbar sample condensed dark background="primary" hideBrand hideIcons fluidSearch hideUsername personId={7} />
|
||||
<Navbar
|
||||
sample
|
||||
condensed
|
||||
dark
|
||||
background="primary"
|
||||
hideBrand
|
||||
hideIcons
|
||||
fluidSearch
|
||||
hideUsername
|
||||
personId={7}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
---
|
||||
// NOTE: the Liquid source passes `current=2` to ui/progress-steps.html, but that
|
||||
// include reads `active` (not `current`) — so the arg is a no-op and only the
|
||||
// first step is active. The reference reflects this (Step 1 = bg-primary).
|
||||
import BaseLayout from '@shared/layouts/BaseLayout.astro'
|
||||
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
|
||||
import ProgressSteps from '@ui/ProgressSteps.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
// NOTE: `current=2` passed to ProgressSteps is a no-op (reads `active`, not `current`) — only step 1 is active.
|
||||
import BaseLayout from '@shared/layouts/BaseLayout.astro';
|
||||
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
|
||||
import ProgressSteps from '@ui/ProgressSteps.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Onboarding">
|
||||
|
||||
+57
-16
@@ -1,12 +1,10 @@
|
||||
---
|
||||
// page-libs [tabler-payments, imask]: `tabler-payments` is not a key in
|
||||
// core/libs.json, so it resolves to nothing (only imask emits a <script>) —
|
||||
// matches the Eleventy reference.
|
||||
import PayLayout from '@shared/layouts/PayLayout.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Payment from '@ui/Payment.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
// tabler-payments is not in core/libs.json — only imask emits a script tag.
|
||||
import PayLayout from '@shared/layouts/PayLayout.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Payment from '@ui/Payment.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
---
|
||||
|
||||
<PayLayout title="Pay" pageLibs={['tabler-payments', 'imask']}>
|
||||
@@ -25,16 +23,33 @@ import Icon from '@ui/Icon.astro'
|
||||
<Avatar personId={1} size="xl" class="avatar-cover rounded-circle mb-3" />
|
||||
<div class="mb-4">
|
||||
<h2 class="h2">Front-End Learning</h2>
|
||||
<div class="text-secondary">Learn to build web apps with HTML & CSS. Get started quickly with included templates.</div>
|
||||
<div class="text-secondary">
|
||||
Learn to build web apps with HTML & CSS. Get started quickly with included templates.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<nav class="nav nav-segmented w-100 mb-4" role="tablist">
|
||||
<button data-bs-target="#tab-card" class="nav-link active" data-bs-toggle="tab" role="tab" aria-controls="tab-card" aria-selected="true">
|
||||
<button
|
||||
data-bs-target="#tab-card"
|
||||
class="nav-link active"
|
||||
data-bs-toggle="tab"
|
||||
role="tab"
|
||||
aria-controls="tab-card"
|
||||
aria-selected="true"
|
||||
>
|
||||
<Icon name="credit-card" />
|
||||
<span>Pay With Card</span>
|
||||
</button>
|
||||
<button data-bs-target="#tab-paypal" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="tab-paypal" aria-selected="false">
|
||||
<button
|
||||
data-bs-target="#tab-paypal"
|
||||
class="nav-link"
|
||||
data-bs-toggle="tab"
|
||||
role="tab"
|
||||
aria-controls="tab-paypal"
|
||||
aria-selected="false"
|
||||
>
|
||||
<Icon name="brand-paypal" />
|
||||
<span>Pay With PayPal</span>
|
||||
</button>
|
||||
@@ -49,16 +64,38 @@ import Icon from '@ui/Icon.astro'
|
||||
<span class="input-group-text">
|
||||
<Payment payment="visa" size="xs" />
|
||||
</span>
|
||||
<input type="text" class="form-control" placeholder="0000 0000 0000 0000" autocomplete="off" id="card-number-addon" />
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="0000 0000 0000 0000"
|
||||
autocomplete="off"
|
||||
id="card-number-addon"
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<div class="row g-3">
|
||||
<FormGroup label="Expiry Date" for="card-expiry" class="col-sm-6">
|
||||
<input type="text" class="form-control" id="card-expiry" placeholder="MM/YY" inputmode="numeric" aria-required="true" maxlength="5" />
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="card-expiry"
|
||||
placeholder="MM/YY"
|
||||
inputmode="numeric"
|
||||
aria-required="true"
|
||||
maxlength="5"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="CVC" for="card-cvc" class="col-sm-6">
|
||||
<input type="text" class="form-control" id="card-cvc" placeholder="CVC" inputmode="numeric" aria-required="true" maxlength="3" />
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="card-cvc"
|
||||
placeholder="CVC"
|
||||
inputmode="numeric"
|
||||
aria-required="true"
|
||||
maxlength="3"
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +109,9 @@ import Icon from '@ui/Icon.astro'
|
||||
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary w-100"> Pay $253 </button>
|
||||
<div class="text-secondary text-center small mt-3">You'll be charged $253, including $48 for VAT in Poland</div>
|
||||
<div class="text-secondary text-center small mt-3">
|
||||
You'll be charged $253, including $48 for VAT in Poland
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -83,7 +122,9 @@ import Icon from '@ui/Icon.astro'
|
||||
<Icon name="brand-paypal" />
|
||||
<span class="ms-2">Pay with PayPal - $253</span>
|
||||
</button>
|
||||
<div class="text-secondary text-center small mt-3">You'll be charged $253, including $48 for VAT in Poland</div>
|
||||
<div class="text-secondary text-center small mt-3">
|
||||
You'll be charged $253, including $48 for VAT in Poland
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
---
|
||||
// Reuses committed components: Icon, Button, Badge, Pagination (+ Avatar).
|
||||
//
|
||||
// NOTE (no-container): the front matter is `no-container: true`, but Liquid's
|
||||
// `page.no-container` (hyphen key via dot access) does NOT resolve, so the
|
||||
// reference build renders the normal `.container-xl` wrapper (default slot).
|
||||
// We therefore use DefaultLayout unchanged — no special no-container handling.
|
||||
//
|
||||
// NOTE (page-menu): none in the front matter → pageMenu is intentionally NOT
|
||||
// passed.
|
||||
//
|
||||
// TODO (Button.astro missing params): the two sort-direction buttons use
|
||||
// `element="label"` + `html_for` + `tooltip`, none of which Button.astro
|
||||
// supports (element type is 'a'|'button'; html_for/tooltip absent). The shared
|
||||
// ui/button.html ignores html_for/tooltip too, and the reference emits a plain
|
||||
// <label> with no `for`. Inlined below to match the reference.
|
||||
|
||||
import ButtonGroup from '@ui/ButtonGroup.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import Badge from '@ui/Badge.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import AvatarList from '@ui/AvatarList.astro'
|
||||
import Pagination from '@ui/Pagination.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import ButtonGroup from '@ui/ButtonGroup.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import Badge from '@ui/Badge.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import AvatarList from '@ui/AvatarList.astro';
|
||||
import Pagination from '@ui/Pagination.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Categories playground">
|
||||
@@ -47,7 +31,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<div class="row align-items-center gx-3">
|
||||
<div class="col col-lg-auto">
|
||||
<div class="input-group input-group-flat rounded-pill">
|
||||
<input type="text" class="form-control" placeholder="Search" aria-label="Search" aria-describedby="playground-categories-search" />
|
||||
<input type="text" class="form-control" placeholder="Search" aria-label="Search" aria-describedby="playground-categories-search">
|
||||
<span class="input-group-text" id="playground-categories-search">
|
||||
<Icon name="search" />
|
||||
</span>
|
||||
@@ -119,9 +103,9 @@ import CardBody from '@ui/CardBody.astro'
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<ButtonGroup aria-label="Sort direction">
|
||||
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortAsc" autocomplete="off" checked />
|
||||
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortAsc" autocomplete="off" checked>
|
||||
<label class="btn btn-light px-0 btn-icon" aria-label="Ascending"><Icon name="arrow-up" /></label>
|
||||
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortDesc" autocomplete="off" />
|
||||
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortDesc" autocomplete="off">
|
||||
<label class="btn btn-light px-0 btn-icon" aria-label="Descending"><Icon name="arrow-down" /></label>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
@@ -137,7 +121,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-7" />
|
||||
<hr class="my-7">
|
||||
|
||||
<div class="row row-cards">
|
||||
<div class="col-12 col-lg-6">
|
||||
@@ -154,7 +138,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Announcements</a></h3>
|
||||
<p class="text-secondary text-truncate">For company updates, system changes, or important notices that everyone should see.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 2 days ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true}>
|
||||
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
|
||||
@@ -183,7 +167,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Marketing campaigns</a></h3>
|
||||
<p class="text-secondary text-truncate">Posts related to new campaigns, marketing strategies, and promotions.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 1 day ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true} size="xs" offset={3} limit={3} />
|
||||
<div class="text-end">
|
||||
@@ -202,7 +186,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Reports & analytics</a></h3>
|
||||
<p class="text-secondary text-truncate">Data-driven insights and performance reports across all departments.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 5 days ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true}>
|
||||
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
|
||||
@@ -224,7 +208,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Events & webinars</a></h3>
|
||||
<p class="text-secondary text-truncate">Upcoming events, webinar recordings, and community meetups.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 3 days ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true}>
|
||||
<Avatar src="static/avatars/005f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Olivia Davis" />
|
||||
@@ -246,7 +230,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Client success stories</a></h3>
|
||||
<p class="text-secondary text-truncate">Case studies and testimonials from our most satisfied customers.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 1 week ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true}>
|
||||
<Avatar src="static/avatars/006m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="James Wilson" />
|
||||
@@ -268,7 +252,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Team highlights</a></h3>
|
||||
<p class="text-secondary text-truncate">Spotlights, shoutouts, and stories from people across the team.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 4 days ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true}>
|
||||
<Avatar src="static/avatars/002m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Michael Johnson" />
|
||||
@@ -290,7 +274,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Training & resources</a></h3>
|
||||
<p class="text-secondary text-truncate">Access to learning materials, tutorials, or resources for users and staff.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 6 days ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true}>
|
||||
<Avatar src="static/avatars/003m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Robert Garcia" />
|
||||
@@ -312,7 +296,7 @@ import CardBody from '@ui/CardBody.astro'
|
||||
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Industry news</a></h3>
|
||||
<p class="text-secondary text-truncate">Trends, news, and commentary from around the industry landscape.</p>
|
||||
<p class="fs-sm text-secondary mb-0">Last post 2 weeks ago</p>
|
||||
<hr />
|
||||
<hr>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<AvatarList stacked={true}>
|
||||
<Avatar src="static/avatars/006m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="James Wilson" />
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
---
|
||||
// Front matter: layout-sidebar + layout-sidebar-dark + page-header + pretitle.
|
||||
// Note: layout-navbar-toolbar is an inert front-matter key — it is not
|
||||
// referenced by default.html or any include, so there is no prop for it.
|
||||
// No page-menu front matter → pageMenu is intentionally not passed (nothing
|
||||
// active in the navbar/sidebar, matching the Eleventy output).
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Layout playground" pageHeader="Layout playground" pretitle="Playground" sidebar sidebarDark>
|
||||
<DefaultLayout
|
||||
title="Layout playground"
|
||||
pageHeader="Layout playground"
|
||||
pretitle="Playground"
|
||||
sidebar
|
||||
sidebarDark
|
||||
>
|
||||
<div class="row row-cards">
|
||||
<div class="col-12">
|
||||
<Card>
|
||||
|
||||
@@ -1,36 +1,20 @@
|
||||
---
|
||||
// Reuses committed components: ProgressSteps, Datepicker, Trending.
|
||||
//
|
||||
// NOTE (datepicker params): the source passes many params to ui/datepicker.html
|
||||
// (date-min, date-max, selection-mode, placement, display-months-count,
|
||||
// first-weekday, selected-dates). The current shared ui/datepicker.html is
|
||||
// Litepicker-based and IGNORES all of them — the reference build renders only a
|
||||
// plain input (id/value/layout/inline). Datepicker.astro mirrors that include, so
|
||||
// we pass only the supported props and drop the rest, matching the reference.
|
||||
//
|
||||
// NOTE (page-libs): front matter is `page-libs: [vanilla-calendar-pro]`, but that
|
||||
// lib is NOT registered in @tabler/core/libs.json, so BaseLayout emits no assets
|
||||
// for it (matches the reference: empty PAGE LEVEL STYLES, no vanilla output).
|
||||
// Passed through faithfully anyway.
|
||||
//
|
||||
// NOTE: the `page-header-link` front matter key ("Go to Team Settings") is inert —
|
||||
// nothing in the layout/page-header consumes it, and it is absent from the
|
||||
// reference. No prop for it.
|
||||
|
||||
import Subheader from '@ui/Subheader.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardHeader from '@ui/CardHeader.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import ProgressSteps from '@ui/ProgressSteps.astro'
|
||||
import Datepicker from '@ui/Datepicker.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
import Trending from '@ui/Trending.astro'
|
||||
import Subheader from '@ui/Subheader.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardHeader from '@ui/CardHeader.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import ProgressSteps from '@ui/ProgressSteps.astro';
|
||||
import Datepicker from '@ui/Datepicker.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
import Trending from '@ui/Trending.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Playground" pageHeader="Project Settings" pageLibs={['vanilla-calendar-pro']}>
|
||||
<div class="row row-cards row-deck">
|
||||
|
||||
|
||||
<div class="col-12">
|
||||
<Card>
|
||||
<CardBody>
|
||||
@@ -50,6 +34,7 @@ import Trending from '@ui/Trending.astro'
|
||||
<div class="bg-pattern-zigzag w-10 h-10 border rounded"></div>
|
||||
<div class="bg-pattern-vertical-stripes w-10 h-10 border rounded"></div>
|
||||
<div class="bg-pattern-horizontal-stripes w-10 h-10 border rounded"></div>
|
||||
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -77,19 +62,27 @@ import Trending from '@ui/Trending.astro'
|
||||
|
||||
<div class="col-3">
|
||||
<Card>
|
||||
<CardBody> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </CardBody>
|
||||
<CardBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua.
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<Card>
|
||||
<CardBody> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </CardBody>
|
||||
<CardBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua.
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<Card>
|
||||
<CardBody> Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur earum perferendis expedita suscipit quasi maiores quas sint harum dolor! Dolorem cumque autem error ea fuga. Ea excepturi temporibus officiis perspiciatis. </CardBody>
|
||||
<CardBody>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur earum perferendis expedita suscipit quasi maiores quas sint harum dolor! Dolorem cumque autem error ea fuga. Ea excepturi temporibus officiis perspiciatis.
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -176,6 +169,7 @@ import Trending from '@ui/Trending.astro'
|
||||
<CardBody>
|
||||
<p class="card-title mb-4">Cohort Statistics</p>
|
||||
<dl class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
|
||||
<div class="col">
|
||||
<Subheader as="dt">Total Users</Subheader>
|
||||
<dd class="mt-1 d-flex align-items-baseline">
|
||||
@@ -247,6 +241,7 @@ import Trending from '@ui/Trending.astro'
|
||||
<Trending value={-3} class="ms-2" />
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
</dl>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import Progress from '@ui/Progress.astro'
|
||||
import ProgressSteps from '@ui/ProgressSteps.astro'
|
||||
import ProgressBg from '@ui/ProgressBg.astro'
|
||||
import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import CaptureScript from '@shared/components/CaptureScript.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import Progress from '@ui/Progress.astro';
|
||||
import ProgressSteps from '@ui/ProgressSteps.astro';
|
||||
import ProgressBg from '@ui/ProgressBg.astro';
|
||||
import ProgressDescription from '@ui/ProgressDescription.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Progress" pageHeader="Progress" pageMenu="base.progress">
|
||||
@@ -113,12 +114,16 @@ import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
<div class="col">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle> Animated with JavaScript </CardTitle>
|
||||
<CardTitle>
|
||||
Animated with JavaScript
|
||||
</CardTitle>
|
||||
<div class="row align-items-center g-0">
|
||||
<div class="col">
|
||||
<Progress value="0" id="progress-animated" />
|
||||
</div>
|
||||
<div class="col-2 text-end" id="progress-animated-value">0%</div>
|
||||
<div class="col-2 text-end" id="progress-animated-value">
|
||||
0%
|
||||
</div>
|
||||
</div>
|
||||
<ButtonList class="mt-3">
|
||||
<button class="btn btn-sm" id="progress-animated-0">0%</button>
|
||||
@@ -128,8 +133,9 @@ import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
<button class="btn btn-sm ms-3" id="progress-animated-minus-10">-10%</button>
|
||||
<button class="btn btn-sm" id="progress-animated-add-10">+10%</button>
|
||||
</ButtonList>
|
||||
<CaptureScript>
|
||||
<!-- BEGIN SCRIPT OF ANIMATION -->
|
||||
<script>
|
||||
<script is:inline>
|
||||
/*
|
||||
This script is for animation of the last progress bar.
|
||||
It increases the progress bar value by a random amount every 2 seconds until it reaches 100%.
|
||||
@@ -137,37 +143,40 @@ import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
|
||||
This is just a demo script to show how to animate the progress bar. You can modify it as needed.
|
||||
*/
|
||||
const progress = document.getElementById('progress-animated')!
|
||||
let width = 0
|
||||
const progress = document.getElementById('progress-animated')!;
|
||||
let width = 0;
|
||||
|
||||
const setWidth = (w: number) => {
|
||||
width = Math.min(Math.max(w, 0), 100)
|
||||
width = Math.min(Math.max(w, 0), 100);
|
||||
|
||||
const bar = progress.querySelector<HTMLElement>('.progress-bar')!
|
||||
bar.style.width = `${width}%`
|
||||
bar.setAttribute('aria-valuenow', `${width}`)
|
||||
document.getElementById('progress-animated-value')!.innerText = `${width}%`
|
||||
bar.classList.toggle('bg-green', width >= 100)
|
||||
}
|
||||
const bar = progress.querySelector<HTMLElement>('.progress-bar')!;
|
||||
bar.style.width = `${width}%`;
|
||||
bar.setAttribute('aria-valuenow', `${width}`);
|
||||
document.getElementById('progress-animated-value')!.innerText = `${width}%`;
|
||||
bar.classList.toggle('bg-green', width >= 100);
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setWidth(width + Math.ceil(Math.random() * 10))
|
||||
if (width >= 100) clearInterval(interval)
|
||||
}, 2000)
|
||||
setWidth(width + Math.ceil(Math.random() * 10));
|
||||
if (width >= 100) clearInterval(interval);
|
||||
}, 2000);
|
||||
|
||||
document.getElementById('progress-animated-0')!.addEventListener('click', () => setWidth(0))
|
||||
document.getElementById('progress-animated-add-10')!.addEventListener('click', () => setWidth(width + 10))
|
||||
document.getElementById('progress-animated-minus-10')!.addEventListener('click', () => setWidth(width - 10))
|
||||
document.getElementById('progress-animated-100')!.addEventListener('click', () => setWidth(100))
|
||||
document.getElementById('progress-animated-0')!.addEventListener('click', () => setWidth(0));
|
||||
document.getElementById('progress-animated-add-10')!.addEventListener('click', () => setWidth(width + 10));
|
||||
document.getElementById('progress-animated-minus-10')!.addEventListener('click', () => setWidth(width - 10));
|
||||
document.getElementById('progress-animated-100')!.addEventListener('click', () => setWidth(100));
|
||||
</script>
|
||||
<!-- END SCRIPT OF ANIMATION -->
|
||||
</CaptureScript>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
<div class="col">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle> Steps Progress </CardTitle>
|
||||
<CardTitle>
|
||||
Steps Progress
|
||||
</CardTitle>
|
||||
<div class="space-y">
|
||||
<ProgressSteps count={3} />
|
||||
<ProgressSteps count={5} active={4} />
|
||||
@@ -180,7 +189,9 @@ import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
<div class="col">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle> Progress Background </CardTitle>
|
||||
<CardTitle>
|
||||
Progress Background
|
||||
</CardTitle>
|
||||
<div class="space-y">
|
||||
<ProgressBg value="85" text="Poland" showValue />
|
||||
<ProgressBg value="65" text="Germany" showValue />
|
||||
@@ -193,7 +204,9 @@ import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
<div class="col">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle> Progress Background Colors </CardTitle>
|
||||
<CardTitle>
|
||||
Progress Background Colors
|
||||
</CardTitle>
|
||||
<div class="space-y">
|
||||
<ProgressBg value="75" text="Success" color="success-lt" showValue />
|
||||
<ProgressBg value="60" text="Warning" color="warning-lt" showValue />
|
||||
@@ -206,7 +219,9 @@ import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
<div class="col">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle> Progress Description </CardTitle>
|
||||
<CardTitle>
|
||||
Progress Description
|
||||
</CardTitle>
|
||||
<div class="space-y">
|
||||
<ProgressDescription label="Project completion" value="85" color="green" />
|
||||
<ProgressDescription label="Storage usage" description="2.4GB of 5GB" value="48" color="blue" />
|
||||
@@ -219,7 +234,9 @@ import ProgressDescription from '@ui/ProgressDescription.astro'
|
||||
<div class="col">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle> Progress Description Sizes </CardTitle>
|
||||
<CardTitle>
|
||||
Progress Description Sizes
|
||||
</CardTitle>
|
||||
<div class="space-y">
|
||||
<ProgressDescription label="Small progress" value="60" size="sm" color="blue" />
|
||||
<ProgressDescription label="Default progress" value="70" color="green" />
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
// The Liquid `{% for color in site.colors %}` swatches map to site.themeColors
|
||||
// (blue…cyan), matching the reference bg-gradient-from-{color} output.
|
||||
// The large `{% comment %}…{% endcomment %}` prose/nav-segmented block in the
|
||||
// source is a Liquid comment (not rendered) — intentionally omitted.
|
||||
import BaseLayout from '@shared/layouts/BaseLayout.astro'
|
||||
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
|
||||
import { site } from '@shared/lib/site'
|
||||
// Color swatches use site.themeColors (blue…cyan).
|
||||
// Large prose/nav-segmented block from source template omitted (was commented out).
|
||||
import BaseLayout from '@shared/layouts/BaseLayout.astro';
|
||||
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
|
||||
import { site } from '@shared/lib/site';
|
||||
---
|
||||
|
||||
<BaseLayout pageLibs={['signature_pad']}>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
// Photo id=11 → the 12th entry of the unfiltered photos list (Liquid
|
||||
// filtered-photos[11]); no `horizontal` filter is applied here.
|
||||
import BaseLayout from '@shared/layouts/BaseLayout.astro'
|
||||
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
|
||||
import SignInForm from '@shared/components/cards/SignInForm.astro'
|
||||
import Photo from '@ui/Photo.astro'
|
||||
import photos from '@data/photos.json'
|
||||
// Photo id=11 → 12th entry of the unfiltered photos list; no horizontal filter.
|
||||
import BaseLayout from '@shared/layouts/BaseLayout.astro';
|
||||
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
|
||||
import SignInForm from '@shared/components/cards/SignInForm.astro';
|
||||
import Photo from '@ui/Photo.astro';
|
||||
import photos from '@data/photos.json';
|
||||
---
|
||||
|
||||
<BaseLayout title="Sign in with cover" bodyClass="d-flex flex-column bg-white">
|
||||
|
||||
@@ -4,8 +4,7 @@ import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
|
||||
import SignInCard from '@shared/components/cards/SignInCard.astro'
|
||||
import Illustration from '@ui/Illustration.astro'
|
||||
|
||||
// Liquid passes show-header="1" to cards/sign-in.html, but the include renders
|
||||
// the header unconditionally — SignInCard.astro mirrors that (no prop needed).
|
||||
// show-header prop ignored — SignInCard renders header unconditionally.
|
||||
---
|
||||
|
||||
<SingleLayout title="Sign in with illustration" containerSize="normal" hideLogo>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
import SingleLayout from '@shared/layouts/SingleLayout.astro'
|
||||
import SingleLayout from '@shared/layouts/SingleLayout.astro';
|
||||
|
||||
// TODO: Liquid uses {{ site.email }} — add `email` to src/lib/site.ts once it is
|
||||
// safe to touch shared files (kept local here to avoid cross-agent conflicts).
|
||||
const siteEmail = 'support@tabler.io'
|
||||
// TODO: add `email` to src/lib/site.ts — kept local here to avoid cross-agent conflicts.
|
||||
const siteEmail = 'support@tabler.io';
|
||||
---
|
||||
|
||||
<SingleLayout title="Sign in link">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,9 +3,7 @@ import { site } from '@shared/lib/site';
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
// (build.format 'file'), index pages collapse to their directory URL.
|
||||
// Entry order mirrors the Eleventy `pages` collection: top-level pages first,
|
||||
// then nested ones, each group in reverse-alphabetical order.
|
||||
// Index pages collapse to directory URLs (build.format 'file').
|
||||
const pages = import.meta.glob('./**/*.astro');
|
||||
|
||||
const urls = Object.keys(pages)
|
||||
@@ -14,7 +12,6 @@ const urls = Object.keys(pages)
|
||||
const depthA = a.split('/').length;
|
||||
const depthB = b.split('/').length;
|
||||
if (depthA !== depthB) return depthA - depthB;
|
||||
// byte-wise, descending — matches the Eleventy `pages` collection order
|
||||
return a < b ? 1 : -1;
|
||||
})
|
||||
.map((path) => {
|
||||
@@ -34,7 +31,7 @@ const escapeXml = (value: string) =>
|
||||
export const GET: APIRoute = () => {
|
||||
const environment = process.env.NODE_ENV || 'production';
|
||||
const baseUrl = environment !== 'development' ? site.previewUrl : '';
|
||||
// same shape as Liquid's `'now' | date_to_xmlschema` (UTC, +00:00 suffix)
|
||||
// ISO 8601 UTC timestamp (+00:00 suffix)
|
||||
const lastModified = new Date().toISOString().replace(/\.\d{3}Z$/, '+00:00');
|
||||
const entries = urls
|
||||
.map(
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
---
|
||||
// Note: the Liquid front matter `plugins: ['social']` is inert for the output —
|
||||
// the `.social` styles ship in tabler-socials.css which is loaded globally on
|
||||
// every page; no extra page lib is emitted. So no pageLibs is passed.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import Trending from '@ui/Trending.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardHeader from '@ui/CardHeader.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import socialTiles from '@data/social-tiles.json'
|
||||
import socials from '@data/socials.json'
|
||||
// plugins: ['social'] front matter is inert — .social styles ship globally in tabler-socials.css.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Trending from '@ui/Trending.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardHeader from '@ui/CardHeader.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import socialTiles from '@data/social-tiles.json';
|
||||
import socials from '@data/socials.json';
|
||||
|
||||
interface SocialTile {
|
||||
icon: string
|
||||
title: string
|
||||
description: string
|
||||
trending: number
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
trending: number;
|
||||
}
|
||||
|
||||
interface Social {
|
||||
name: string
|
||||
file: string
|
||||
name: string;
|
||||
file: string;
|
||||
}
|
||||
|
||||
const tiles = socialTiles as SocialTile[]
|
||||
const socialList = socials as Social[]
|
||||
const fillers = Array.from({ length: 21 })
|
||||
const tiles = socialTiles as SocialTile[];
|
||||
const socialList = socials as Social[];
|
||||
const fillers = Array.from({ length: 21 });
|
||||
---
|
||||
|
||||
<DefaultLayout title="Social icons" pageHeader="Social icons" pageMenu="base.social">
|
||||
@@ -86,7 +84,12 @@ const fillers = Array.from({ length: 21 })
|
||||
<div class="demo-icons-list">
|
||||
{
|
||||
socialList.map((social) => (
|
||||
<span class="demo-icons-list-item" title={social.name} data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<span
|
||||
class="demo-icons-list-item"
|
||||
title={social.name}
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
>
|
||||
<span class={`social social-app-${social.file}`} />
|
||||
</span>
|
||||
))
|
||||
|
||||
+15
-16
@@ -1,24 +1,23 @@
|
||||
---
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Tag from '@ui/Tag.astro'
|
||||
import TagsList from '@ui/TagsList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import flags from '@data/flags.json'
|
||||
import siteData from '@data/site.json'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Tag from '@ui/Tag.astro';
|
||||
import TagsList from '@ui/TagsList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import flags from '@data/flags.json';
|
||||
import siteData from '@data/site.json';
|
||||
|
||||
const tagIcons = ['bold', 'italic', 'underline', 'copy', 'scissors', 'file-plus', 'file-minus', 'ghost', 'star', 'script', 'photo', 'dog', 'piano']
|
||||
const tagIcons = ['bold', 'italic', 'underline', 'copy', 'scissors', 'file-plus', 'file-minus', 'ghost', 'star', 'script', 'photo', 'dog', 'piano'];
|
||||
|
||||
const range = (a: number, b: number) => Array.from({ length: b - a + 1 }, (_, i) => a + i)
|
||||
const range = (a: number, b: number) => Array.from({ length: b - a + 1 }, (_, i) => a + i);
|
||||
|
||||
const flags9 = flags.slice(0, 9)
|
||||
const people8 = people.slice(0, 8)
|
||||
const flags9 = flags.slice(0, 9);
|
||||
const people8 = people.slice(0, 8);
|
||||
|
||||
// site.colors is an object keyed by colour name; the Liquid loop yields the
|
||||
// value objects with .class / .title.
|
||||
const colors = Object.values(siteData.colors) as { class: string; title: string }[]
|
||||
// site.colors yields value objects with .class / .title.
|
||||
const colors = Object.values(siteData.colors) as { class: string; title: string }[];
|
||||
---
|
||||
|
||||
<DefaultLayout title="Tags" pageHeader="Tags" pageMenu="base.tags">
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
---
|
||||
import CardActions from '@ui/CardActions.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Badge from '@ui/Badge.astro'
|
||||
import Modal from '@shared/components/modals/Modal.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
import tasks from '@data/tasks.json'
|
||||
import people from '@data/people.json'
|
||||
|
||||
import CardActions from '@ui/CardActions.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Badge from '@ui/Badge.astro';
|
||||
import Modal from '@shared/components/modals/Modal.astro';
|
||||
import CaptureModal from '@shared/components/CaptureModal.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
import tasks from '@data/tasks.json';
|
||||
import people from '@data/people.json';
|
||||
|
||||
interface Person {
|
||||
id?: string
|
||||
full_name?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
id?: string;
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Task {
|
||||
'name'?: string
|
||||
'assigned_to'?: number
|
||||
'due_date'?: string
|
||||
'due-date'?: string
|
||||
'priority'?: string
|
||||
[key: string]: unknown
|
||||
name?: string;
|
||||
assigned_to?: number;
|
||||
due_date?: string;
|
||||
'due-date'?: string;
|
||||
priority?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Column {
|
||||
name: string
|
||||
tasks: Task[]
|
||||
name: string;
|
||||
tasks: Task[];
|
||||
}
|
||||
|
||||
const columns = (tasks as { columns: Column[] }).columns
|
||||
const peopleList = people as Person[]
|
||||
const columns = (tasks as { columns: Column[] }).columns;
|
||||
const peopleList = people as Person[];
|
||||
|
||||
// add-task modal: assignable people (parts/modals/add-task.html — "5,6,2,3")
|
||||
const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
|
||||
// Assignable people for the add-task modal.
|
||||
const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1]);
|
||||
---
|
||||
|
||||
<DefaultLayout title="Task List" pageHeader="Task List" pageMenu="extra.tasks.list">
|
||||
@@ -55,7 +57,11 @@ const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-1">
|
||||
<input type="checkbox" class="form-check-input align-middle table-selectable-check" aria-label="Select all tasks" />
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input align-middle table-selectable-check"
|
||||
aria-label="Select all tasks"
|
||||
/>
|
||||
</th>
|
||||
<th class="w-50">Name</th>
|
||||
<th class="d-none d-xl-table-cell">Assigned To</th>
|
||||
@@ -66,11 +72,17 @@ const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
|
||||
</thead>
|
||||
<tbody>
|
||||
{section.tasks.map((task) => {
|
||||
const person = task.assigned_to ? peopleList[task.assigned_to - 1] : undefined
|
||||
const person = task.assigned_to
|
||||
? peopleList[task.assigned_to - 1]
|
||||
: undefined;
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" class="form-check-input align-middle table-selectable-check" aria-label="Select task" />
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input align-middle table-selectable-check"
|
||||
aria-label="Select task"
|
||||
/>
|
||||
</td>
|
||||
<td>{task.name}</td>
|
||||
<td>
|
||||
@@ -98,12 +110,22 @@ const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
|
||||
<span class="text-muted">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{task.priority === 'High' ? <Badge text="High" color="red" light /> : task.priority === 'Medium' ? <Badge text="Medium" color="yellow" light /> : task.priority === 'Low' ? <Badge text="Low" color="blue" light /> : <Badge text="—" light />}</td>
|
||||
<td>
|
||||
{task.priority === 'High' ? (
|
||||
<Badge text="High" color="red" light />
|
||||
) : task.priority === 'Medium' ? (
|
||||
<Badge text="Medium" color="yellow" light />
|
||||
) : task.priority === 'Low' ? (
|
||||
<Badge text="Low" color="blue" light />
|
||||
) : (
|
||||
<Badge text="—" light />
|
||||
)}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<Button text="View" size="sm" />
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -114,6 +136,7 @@ const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CaptureModal>
|
||||
<Modal modalId="add-task">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Add task</h4>
|
||||
@@ -128,7 +151,11 @@ const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
|
||||
<FormGroup label="Assigned To">
|
||||
<select class="form-select">
|
||||
<option value="">Select person</option>
|
||||
{selectedPeople.map((person) => <option value={person.id}>{person.full_name}</option>)}
|
||||
{
|
||||
selectedPeople.map((person) => (
|
||||
<option value={person.id}>{person.full_name}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</FormGroup>
|
||||
<FormGroup label="Priority">
|
||||
@@ -146,7 +173,10 @@ const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary ms-auto" data-bs-dismiss="modal"> Save </button>
|
||||
<button type="button" class="btn btn-primary ms-auto" data-bs-dismiss="modal">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</CaptureModal>
|
||||
</DefaultLayout>
|
||||
|
||||
@@ -3,16 +3,7 @@ import SingleLayout from '@shared/layouts/SingleLayout.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import Prose from '@ui/Prose.astro'
|
||||
|
||||
// Liquid renders shared/includes/terms-of-service.md via `renderContent: "md"`
|
||||
// (markdown-it). The rendered HTML below is inlined verbatim from the reference
|
||||
// build to guarantee a 1:1 DOM match — Astro's markdown pipeline (remark +
|
||||
// smartypants) would produce different quote characters and list structure.
|
||||
// TODO: source of truth remains shared/includes/terms-of-service.md; regenerate
|
||||
// this block if the markdown changes.
|
||||
//
|
||||
// The card title is empty on purpose: the Liquid template prints {{ page.title }},
|
||||
// which is Eleventy's `page` object (no `title` property) — the reference output
|
||||
// is an empty <h3>.
|
||||
// Card title is empty on purpose.
|
||||
---
|
||||
|
||||
<SingleLayout title="Terms of service" containerSize="narrow">
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
// The right column renders headings h1..h6 via a Liquid {% for i in (1..6) %}
|
||||
// loop → dynamic tag name here. {{ site.homepage }} → @data/site.json homepage.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Prose from '@ui/Prose.astro'
|
||||
import siteData from '@data/site.json'
|
||||
// Right column renders headings h1..h6 with dynamic tag names. Homepage URL from site.json.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Prose from '@ui/Prose.astro';
|
||||
import siteData from '@data/site.json';
|
||||
|
||||
const homepage = siteData.homepage
|
||||
const homepage = siteData.homepage;
|
||||
---
|
||||
|
||||
<DefaultLayout title="Text features" pageHeader="Text features" pageMenu="extra.text-features">
|
||||
@@ -53,40 +52,26 @@ const homepage = siteData.homepage
|
||||
<Prose>
|
||||
{
|
||||
[1, 2, 3, 4, 5, 6].map((i) => {
|
||||
const Heading = `h${i}`
|
||||
const Heading = `h${i}`;
|
||||
return (
|
||||
<Heading>
|
||||
Heading {i} by{' '}
|
||||
<a class="mention">
|
||||
<>
|
||||
<span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)" />
|
||||
<span class="visually-hidden">@</span>
|
||||
</>
|
||||
JohnDoe
|
||||
</a>
|
||||
Heading {i} by <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>
|
||||
</Heading>
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<p>
|
||||
Tabler is a modern UI framework which <span class="text-incorrect" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">provide</span> developers with a lot of <span class="text-incorrect">pre-build</span> components and customizable options. It is
|
||||
<span class="text-incorrect">build</span> on Bootstrap, making it easy to integrate into existing projects. The design is clean, responsive, and accessible, ensuring that <span class="text-incorrect">user</span> can navigate through <span class="text-incorrect">interface</span> easily. Tabler also <span
|
||||
class="text-incorrect">support</span
|
||||
> all modern browsers, but some features may not work properly on Internet Explorer. With
|
||||
<span class="text-incorrect">build</span> on Bootstrap, making it easy to integrate into existing projects. The design is clean, responsive, and accessible, ensuring that <span class="text-incorrect">user</span> can navigate
|
||||
through <span class="text-incorrect">interface</span> easily. Tabler also <span class="text-incorrect">support</span> all modern browsers, but some features may not work properly on Internet Explorer. With
|
||||
<span class="text-incorrect">it's</span> lightweight structure and optimized performance, Tabler helps developers create stunning web applications faster.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Hey <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>, have you seen the latest updates on <a class="mention">#WebDevelopment<span class="mention-count">16</span></a>? <a class="mention"
|
||||
><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JaneSmith</a
|
||||
> just shared an interesting article about <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/messenger.svg)"></span>Messenger</a> and <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/netflix.svg)"></span>Netflix</a>!
|
||||
</p>
|
||||
<p>Hey <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>, have you seen the latest updates on <a class="mention">#WebDevelopment<span class="mention-count">16</span></a>? <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JaneSmith</a> just shared an interesting article about <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/messenger.svg)"></span>Messenger</a> and <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/netflix.svg)"></span>Netflix</a>!</p>
|
||||
|
||||
<p>
|
||||
The sky is <span class="mention"><span class="mention-color bg-blue"></span>#066fd1</span>, the grass is <span class="mention"><span class="mention-color bg-green"></span>rgb(47, 179, 68)</span>, fire trucks are often <span class="mention"><span class="mention-color bg-red"></span>red</span>, oranges are <span
|
||||
class="mention"><span class="mention-color bg-orange"></span>hsl(24deg, 94.49%, 49.8%)</span
|
||||
>. Some flowers are <span class="mention"><span class="mention-color bg-purple"></span>hwb(288.35deg, 24.31%, 21.18%)</span>.
|
||||
The sky is <span class="mention"><span class="mention-color bg-blue"></span>#066fd1</span>, the grass is <span class="mention"><span class="mention-color bg-green"></span>rgb(47, 179, 68)</span>, fire trucks are often <span class="mention"><span class="mention-color bg-red"></span>red</span>, oranges are <span class="mention"><span class="mention-color bg-orange"></span>hsl(24deg, 94.49%, 49.8%)</span>. Some flowers are <span class="mention"><span class="mention-color bg-purple"></span>hwb(288.35deg, 24.31%, 21.18%)</span>.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
+49
-37
@@ -1,17 +1,23 @@
|
||||
---
|
||||
import CardActions from '@ui/CardActions.astro'
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardHeader from '@ui/CardHeader.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import Badge from '@ui/Badge.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardActions from '@ui/CardActions.astro';
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import CaptureScript from '@shared/components/CaptureScript.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardHeader from '@ui/CardHeader.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import Badge from '@ui/Badge.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
---
|
||||
|
||||
<DefaultLayout title="Driver Tour" pageHeader="Driver Tour" pageMenu="plugins.tour" pageLibs={['driver.js']}>
|
||||
<DefaultLayout
|
||||
title="Driver Tour"
|
||||
pageHeader="Driver Tour"
|
||||
pageMenu="plugins.tour"
|
||||
pageLibs={['driver.js']}
|
||||
>
|
||||
<div class="row row-cards">
|
||||
<div class="col-12">
|
||||
<Card>
|
||||
@@ -21,7 +27,10 @@ import Icon from '@ui/Icon.astro'
|
||||
</CardActions>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>Click the "Start Tour" button to begin an interactive tour of this page. The tour will guide you through different elements and features.</p>
|
||||
<p>
|
||||
Click the "Start Tour" button to begin an interactive tour of this page.
|
||||
The tour will guide you through different elements and features.
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -42,14 +51,15 @@ import Icon from '@ui/Icon.astro'
|
||||
<Card id="tour-card-2">
|
||||
<CardHeader title="Features Section" />
|
||||
<CardBody>
|
||||
<p>This card shows additional features and demonstrates the tour's ability to navigate between different elements.</p>
|
||||
<p>This card shows additional features and demonstrates the tour's ability to navigate between different
|
||||
elements.</p>
|
||||
<div class="form-selectgroup">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="options" value="1" class="form-selectgroup-input" checked />
|
||||
<input type="radio" name="options" value="1" class="form-selectgroup-input" checked>
|
||||
<span class="form-selectgroup-label">Option 1</span>
|
||||
</label>
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="options" value="2" class="form-selectgroup-input" />
|
||||
<input type="radio" name="options" value="2" class="form-selectgroup-input">
|
||||
<span class="form-selectgroup-label">Option 2</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -132,6 +142,7 @@ import Icon from '@ui/Icon.astro'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CaptureScript>
|
||||
<!-- BEGIN TOUR SCRIPT -->
|
||||
<script is:inline>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
@@ -147,8 +158,8 @@ import Icon from '@ui/Icon.astro'
|
||||
title: 'Welcome to the Tour!',
|
||||
description: 'This button starts the interactive tour. Click it to begin exploring the page.',
|
||||
side: 'bottom',
|
||||
align: 'start',
|
||||
},
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#tour-card-1',
|
||||
@@ -156,17 +167,17 @@ import Icon from '@ui/Icon.astro'
|
||||
title: 'Welcome Section',
|
||||
description: 'This is the first card in our tour. It demonstrates how Driver.js highlights elements on the page.',
|
||||
side: 'right',
|
||||
align: 'start',
|
||||
},
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#tour-card-2',
|
||||
popover: {
|
||||
title: 'Features Section',
|
||||
description: "This card shows additional features and demonstrates the tour's ability to navigate between different elements.",
|
||||
description: 'This card shows additional features and demonstrates the tour\'s ability to navigate between different elements.',
|
||||
side: 'left',
|
||||
align: 'start',
|
||||
},
|
||||
align: 'start'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#tour-card-3',
|
||||
@@ -174,8 +185,8 @@ import Icon from '@ui/Icon.astro'
|
||||
title: 'Data Table',
|
||||
description: 'This table shows how Driver.js works with larger elements. You can highlight entire sections of your page.',
|
||||
side: 'top',
|
||||
align: 'center',
|
||||
},
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#tour-card-4',
|
||||
@@ -183,8 +194,8 @@ import Icon from '@ui/Icon.astro'
|
||||
title: 'Settings Card',
|
||||
description: 'This card demonstrates how the tour works with smaller, centered elements.',
|
||||
side: 'top',
|
||||
align: 'center',
|
||||
},
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#tour-card-5',
|
||||
@@ -192,8 +203,8 @@ import Icon from '@ui/Icon.astro'
|
||||
title: 'Users Card',
|
||||
description: 'Another example card showing user management features.',
|
||||
side: 'top',
|
||||
align: 'center',
|
||||
},
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#tour-card-6',
|
||||
@@ -201,19 +212,20 @@ import Icon from '@ui/Icon.astro'
|
||||
title: 'Analytics Card',
|
||||
description: 'The final step of the tour. This card shows analytics features.',
|
||||
side: 'top',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
align: 'center'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const startButton = document.getElementById('start-tour')
|
||||
const startButton = document.getElementById('start-tour');
|
||||
if (startButton) {
|
||||
startButton.addEventListener('click', function () {
|
||||
driverObj.drive()
|
||||
})
|
||||
driverObj.drive();
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<!-- END TOUR SCRIPT -->
|
||||
</CaptureScript>
|
||||
</DefaultLayout>
|
||||
|
||||
+30
-20
@@ -1,31 +1,35 @@
|
||||
---
|
||||
// Note: the Liquid template assigns `progress` and `online_counter` per person
|
||||
// but never renders them — omitted here.
|
||||
// Note: the avatar include passes `rounded=true`, which ui/avatar.html ignores
|
||||
// (no such param) — omitted; Avatar.astro has no `rounded` prop.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Pagination from '@ui/Pagination.astro'
|
||||
import people from '@data/people.json'
|
||||
// progress and online_counter assigned per person but never rendered — omitted.
|
||||
// rounded=true on avatar has no effect — Avatar has no `rounded` prop.
|
||||
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Card from '@ui/Card.astro';
|
||||
import CardBody from '@ui/CardBody.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Pagination from '@ui/Pagination.astro';
|
||||
import people from '@data/people.json';
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
job_title?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
job_title?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const users = (people as Person[]).slice(0, 18)
|
||||
const users = (people as Person[]).slice(0, 18);
|
||||
---
|
||||
|
||||
<DefaultLayout title="Users list" pageHeader="Users" actions="users" description="1-18 of 413 people" pageMenu="extra.users">
|
||||
<DefaultLayout
|
||||
title="Users list"
|
||||
pageHeader="Users"
|
||||
actions="users"
|
||||
description="1-18 of 413 people"
|
||||
pageMenu="extra.users"
|
||||
>
|
||||
<div class="row row-cards">
|
||||
{
|
||||
users.map((person, idx) => {
|
||||
const index = idx + 1
|
||||
const index = idx + 1;
|
||||
return (
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<Card>
|
||||
@@ -36,7 +40,13 @@ const users = (people as Person[]).slice(0, 18)
|
||||
</h3>
|
||||
<div class="text-secondary">{person.job_title}</div>
|
||||
|
||||
<div class="mt-3">{index === 1 ? <span class="badge bg-purple-lt">Owner</span> : index < 5 ? <span class="badge bg-green-lt">Admin</span> : null}</div>
|
||||
<div class="mt-3">
|
||||
{index === 1 ? (
|
||||
<span class="badge bg-purple-lt">Owner</span>
|
||||
) : index < 5 ? (
|
||||
<span class="badge bg-green-lt">Admin</span>
|
||||
) : null}
|
||||
</div>
|
||||
</CardBody>
|
||||
<div class="d-flex">
|
||||
<a href="#" class="card-btn">
|
||||
@@ -48,7 +58,7 @@ const users = (people as Person[]).slice(0, 18)
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,7 @@ import Progress from '@ui/Progress.astro'
|
||||
import { site } from '@shared/lib/site'
|
||||
import timezones from '@data/timezones.json'
|
||||
|
||||
// TODO: front matter `page-menu: extra.wizard` is not ported — it is only read
|
||||
// by layout/navbar-menu.html, which the `single` layout does not include.
|
||||
// TODO: page-menu (extra.wizard) is unused — SingleLayout has no navbar menu.
|
||||
---
|
||||
|
||||
<SingleLayout title="Wizard">
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
// Captures slot markup (typically a modal) into page-modals; BaseLayout emits
|
||||
// it at the end of <body> via <PageModals />.
|
||||
// Registration is synchronous (a promise) — see page-modals.ts.
|
||||
import { addPageModal } from '@shared/lib/page-modals';
|
||||
|
||||
addPageModal(Astro.slots.render('default'));
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
// Captures slot markup (typically <script>) into page-scripts; BaseLayout /
|
||||
// DocsLayout emit it at the end of <body> via <PageScripts />.
|
||||
// Registration is synchronous (a promise) — see page-scripts.ts.
|
||||
import { addPageScript } from '@shared/lib/page-scripts';
|
||||
|
||||
addPageScript(Astro.slots.render('default'));
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
// Separate component so drainPageScripts() executes only at this point of the
|
||||
// render stream (after the page content render has STARTED — registrations are
|
||||
// synchronous, script content may be a promise).
|
||||
import { drainPageScripts } from '@shared/lib/page-scripts';
|
||||
|
||||
const scripts = await Promise.all(drainPageScripts());
|
||||
---
|
||||
|
||||
{scripts.length > 0 && <Fragment set:html={scripts.join('\n')} />}
|
||||
@@ -1,11 +1,10 @@
|
||||
---
|
||||
// Equivalent of cards/auth-lock.html
|
||||
import Button from '@ui/Button.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import Button from '@ui/Button.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
|
||||
const person = people[0]
|
||||
const person = people[0];
|
||||
---
|
||||
|
||||
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
|
||||
@@ -21,7 +20,7 @@ const person = people[0]
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<input type="password" class="form-control" placeholder="Password…" />
|
||||
<input type="password" class="form-control" placeholder="Password…" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/body-placeholder.html (ui/svg.html inlined).
|
||||
// TODO: the ui/svg.html `border` branch (rect + inline style) is not ported —
|
||||
// body-placeholder.html never sets it (the "border" token in the class
|
||||
// list is just a CSS class).
|
||||
interface Props {
|
||||
width?: number
|
||||
height?: number
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/card.html
|
||||
// TODO: ui/nav.html is inlined here only in its header variant (tabs/pills
|
||||
// inside .card-header) — the standalone nav variants are not ported.
|
||||
// TODO: ui/photo.html is inlined only in its ratio branch with the fixed
|
||||
// photo ids from the Liquid source (img-top → id=7, img-bottom → id=11;
|
||||
// the `id=img-id` argument in card.html is always undefined and the
|
||||
// trailing literal id wins).
|
||||
// TODO: ui/form/check.html is inlined only in the empty+checked (switch)
|
||||
// variants used by footer-elements; other check params are not ported.
|
||||
|
||||
import CardSubtitle from '@ui/CardSubtitle.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import AvatarList from '@ui/AvatarList.astro';
|
||||
@@ -40,8 +30,7 @@ interface Props {
|
||||
footer?: boolean;
|
||||
footerButton?: boolean;
|
||||
footerButtons?: boolean;
|
||||
/** a ">" prefix pushes the element (and, per the Liquid loop-variable leak,
|
||||
* every following one) to the right */
|
||||
/** a ">" prefix pushes the element (and every following one) to the right — `right` is never reset */
|
||||
footerElements?: string[];
|
||||
progress?: boolean;
|
||||
}
|
||||
@@ -83,8 +72,7 @@ const classes = [
|
||||
className,
|
||||
]
|
||||
|
||||
// footer-elements parsing — mirrors the Liquid loop, including the fact that
|
||||
// `right` is never reset once set (Liquid assign leaks across iterations).
|
||||
// footer-elements parsing — `right` is never reset once set across iterations.
|
||||
let right = false;
|
||||
const footerEls = (footerElements ?? []).map((element: string) => {
|
||||
let el = element;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
---
|
||||
// Equivalent of cards/card-background-icon.html
|
||||
import CardStamp from '@ui/CardStamp.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import CardStamp from '@ui/CardStamp.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
---
|
||||
|
||||
<div class="card bg-primary text-primary-fg">
|
||||
<CardStamp icon="star" color="white" textColor="primary" />
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with background and icon</CardTitle>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
|
||||
ipsa iusto, modi nostrum recusandae reiciendis saepe.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
---
|
||||
// Equivalent of cards/card-image.html
|
||||
// TODO: ui/photo.html is inlined only in its plain <img> branch (no
|
||||
// background/ratio params are used by card-image.html).
|
||||
import photos from '@data/photos.json'
|
||||
import photos from '@data/photos.json';
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
right?: boolean
|
||||
imgId?: number
|
||||
title?: string;
|
||||
right?: boolean;
|
||||
imgId?: number;
|
||||
}
|
||||
|
||||
const { title, right, imgId = 1 } = Astro.props
|
||||
const { title, right, imgId = 1 } = Astro.props;
|
||||
|
||||
const photo = (photos as { file: string; title: string }[])[imgId]
|
||||
const imgClass = `w-100 h-100 object-cover ${right ? 'card-img-end' : 'card-img-start'}`
|
||||
const photo = (photos as { file: string; title: string }[])[imgId];
|
||||
const imgClass = `w-100 h-100 object-cover ${right ? 'card-img-end' : 'card-img-start'}`;
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -25,7 +22,10 @@ const imgClass = `w-100 h-100 object-cover ${right ? 'card-img-end' : 'card-img-
|
||||
<div class="col">
|
||||
<div class="card-body">
|
||||
{title && <h3 class="card-title" set:html={title} />}
|
||||
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima neque pariatur perferendis sed suscipit velit vitae voluptatem.</p>
|
||||
<p class="text-secondary">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima
|
||||
neque pariatur perferendis sed suscipit velit vitae voluptatem.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
---
|
||||
// Equivalent of cards/card-ribbon-text.html
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="ribbon bg-red">NEW</div>
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with text ribbon</CardTitle>
|
||||
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
|
||||
<p class="text-secondary">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
|
||||
ipsa iusto, modi nostrum recusandae reiciendis saepe.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
---
|
||||
// Equivalent of cards/card-ribbon-top.html
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="ribbon ribbon-top bg-yellow"><Icon name="star" /></div>
|
||||
<div class="card-body">
|
||||
<CardTitle>Card with top ribbon</CardTitle>
|
||||
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
|
||||
<p class="text-secondary">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
|
||||
ipsa iusto, modi nostrum recusandae reiciendis saepe.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
---
|
||||
// Equivalent of cards/card-tabs.html
|
||||
// The tabs/content blocks are built as HTML strings to mirror the Liquid
|
||||
// {% capture %} blocks (they are emitted in a different order for `bottom`).
|
||||
// Tabs/content blocks are HTML strings (different order when `bottom`).
|
||||
interface Props {
|
||||
/** present in the Liquid source (tabs-count) but never used there */
|
||||
count?: number
|
||||
id?: string
|
||||
bottom?: boolean
|
||||
borderless?: boolean
|
||||
/** tabs-count — present but unused */
|
||||
count?: number;
|
||||
id?: string;
|
||||
bottom?: boolean;
|
||||
borderless?: boolean;
|
||||
}
|
||||
|
||||
const { id = 'top', bottom, borderless } = Astro.props
|
||||
const { id = 'top', bottom, borderless } = Astro.props;
|
||||
|
||||
const tabs = ['Activity', 'Profile', 'Settings']
|
||||
const tabs = ['Activity', 'Profile', 'Settings'];
|
||||
|
||||
const tabsHtml = `
|
||||
<!-- Cards navigation -->
|
||||
<ul class="nav nav-tabs${bottom ? ' nav-tabs-bottom' : ''}">
|
||||
${tabs.map((tab, i) => ` <li class="nav-item"><a href="#tab-${id}-${i + 1}" class="nav-link${i === 0 ? ' active' : ''}" data-bs-toggle="tab">${tab}</a></li>`).join('\n')}
|
||||
</ul>`
|
||||
${tabs
|
||||
.map(
|
||||
(tab, i) =>
|
||||
` <li class="nav-item"><a href="#tab-${id}-${i + 1}" class="nav-link${i === 0 ? ' active' : ''}" data-bs-toggle="tab">${tab}</a></li>`,
|
||||
)
|
||||
.join('\n')}
|
||||
</ul>`;
|
||||
|
||||
const tabsContentHtml = `
|
||||
<div class="tab-content">
|
||||
@@ -35,7 +38,7 @@ ${tabs
|
||||
</div>`,
|
||||
)
|
||||
.join('\n')}
|
||||
</div>`
|
||||
</div>`;
|
||||
---
|
||||
|
||||
<!-- Cards with tabs component -->
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
---
|
||||
// ui/carousel.html is inlined here (not a shared component in this task's
|
||||
// scope). Photos are filtered to horizontal=true, then sliced by offset/limit.
|
||||
import photos from '@data/photos.json'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
// Photos filtered to horizontal=true, then sliced by offset/limit.
|
||||
import photos from '@data/photos.json';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
|
||||
interface Props {
|
||||
id?: string
|
||||
title?: string
|
||||
indicators?: boolean
|
||||
indicatorsThumb?: boolean
|
||||
indicatorsThumbRatio?: boolean
|
||||
indicatorsDot?: boolean
|
||||
indicatorsVertical?: boolean
|
||||
controls?: boolean
|
||||
captions?: boolean
|
||||
offset?: number
|
||||
limit?: number
|
||||
fade?: boolean
|
||||
id?: string;
|
||||
title?: string;
|
||||
indicators?: boolean;
|
||||
indicatorsThumb?: boolean;
|
||||
indicatorsThumbRatio?: boolean;
|
||||
indicatorsDot?: boolean;
|
||||
indicatorsVertical?: boolean;
|
||||
controls?: boolean;
|
||||
captions?: boolean;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
fade?: boolean;
|
||||
}
|
||||
|
||||
const { id: idProp, title = 'Carousel', indicators, indicatorsThumb, indicatorsThumbRatio, indicatorsDot, indicatorsVertical, controls, captions, offset = 0, limit = 5, fade } = Astro.props
|
||||
const {
|
||||
id: idProp,
|
||||
title = 'Carousel',
|
||||
indicators,
|
||||
indicatorsThumb,
|
||||
indicatorsThumbRatio,
|
||||
indicatorsDot,
|
||||
indicatorsVertical,
|
||||
controls,
|
||||
captions,
|
||||
offset = 0,
|
||||
limit = 5,
|
||||
fade,
|
||||
} = Astro.props;
|
||||
|
||||
const carouselId = idProp ?? 'carousel'
|
||||
const filteredPhotos = (photos as { file: string; horizontal?: boolean }[]).filter((p) => p.horizontal)
|
||||
const slides = filteredPhotos.slice(offset, offset + limit)
|
||||
const carouselId = idProp ?? 'carousel';
|
||||
const filteredPhotos = (photos as { file: string; horizontal?: boolean }[]).filter((p) => p.horizontal);
|
||||
const slides = filteredPhotos.slice(offset, offset + limit);
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -34,9 +46,19 @@ const slides = filteredPhotos.slice(offset, offset + limit)
|
||||
<div id={`carousel-${carouselId}`} class={`carousel slide${fade ? ' carousel-fade' : ''}`} data-bs-ride="carousel">
|
||||
{
|
||||
indicators && (
|
||||
<div class={`carousel-indicators${indicatorsVertical ? ' carousel-indicators-vertical' : ''}${indicatorsDot ? ' carousel-indicators-dot' : indicatorsThumb ? ' carousel-indicators-thumb' : ''}`}>
|
||||
<div
|
||||
class={`carousel-indicators${indicatorsVertical ? ' carousel-indicators-vertical' : ''}${
|
||||
indicatorsDot ? ' carousel-indicators-dot' : indicatorsThumb ? ' carousel-indicators-thumb' : ''
|
||||
}`}
|
||||
>
|
||||
{slides.map((photo, i) => (
|
||||
<button type="button" data-bs-target={`#carousel-${carouselId}`} data-bs-slide-to={`${i}`} class={`${indicatorsThumbRatio ? ' ratio ratio-4x3' : ''}${i === 0 ? ' active' : ''}`} style={indicatorsThumb ? `background-image: url(./static/photos/${photo.file})` : undefined} />
|
||||
<button
|
||||
type="button"
|
||||
data-bs-target={`#carousel-${carouselId}`}
|
||||
data-bs-slide-to={`${i}`}
|
||||
class={`${indicatorsThumbRatio ? ' ratio ratio-4x3' : ''}${i === 0 ? ' active' : ''}`}
|
||||
style={indicatorsThumb ? `background-image: url(./static/photos/${photo.file})` : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
// The `{% highlight css %}` block emits the raw CSS text (no highlight markup in
|
||||
// the dev build), rendered here as a text node inside .card-code.
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
// Raw CSS text inside .card-code (no highlight markup).
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
|
||||
const code = `
|
||||
.card-footer {
|
||||
@@ -11,7 +10,7 @@ const code = `
|
||||
border-radius: 0 0 1 2;
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
---
|
||||
// Equivalent of cards/credit-card.html
|
||||
// TODO: ui/form/input-mask.html is inlined only with the params used here
|
||||
// (mask + visible; placeholder falls back to the mask value).
|
||||
import Button from '@ui/Button.astro'
|
||||
import FormGroup from '@ui/FormGroup.astro'
|
||||
import Button from '@ui/Button.astro';
|
||||
import FormGroup from '@ui/FormGroup.astro';
|
||||
|
||||
const months = Array.from({ length: 12 }, (_, i) => i + 1)
|
||||
const years = Array.from({ length: 11 }, (_, i) => 2020 + i)
|
||||
const months = Array.from({ length: 12 }, (_, i) => i + 1);
|
||||
const years = Array.from({ length: 11 }, (_, i) => 2020 + i);
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-label">Card number</div>
|
||||
<input type="text" name="input-mask" class="form-control" data-mask="0000 0000 0000 0000" data-mask-visible="true" placeholder="0000 0000 0000 0000" autocomplete="off" />
|
||||
<input
|
||||
type="text"
|
||||
name="input-mask"
|
||||
class="form-control"
|
||||
data-mask="0000 0000 0000 0000"
|
||||
data-mask-visible="true"
|
||||
placeholder="0000 0000 0000 0000"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
---
|
||||
// TODO: unported ui/crm-stats.html branches (unused on dashboard-crm):
|
||||
// person-id (avatar), chart-data + chart-position/chart-label/chart-label-icon
|
||||
// (sparkline), small-icon, description-value/description-value-color, trending,
|
||||
// button, chart-type/chart-color.
|
||||
|
||||
import Subheader from '@ui/Subheader.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Trending from '@ui/Trending.astro'
|
||||
import Subheader from '@ui/Subheader.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Trending from '@ui/Trending.astro';
|
||||
|
||||
interface Props {
|
||||
color?: string
|
||||
icon?: string
|
||||
color?: string;
|
||||
icon?: string;
|
||||
/** lt — light avatar background (bg-{color}-lt) instead of bg-{color} text-white */
|
||||
lt?: boolean
|
||||
title?: string
|
||||
description?: string
|
||||
/** change-value — passed straight to ui/trending.html (keeps its +/- sign) */
|
||||
changeValue?: string
|
||||
lt?: boolean;
|
||||
title?: string;
|
||||
description?: string;
|
||||
/** change-value — passed to Trending (keeps its +/- sign) */
|
||||
changeValue?: string;
|
||||
/** change-value-unit — trending unit; defaults to '%' in Trending when unset */
|
||||
changeValueUnit?: string
|
||||
class?: string
|
||||
changeValueUnit?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { color, icon, lt, title = '1700', description = 'Users', changeValue, changeValueUnit, class: className } = Astro.props
|
||||
const {
|
||||
color,
|
||||
icon,
|
||||
lt,
|
||||
title = '1700',
|
||||
description = 'Users',
|
||||
changeValue,
|
||||
changeValueUnit,
|
||||
class: className,
|
||||
} = Astro.props;
|
||||
|
||||
const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square']
|
||||
const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square'];
|
||||
---
|
||||
|
||||
<div class:list={['card card-sm', className]}>
|
||||
@@ -33,9 +37,7 @@ const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'a
|
||||
{
|
||||
icon && (
|
||||
<div class="col-auto">
|
||||
<span class:list={avatarClass}>
|
||||
<Icon name={icon} />
|
||||
</span>
|
||||
<span class:list={avatarClass}><Icon name={icon} /></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
---
|
||||
// Equivalent of cards/empty-team.html
|
||||
// ui/avatar-list.html is inlined (stacked, no explicit size — the `avatar-1`
|
||||
// class in the Eleventy output is the include['size'] bug and is skipped).
|
||||
import AvatarList from '@ui/AvatarList.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import AvatarList from '@ui/AvatarList.astro';
|
||||
import Button from '@ui/Button.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
---
|
||||
|
||||
<div class="card card-lg card-dashed card-transparent">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/forgot-password.html
|
||||
|
||||
import FormFooter from '@ui/FormFooter.astro'
|
||||
import Button from '@ui/Button.astro'
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
---
|
||||
// Equivalent of cards/gallery-photo.html.
|
||||
// The Liquid include reads `photo` and `forloop.index` from the parent loop scope —
|
||||
// in Astro they are passed explicitly as the `photo` and `index` props.
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random'
|
||||
// `photo` and `index` are passed explicitly from the parent loop.
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random';
|
||||
|
||||
interface Photo {
|
||||
file: string
|
||||
[key: string]: unknown
|
||||
file: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
photo: Photo
|
||||
person: Person
|
||||
/** 1-based loop index (forloop.index in Liquid) — seeds the deterministic pseudo-random values */
|
||||
index: number
|
||||
hideLikes?: boolean
|
||||
photo: Photo;
|
||||
person: Person;
|
||||
/** 1-based loop index — seeds the deterministic pseudo-random values */
|
||||
index: number;
|
||||
hideLikes?: boolean;
|
||||
}
|
||||
|
||||
const { photo, person, index, hideLikes } = Astro.props
|
||||
const { photo, person, index, hideLikes } = Astro.props;
|
||||
|
||||
// Liquid: {% if forloop.index > 2 and forloop.index < 9 or forloop.index == 10 %}
|
||||
const heartClass = (index > 2 && index < 9) || index === 10 ? 'icon-filled text-red' : undefined
|
||||
// Heart filled when (index > 2 && index < 9) || index === 10.
|
||||
const heartClass = (index > 2 && index < 9) || index === 10 ? 'icon-filled text-red' : undefined;
|
||||
---
|
||||
|
||||
<div class="card card-sm">
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
---
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import icons from '@data/icons.json'
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import icons from '@data/icons.json';
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
type?: 'outline' | 'filled'
|
||||
title?: string;
|
||||
type?: 'outline' | 'filled';
|
||||
}
|
||||
|
||||
const { title, type = 'outline' } = Astro.props
|
||||
const { title, type = 'outline' } = Astro.props;
|
||||
|
||||
// Liquid: limit is Infinity, but 20 when environment == 'development'.
|
||||
// The PoC mirrors the development build (see BaseLayout), so limit = 20.
|
||||
// NOTE: the limit applies to the ITERATION, before the svg[type] filter —
|
||||
// the "filled" card therefore shows only the filled icons among the first 20.
|
||||
const limit = 20
|
||||
// Dev preview limits icons to 20 (see BaseLayout).
|
||||
// The limit applies before the svg[type] filter — the "filled" card shows only filled icons among the first 20.
|
||||
const limit = 20;
|
||||
|
||||
const entries = Object.entries(icons as unknown as Record<string, { svg?: Record<string, string> }>).slice(0, limit)
|
||||
const entries = Object.entries(icons as Record<string, { svg?: Record<string, string> }>).slice(
|
||||
0,
|
||||
limit,
|
||||
);
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -29,13 +30,21 @@ const entries = Object.entries(icons as unknown as Record<string, { svg?: Record
|
||||
entries.map(
|
||||
([iconName, icon]) =>
|
||||
icon.svg?.[type] && (
|
||||
<a href={`https://tabler.io/icons/icon/${iconName}`} target="_blank" rel="noopener" class="demo-icons-list-item" title={iconName} data-bs-toggle="tooltip" data-bs-placement="top">
|
||||
<a
|
||||
href={`https://tabler.io/icons/icon/${iconName}`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="demo-icons-list-item"
|
||||
title={iconName}
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
>
|
||||
<Icon name={iconName} type={type} />
|
||||
</a>
|
||||
),
|
||||
)
|
||||
}
|
||||
{/* Liquid: {% for icon in (0..20) %} → 21 empty divs (flexbox filler). */}
|
||||
{/* 21 empty divs (flexbox filler) */}
|
||||
{Array.from({ length: 21 }).map(() => <div />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/invoice.html (static markup, no parameters)
|
||||
---
|
||||
|
||||
<div class="card card-lg">
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
---
|
||||
// The feature names are the fixed list from the Liquid source; the
|
||||
// `features` string is split per-character into a 1/0 availability mask.
|
||||
// ui/ribbon.html is inlined here in its bookmark+top+color variant (the only
|
||||
// one used by this card); its star icon renders as the outline `star`
|
||||
// (ui/icon.html ignores the `use-svg`/`filled` args).
|
||||
import Icon from '@ui/Icon.astro'
|
||||
// Feature names are a fixed list; the `features` string is split per-character into a 1/0 availability mask.
|
||||
import Icon from '@ui/Icon.astro';
|
||||
|
||||
interface Props {
|
||||
price?: string
|
||||
users?: string | number
|
||||
category?: string
|
||||
features?: string
|
||||
price?: string;
|
||||
users?: string | number;
|
||||
category?: string;
|
||||
features?: string;
|
||||
/** featured-color — ribbon + button color (e.g. "green") */
|
||||
featuredColor?: string
|
||||
featuredColor?: string;
|
||||
}
|
||||
|
||||
const { price = '79', users = 10, category = 'Enterprise', features = '1000', featuredColor } = Astro.props
|
||||
const { price = '79', users = 10, category = 'Enterprise', features = '1000', featuredColor } = Astro.props;
|
||||
|
||||
const featureNames = ['Sharing Tools', 'Design Tools', 'Private Messages', 'Twitter API']
|
||||
const availableFeatures = features.split('')
|
||||
const featureNames = ['Sharing Tools', 'Design Tools', 'Private Messages', 'Twitter API'];
|
||||
const availableFeatures = features.split('');
|
||||
---
|
||||
|
||||
<div class="card card-md">
|
||||
@@ -40,7 +36,11 @@ const availableFeatures = features.split('')
|
||||
{
|
||||
featureNames.map((feature, i) => (
|
||||
<li>
|
||||
{availableFeatures[i] === '1' ? <Icon name="check" class="me-1 text-success" /> : <Icon name="x" class="me-1 text-danger" />}
|
||||
{availableFeatures[i] === '1' ? (
|
||||
<Icon name="check" class="me-1 text-success" />
|
||||
) : (
|
||||
<Icon name="x" class="me-1 text-danger" />
|
||||
)}
|
||||
{feature}
|
||||
</li>
|
||||
))
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
---
|
||||
// TODO: Liquid default `person = include.person | default: people[1]` — the
|
||||
// no-person fallback branch is unused on card-gradients (person is always
|
||||
// passed), so `person` is required here.
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import ButtonList from '@ui/ButtonList.astro'
|
||||
// TODO: fallback to people[1] when person omitted — unused on card-gradients, so `person` is required here.
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import ButtonList from '@ui/ButtonList.astro';
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
job_title?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
job_title?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
person: Person
|
||||
color?: string
|
||||
person: Person;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const { person, color = 'yellow' } = Astro.props
|
||||
const { person, color = 'yellow' } = Astro.props;
|
||||
---
|
||||
|
||||
<div class={`card card-gradient card-gradient-${color}`}>
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
---
|
||||
// NOTE: the widgets page passes a `value=` argument, but the Liquid source only
|
||||
// reads `percentage` (default 20) for the progress bar — `value` is never used.
|
||||
// It is accepted here for signature parity but intentionally ignored.
|
||||
import Progress from '@ui/Progress.astro'
|
||||
import AvatarList from '@ui/AvatarList.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
// NOTE: widgets page passes `value=` but only `percentage` (default 20) drives the bar — `value` is accepted but ignored.
|
||||
import Progress from '@ui/Progress.astro';
|
||||
import AvatarList from '@ui/AvatarList.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
badge?: string
|
||||
offset?: number
|
||||
limit?: number
|
||||
percentage?: number | string
|
||||
percentageColor?: string
|
||||
due?: string
|
||||
title?: string;
|
||||
badge?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
percentage?: number | string;
|
||||
percentageColor?: string;
|
||||
due?: string;
|
||||
/** unused — see note above */
|
||||
value?: number | string
|
||||
value?: number | string;
|
||||
}
|
||||
|
||||
const { title = 'Task Title', badge, offset = 40, limit = 7, percentage = 20, percentageColor = 'green', due = '2 days' } = Astro.props
|
||||
const {
|
||||
title = 'Task Title',
|
||||
badge,
|
||||
offset = 40,
|
||||
limit = 7,
|
||||
percentage = 20,
|
||||
percentageColor = 'green',
|
||||
due = '2 days',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -27,14 +33,7 @@ const { title = 'Task Title', badge, offset = 40, limit = 7, percentage = 20, pe
|
||||
<div class="card-body">
|
||||
<CardTitle>
|
||||
<a href="#">{title}</a>
|
||||
{
|
||||
badge && (
|
||||
<Fragment>
|
||||
{' '}
|
||||
<span class="badge ms-2">{badge}</span>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
{badge && <Fragment> <span class="badge ms-2">{badge}</span></Fragment>}
|
||||
</CardTitle>
|
||||
|
||||
<AvatarList offset={offset} limit={limit} stacked class="mb-3" />
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
---
|
||||
// NOTE: the `days-ago` param is assigned in the Liquid source but never rendered
|
||||
// ("Updated 2 hours ago" is hardcoded) — accepted here but unused.
|
||||
import Progress from '@ui/Progress.astro'
|
||||
import CardDropdown from '@ui/CardDropdown.astro'
|
||||
import projects from '@data/projects.json'
|
||||
// NOTE: `days-ago` param accepted but unused ("Updated 2 hours ago" is hardcoded).
|
||||
import Progress from '@ui/Progress.astro';
|
||||
import CardDropdown from '@ui/CardDropdown.astro';
|
||||
import projects from '@data/projects.json';
|
||||
|
||||
interface Project {
|
||||
title?: string
|
||||
image?: string
|
||||
[key: string]: unknown
|
||||
title?: string;
|
||||
image?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
projectId?: number
|
||||
progress?: number
|
||||
daysAgo?: number
|
||||
projectId?: number;
|
||||
progress?: number;
|
||||
daysAgo?: number;
|
||||
}
|
||||
|
||||
const { projectId = 0, progress = 25 } = Astro.props
|
||||
const project = (projects as Project[])[projectId]
|
||||
const { projectId = 0, progress = 25 } = Astro.props;
|
||||
const project = (projects as Project[])[projectId];
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -31,7 +30,9 @@ const project = (projects as Project[])[projectId]
|
||||
<h3 class="card-title mb-1">
|
||||
<a href="#" class="text-reset">{project.title}</a>
|
||||
</h3>
|
||||
<div class="text-secondary">Updated 2 hours ago</div>
|
||||
<div class="text-secondary">
|
||||
Updated 2 hours ago
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="row g-2 align-items-center">
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
---
|
||||
// NOTE: the Liquid source passes class="icon text-{{ item.icon_color }}" as an
|
||||
// include argument to ui/icon.html; Liquid does NOT re-interpolate include-arg
|
||||
// strings, so the reference output contains the LITERAL text
|
||||
// `icon text-{{ item.icon_color }}` in the icon class. We reproduce it verbatim.
|
||||
// Icon class is a literal template string (color token is not interpolated).
|
||||
import CardActions from '@ui/CardActions.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import crmDashboard from '@data/crm-dashboard.json';
|
||||
|
||||
import CardActions from '@ui/CardActions.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import crmDashboard from '@data/crm-dashboard.json'
|
||||
|
||||
const activity = crmDashboard.recent_activity
|
||||
const activity = crmDashboard.recent_activity;
|
||||
---
|
||||
|
||||
<div class="card" style="height: 28rem">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/sign-up.html
|
||||
|
||||
import FormFooter from '@ui/FormFooter.astro'
|
||||
import InputGroup from '@ui/InputGroup.astro'
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
// Port cards/social-traffic.html
|
||||
import { formatNumber } from '@shared/lib/string-format'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import { formatNumber } from '@shared/lib/string-format';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
|
||||
const services = 'Instagram:3550,Twitter:1798,Facebook:1245,TikTok:986,Pinterest:854,VK:650,Pinterest:420'.split(',').map((service) => {
|
||||
const [name, visitors] = service.split(':')
|
||||
return { name, visitors: Number(visitors) }
|
||||
})
|
||||
const services = 'Instagram:3550,Twitter:1798,Facebook:1245,TikTok:986,Pinterest:854,VK:650,Pinterest:420'
|
||||
.split(',')
|
||||
.map((service) => {
|
||||
const [name, visitors] = service.split(':');
|
||||
return { name, visitors: Number(visitors) };
|
||||
});
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -21,19 +22,18 @@ const services = 'Instagram:3550,Twitter:1798,Facebook:1245,TikTok:986,Pinterest
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
services.map((service) => (
|
||||
{services.map((service) => (
|
||||
<tr>
|
||||
<td>{service.name}</td>
|
||||
<td>{formatNumber(service.visitors)}</td>
|
||||
<td class="w-50">
|
||||
<div class="progress progress-xs">
|
||||
<div class="progress-bar bg-primary" style={`width: ${(service.visitors / 5000.0) * 100}%`} />
|
||||
|
||||
<div class="progress-bar bg-primary" style={`width: ${(service.visitors / 5000.0) * 100}%`}></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
---
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro'
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro';
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
icons?: boolean
|
||||
/** hide-text param; not exercised on tabs.html, ported 1:1 from the Liquid source */
|
||||
hideText?: boolean
|
||||
reverse?: boolean
|
||||
justified?: boolean
|
||||
activity?: boolean
|
||||
disabled?: boolean
|
||||
dropdown?: boolean
|
||||
settings?: boolean
|
||||
animation?: boolean
|
||||
id: string;
|
||||
icons?: boolean;
|
||||
/** hide-text param; not used by current call sites */
|
||||
hideText?: boolean;
|
||||
reverse?: boolean;
|
||||
justified?: boolean;
|
||||
activity?: boolean;
|
||||
disabled?: boolean;
|
||||
dropdown?: boolean;
|
||||
settings?: boolean;
|
||||
animation?: boolean;
|
||||
}
|
||||
|
||||
const { id, icons, hideText, reverse = false, justified, activity, disabled, dropdown, settings, animation } = Astro.props
|
||||
const {
|
||||
id,
|
||||
icons,
|
||||
hideText,
|
||||
reverse = false,
|
||||
justified,
|
||||
activity,
|
||||
disabled,
|
||||
dropdown,
|
||||
settings,
|
||||
animation,
|
||||
} = Astro.props;
|
||||
|
||||
const iconClass = hideText ? undefined : 'me-2'
|
||||
const navClass = `nav nav-tabs card-header-tabs${reverse ? ' flex-row-reverse' : ''}${justified ? ' nav-fill' : ''}`
|
||||
const paneClass = `tab-pane${animation ? ' fade' : ''}`
|
||||
const iconClass = hideText ? undefined : 'me-2';
|
||||
const navClass = `nav nav-tabs card-header-tabs${reverse ? ' flex-row-reverse' : ''}${justified ? ' nav-fill' : ''}`;
|
||||
const paneClass = `tab-pane${animation ? ' fade' : ''}`;
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -33,47 +44,29 @@ const paneClass = `tab-pane${animation ? ' fade' : ''}`
|
||||
<a href={`#tabs-profile-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="user" class={iconClass} />}{!hideText && 'Profile'}</a>
|
||||
</li>
|
||||
|
||||
{
|
||||
activity && (
|
||||
{activity && (
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link" data-bs-toggle="tab">
|
||||
{icons && <Icon name="activity" class={iconClass} />}
|
||||
{!hideText && 'Activity'}
|
||||
</a>
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="activity" class={iconClass} />}{!hideText && 'Activity'}</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
{
|
||||
disabled && (
|
||||
{disabled && (
|
||||
<li class="nav-item">
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link disabled" data-bs-toggle="tab">
|
||||
{icons && <Icon name="x" class={iconClass} />}
|
||||
{!hideText && 'Disabled'}
|
||||
</a>
|
||||
<a href={`#tabs-activity-${id}`} class="nav-link disabled" data-bs-toggle="tab">{icons && <Icon name="x" class={iconClass} />}{!hideText && 'Disabled'}</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
{
|
||||
dropdown && (
|
||||
{dropdown && (
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||
Dropdown
|
||||
</a>
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
|
||||
<DropdownMenu />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
settings && (
|
||||
)}
|
||||
{settings && (
|
||||
<li class={`nav-item ${reverse ? 'me-auto' : 'ms-auto'}`}>
|
||||
<a href={`#tabs-settings-${id}`} class="nav-link" title="Settings" data-bs-toggle="tab">
|
||||
<Icon name="settings" />
|
||||
</a>
|
||||
<a href={`#tabs-settings-${id}`} class="nav-link" title="Settings" data-bs-toggle="tab"><Icon name="settings" /></a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -86,22 +79,18 @@ const paneClass = `tab-pane${animation ? ' fade' : ''}`
|
||||
<h4>Profile tab</h4>
|
||||
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
|
||||
</div>
|
||||
{
|
||||
settings && (
|
||||
{settings && (
|
||||
<div class={paneClass} id={`tabs-settings-${id}`}>
|
||||
<h4>Settings tab</h4>
|
||||
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
activity && (
|
||||
)}
|
||||
{activity && (
|
||||
<div class={paneClass} id={`tabs-activity-${id}`}>
|
||||
<h4>Activity tab</h4>
|
||||
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
---
|
||||
// person = people[person-id]; the cover photo is photos[person-id].file.
|
||||
// Base card-cover already carries `card-cover-blurred`; `blurred` adds it a 2nd time
|
||||
// (faithful to the Liquid source).
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import people from '@data/people.json'
|
||||
import photos from '@data/photos.json'
|
||||
// Base card-cover already carries `card-cover-blurred`; `blurred` adds it a second time.
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import people from '@data/people.json';
|
||||
import photos from '@data/photos.json';
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
job_title?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
job_title?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Photo {
|
||||
file?: string
|
||||
[key: string]: unknown
|
||||
file?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
personId?: number
|
||||
blurred?: boolean
|
||||
personId?: number;
|
||||
blurred?: boolean;
|
||||
}
|
||||
|
||||
const { personId = 0, blurred } = Astro.props
|
||||
const person = (people as Person[])[personId]
|
||||
const photo = (photos as Photo[])[personId]
|
||||
const { personId = 0, blurred } = Astro.props;
|
||||
const person = (people as Person[])[personId];
|
||||
const photo = (photos as Photo[])[personId];
|
||||
---
|
||||
|
||||
<a class="card card-link" href="#">
|
||||
<div class={`card-cover card-cover-blurred text-center${blurred ? ' card-cover-blurred' : ''}`} style={`background-image: url(./static/photos/${photo.file})`}>
|
||||
<div
|
||||
class={`card-cover card-cover-blurred text-center${blurred ? ' card-cover-blurred' : ''}`}
|
||||
style={`background-image: url(./static/photos/${photo.file})`}
|
||||
>
|
||||
<Avatar size="xl" person={person} thumb />
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
---
|
||||
// person = people[person-id] (object passed to Avatar; `rounded` is a no-op in avatar.html).
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import people from '@data/people.json'
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import people from '@data/people.json';
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
job_title?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
job_title?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
personId?: number
|
||||
personId?: number;
|
||||
}
|
||||
|
||||
const { personId = 0 } = Astro.props
|
||||
const person = (people as Person[])[personId]
|
||||
const { personId = 0 } = Astro.props;
|
||||
const person = (people as Person[])[personId];
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
---
|
||||
// person = people[person-id - 1] (Liquid default person-id = 25 → people[24]).
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import Flag from '@ui/Flag.astro'
|
||||
import people from '@data/people.json'
|
||||
// person = people[person-id - 1] (default person-id = 25 → people[24]).
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import Flag from '@ui/Flag.astro';
|
||||
import people from '@data/people.json';
|
||||
|
||||
interface Person {
|
||||
university?: string
|
||||
company?: string
|
||||
city?: string
|
||||
country?: string
|
||||
country_code?: string
|
||||
birth_date?: string
|
||||
time_zone?: string
|
||||
[key: string]: unknown
|
||||
university?: string;
|
||||
company?: string;
|
||||
city?: string;
|
||||
country?: string;
|
||||
country_code?: string;
|
||||
birth_date?: string;
|
||||
time_zone?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
personId?: number
|
||||
title?: string;
|
||||
personId?: number;
|
||||
}
|
||||
|
||||
const { title = 'Basic info', personId = 25 } = Astro.props
|
||||
const person = (people as Person[])[personId - 1]
|
||||
const { title = 'Basic info', personId = 25 } = Astro.props;
|
||||
const person = (people as Person[])[personId - 1];
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,56 +1,63 @@
|
||||
---
|
||||
// Equivalent of cards/users-list.html
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import commits from '@data/commits.json'
|
||||
import { randomNumber } from '@shared/lib/pseudo-random'
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import commits from '@data/commits.json';
|
||||
import { randomNumber } from '@shared/lib/pseudo-random';
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
photo?: string
|
||||
last_name?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
last_name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
limit?: number
|
||||
offset?: number
|
||||
hoverable?: boolean
|
||||
checkbox?: boolean
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
hoverable?: boolean;
|
||||
checkbox?: boolean;
|
||||
/** checked-ids — 1-based indices, e.g. [2, 5, 8] */
|
||||
checkedIds?: number[]
|
||||
title?: string
|
||||
class?: string
|
||||
checkedIds?: number[];
|
||||
title?: string;
|
||||
class?: string;
|
||||
/**
|
||||
* Inert. The lists.html Contacts card passes `hover=true`, but
|
||||
* users-list.html never reads it — only `hoverable` toggles the star column.
|
||||
* Declared so the call site can mirror the Liquid source faithfully.
|
||||
* Declared for call-site compatibility.
|
||||
*/
|
||||
hover?: boolean
|
||||
hover?: boolean;
|
||||
}
|
||||
|
||||
const { limit = 8, offset = 0, hoverable = false, checkbox, checkedIds, title = 'Last commits', class: className } = Astro.props
|
||||
const {
|
||||
limit = 8,
|
||||
offset = 0,
|
||||
hoverable = false,
|
||||
checkbox,
|
||||
checkedIds,
|
||||
title = 'Last commits',
|
||||
class: className,
|
||||
} = Astro.props;
|
||||
|
||||
const colors = ['green', 'red', 'yellow', 'x', 'x']
|
||||
const commitList = commits as { description: string }[]
|
||||
const colors = ['green', 'red', 'yellow', 'x', 'x'];
|
||||
const commitList = commits as { description: string }[];
|
||||
|
||||
const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx) => {
|
||||
const index = idx + 1 // forloop.index (1-based)
|
||||
const color = colors[randomNumber(index + 5, 0, colors.length - 1)]
|
||||
const checked = checkedIds?.includes(index) ?? false
|
||||
const i = index + offset
|
||||
const index = idx + 1; // forloop.index (1-based)
|
||||
const color = colors[randomNumber(index + 5, 0, colors.length - 1)];
|
||||
const checked = checkedIds?.includes(index) ?? false;
|
||||
const i = index + offset;
|
||||
return {
|
||||
person,
|
||||
color,
|
||||
checked,
|
||||
description: commitList[i]?.description,
|
||||
starColor: checked ? 'text-yellow' : 'text-secondary',
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
---
|
||||
|
||||
<div class={`card${className ? ` ${className}` : ''}`}>
|
||||
@@ -78,9 +85,7 @@ const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx
|
||||
</div>
|
||||
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-reset d-block">
|
||||
{row.person.full_name}
|
||||
</a>
|
||||
<a href="#" class="text-reset d-block">{row.person.full_name}</a>
|
||||
<div class="d-block text-secondary text-truncate mt-n1">{row.description}</div>
|
||||
</div>
|
||||
{hoverable && (
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
---
|
||||
// Equivalent of cards/users-list-2.html
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random'
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random';
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
photo?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
limit?: number
|
||||
offset?: number
|
||||
title?: string
|
||||
class?: string
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
title?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { limit = 10, offset = 0, title = 'Top users', class: className } = Astro.props
|
||||
const { limit = 10, offset = 0, title = 'Top users', class: className } = Astro.props;
|
||||
|
||||
const colors = 'green,red,yellow,x,x'.split(',')
|
||||
const colors = 'green,red,yellow,x,x'.split(',');
|
||||
|
||||
const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx) => {
|
||||
const index = idx + 1 // forloop.index (1-based)
|
||||
const index = idx + 1; // forloop.index (1-based)
|
||||
return {
|
||||
person,
|
||||
status: colors[randomNumber(index + 5, 0, colors.length - 1)],
|
||||
timeago: timeagoLabel(index, 6),
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
---
|
||||
|
||||
<div class={`card${className ? ` ${className}` : ''}`}>
|
||||
@@ -46,9 +45,7 @@ const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx
|
||||
<Avatar person={row.person} status={row.status} />
|
||||
</a>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-reset d-block text-truncate">
|
||||
{row.person.full_name}
|
||||
</a>
|
||||
<a href="#" class="text-reset d-block text-truncate">{row.person.full_name}</a>
|
||||
<div class="text-secondary text-truncate mt-n1">{row.timeago}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
---
|
||||
// Equivalent of cards/users-list-headers.html
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import ListGroupHeader from '@ui/ListGroupHeader.astro'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import people from '@data/people.json'
|
||||
import commits from '@data/commits.json'
|
||||
import { sortBy } from '@shared/lib/string-format'
|
||||
import Avatar from '@ui/Avatar.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
import ListGroupHeader from '@ui/ListGroupHeader.astro';
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import people from '@data/people.json';
|
||||
import commits from '@data/commits.json';
|
||||
import { sortBy } from '@shared/lib/string-format';
|
||||
|
||||
interface Person {
|
||||
full_name?: string
|
||||
photo?: string
|
||||
last_name?: string
|
||||
[key: string]: unknown
|
||||
full_name?: string;
|
||||
photo?: string;
|
||||
last_name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const { title = 'People' } = Astro.props
|
||||
const { title = 'People' } = Astro.props;
|
||||
|
||||
const commitList = commits as { description: string }[]
|
||||
const commitList = commits as { description: string }[];
|
||||
|
||||
// Equivalent of `people | sort: 'last_name'` (LiquidJS case-sensitive sort).
|
||||
const sorted = sortBy(people as Person[], (p) => p.last_name ?? '')
|
||||
// Sort by last_name (case-sensitive).
|
||||
const sorted = sortBy(people as Person[], (p) => p.last_name ?? '');
|
||||
|
||||
// offset is unset in the include, so `forloop.index | plus: offset` == forloop.index.
|
||||
let prevLetter = ''
|
||||
let prevLetter = '';
|
||||
const rows = sorted.map((person, idx) => {
|
||||
const index = idx + 1 // forloop.index (1-based)
|
||||
const firstLetter = (person.last_name ?? '').slice(0, 1)
|
||||
let header: string | null = null
|
||||
const index = idx + 1; // forloop.index (1-based)
|
||||
const firstLetter = (person.last_name ?? '').slice(0, 1);
|
||||
let header: string | null = null;
|
||||
if (prevLetter !== firstLetter) {
|
||||
prevLetter = firstLetter
|
||||
header = firstLetter
|
||||
prevLetter = firstLetter;
|
||||
header = firstLetter;
|
||||
}
|
||||
return { person, header, description: commitList[index]?.description }
|
||||
})
|
||||
return { person, header, description: commitList[index]?.description };
|
||||
});
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
@@ -59,9 +58,7 @@ const rows = sorted.map((person, idx) => {
|
||||
</a>
|
||||
</div>
|
||||
<div class="col text-truncate">
|
||||
<a href="#" class="text-body d-block">
|
||||
{row.person.full_name}
|
||||
</a>
|
||||
<a href="#" class="text-body d-block">{row.person.full_name}</a>
|
||||
<div class="text-secondary text-truncate mt-n1">{row.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
---
|
||||
// Note: the Liquid include passes legend=true to ui/chart.html, but ui/chart.html
|
||||
// reads the legend flag from the chart data (data.legend), not the include arg —
|
||||
// so the arg is inert and Chart.astro needs no legend prop.
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import Chart from '@ui/Chart.astro'
|
||||
import DropdownDays from '@ui/DropdownDays.astro'
|
||||
// legend=true at call site is inert — Chart reads legend from chart data, not props.
|
||||
import CardTitle from '@ui/CardTitle.astro';
|
||||
import Chart from '@ui/Chart.astro';
|
||||
import DropdownDays from '@ui/DropdownDays.astro';
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
---
|
||||
// The nested ui/switch-icon.html include (icon="heart") is inlined below —
|
||||
// it has no Astro component yet and this is its only use so far. For
|
||||
// icon="heart" the Liquid include resolves to: icon-b="heart",
|
||||
// icon-b-class="icon-filled", icon-a-color="muted", icon-b-color="red".
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro'
|
||||
import ListGroup from '@ui/ListGroup.astro'
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro'
|
||||
import tracks from '@data/tracks.json'
|
||||
import { millisecondsToMinutes } from '@shared/lib/string-format'
|
||||
// Switch-icon heart variant inlined: icon-b="heart", icon-b-class="icon-filled", icon-a-color="muted", icon-b-color="red".
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import DropdownMenu from '@ui/DropdownMenu.astro';
|
||||
import ListGroup from '@ui/ListGroup.astro';
|
||||
import ListGroupItem from '@ui/ListGroupItem.astro';
|
||||
import tracks from '@data/tracks.json';
|
||||
import { millisecondsToMinutes } from '@shared/lib/string-format';
|
||||
|
||||
// {% for track in tracks limit: 12 %}
|
||||
const items = tracks.slice(0, 12)
|
||||
const items = tracks.slice(0, 12);
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<ListGroup class="card-list-group">
|
||||
{
|
||||
items.map((track, index) => (
|
||||
{items.map((track, index) => (
|
||||
<ListGroupItem>
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto fs-3">{index + 1}</div>
|
||||
<div class="col-auto fs-3">
|
||||
{index + 1}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<img src={`./static/tracks/${track.album.images[1].path}`} class="rounded" alt={track.name} width="40" height="40" />
|
||||
</div>
|
||||
<div class="col">
|
||||
{track.name}
|
||||
<div class="text-secondary">{track.artists.map((artist) => artist.name).join(', ')}</div>
|
||||
<div class="text-secondary">
|
||||
{track.artists.map((artist) => artist.name).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto text-secondary">
|
||||
{millisecondsToMinutes(track.duration_ms)}
|
||||
</div>
|
||||
<div class="col-auto text-secondary">{millisecondsToMinutes(track.duration_ms)}</div>
|
||||
<div class="col-auto">
|
||||
<a href="#" class="link-secondary">
|
||||
<button class="switch-icon" data-bs-toggle="switch-icon">
|
||||
@@ -43,15 +44,12 @@ const items = tracks.slice(0, 12)
|
||||
</div>
|
||||
<div class="col-auto lh-1">
|
||||
<div class="dropdown">
|
||||
<a href="#" class="link-secondary" data-bs-toggle="dropdown">
|
||||
<Icon name="dots" />
|
||||
</a>
|
||||
<a href="#" class="link-secondary" data-bs-toggle="dropdown"><Icon name="dots" /></a>
|
||||
<DropdownMenu right />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ListGroupItem>
|
||||
))
|
||||
}
|
||||
))}
|
||||
</ListGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/placeholder/card-1.html
|
||||
---
|
||||
|
||||
<div class="card placeholder-glow">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/placeholder/card-2.html
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/placeholder/card-3.html
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
// Equivalent of cards/placeholder/card-4.html
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user