From 008824f952dc22d9489a09ffd6b53fc70da07f17 Mon Sep 17 00:00:00 2001 From: Carlos Costa Date: Wed, 11 Nov 2026 16:16:05 -0300 Subject: [PATCH] feat: add PieChart textures and polish ImageExpander, Sidebar, and Hero --- public/llms-full.txt | 41 +++++ public/registry.json | 2 +- src/App.tsx | 12 +- .../ImageExpander/ImageExpander.test.tsx | 22 ++- src/dashkit/ImageExpander/ImageExpander.tsx | 34 ++-- src/dashkit/PieChart/PieChart.test.tsx | 15 ++ src/dashkit/PieChart/PieChart.tsx | 151 +++++++++++++++--- src/pages/image-expander.mdx | 2 +- src/pages/pie-chart.mdx | 32 ++++ src/partials/Sidebar.tsx | 10 +- 10 files changed, 263 insertions(+), 58 deletions(-) diff --git a/public/llms-full.txt b/public/llms-full.txt index e68c9fb2..9541ca2a 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -796,6 +796,8 @@ bunx carllosnc/dashkit add navigation-menu The Navigation Menu uses a value-based system to track the active item. Ensure the `value` on `NavigationMenuTrigger` matches the `value` on `NavigationMenuContent`. + + ```tsx @@ -819,19 +821,24 @@ The Navigation Menu uses a value-based system to track the active item. Ensure t ### NavigationMenu + | Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | `delay` | `number` | `200` | Delay in milliseconds before closing the menu after the mouse leaves. | | `className` | `string` | - | Additional CSS classes for the root container. | + ### NavigationMenuTrigger + | Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | `value` | `string` | - | A unique value to associate the trigger with its corresponding content. | + ### NavigationMenuContent + | Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | `value` | `string` | - | The unique value matching the NavigationMenuTrigger. | @@ -6107,10 +6114,12 @@ bunx carllosnc/dashkit add statscard The `StatsCard` is perfect for dashboards and summary sections. + + ```tsx import { StatsCard } from 'dashkit'; @@ -6144,6 +6153,7 @@ export default () => ( Add visual context using icons from `react-icons`. + } status="info" description="Completion across all active sprints" @@ -6155,6 +6165,7 @@ Add visual context using icons from `react-icons`. description="Unassigned support requests" /> + ```tsx import { StatsCard } from 'dashkit'; import { HiOutlineChartBarSquare, HiOutlineTicket } from 'react-icons/hi2'; @@ -6182,10 +6193,12 @@ export default () => ( Visualize data trends directly within the card using the `chart` prop. + + ```tsx import { StatsCard } from 'dashkit'; @@ -6207,9 +6220,11 @@ export default () => ( Combine sparklines and descriptive footers for maximum context. + + ```tsx import { StatsCard } from 'dashkit'; @@ -6244,6 +6259,7 @@ export default () => ( ### StatsCard Props + | Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | `title` | `string` | - | The label for the metric. | @@ -6380,8 +6396,10 @@ bunx carllosnc/dashkit add systemlogs The `SystemLogs` component takes an array of `logs` and renders them in a dark, monospace container that mimics a terminal environment. + + ```tsx import { SystemLogs } from '@/dashkit/SystemLogs/SystemLogs' @@ -6414,8 +6432,10 @@ The component supports five distinct log levels, each with its own visual style: - `error`: Error messages (indicated by `[ERR]`) - `stable`: Stability or state updates (indicated by `[STB]`) + + ## Customization ### Auto-scrolling @@ -6431,6 +6451,7 @@ By default, the component automatically scrolls to the bottom when new logs are ## Props + | Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | `logs` | `LogEntry[]` | - | An array of log entries to display. Each entry has a type, message, and optional timestamp. | @@ -6908,6 +6929,25 @@ Transform any pie chart into a donut chart by setting the `innerRadius` property /> ``` +### Textured Slices + +Apply SVG patterns across the entire chart with the `texture` prop (`'striped' | 'dots' | 'grid' | 'gradient'`) or mix different textures per slice inside `data`. + + + + + +```tsx + +``` + ## API Reference ### PieChart @@ -6921,6 +6961,7 @@ Transform any pie chart into a donut chart by setting the `innerRadius` property | `showLabels` | `boolean` | `true` | Display the legend below the chart. | | `showTooltip` | `boolean` | `true` | Enable interactive segment tooltips. | | `animate` | `boolean` | `true` | Enable entrance animations. | +| `texture` | `'none' \| 'striped' \| 'dots' \| 'grid' \| 'gradient'` | `'none'` | SVG pattern texture applied over the slices (can also be overridden per item in data). | | `className` | `string` | - | Additional CSS for the container. | diff --git a/public/registry.json b/public/registry.json index 0fddd78c..f0037f16 100644 --- a/public/registry.json +++ b/public/registry.json @@ -998,7 +998,7 @@ "usePieChart" ], "propsDefinitions": [ - "export interface PieChartProps {\r\n data: PieChartDataItem[];\r\n className?: string;\r\n innerRadius?: number;\r\n showLabels?: boolean;\r\n showTooltip?: boolean;\r\n animate?: boolean;\r\n}", + "export interface PieChartProps {\r\n data: PieChartDataItem[];\r\n className?: string;\r\n innerRadius?: number;\r\n showLabels?: boolean;\r\n showTooltip?: boolean;\r\n animate?: boolean;\r\n texture?: PieChartTexture;\r\n}", "export interface UsePieChartProps {\r\n data: PieChartDataItem[];\r\n innerRadius: number;\r\n showTooltip: boolean;\r\n}" ] }, diff --git a/src/App.tsx b/src/App.tsx index e53cf8ea..773c7cd1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,10 +11,10 @@ import { Footer } from './partials/Footer' import { HomeExamples } from './partials/HomeExamples' import { Divider } from './dashkit/Divider/Divider' -const TITLE = "Dashkit | Build Modern Interfaces Faster"; -const DESCRIPTION = "Copy-paste 55+ customizable React 19 & Tailwind CSS v4 components straight into your project. Total code ownership, dynamic semantic tokens, and native context for AI coding agents."; -const TAGLINE = "Build Modern Interfaces Faster"; -const OG_DESCRIPTION = "Copy-paste 55+ customizable React 19 & Tailwind CSS v4 components straight into your project with total code ownership and native AI context."; +const TITLE = "Dashkit | Ship modern interfaces in minutes"; +const DESCRIPTION = "Ready-to-use components you can copy straight into your project. Fully customizable, accessible by default, and yours to own."; +const TAGLINE = "Ship modern interfaces in minutes"; +const OG_DESCRIPTION = "Ready-to-use components you can copy straight into your project. Fully customizable, accessible by default, and yours to own."; function App() { return ( @@ -44,11 +44,11 @@ function App() { /> -

+

{TAGLINE}

-

+

{DESCRIPTION}

diff --git a/src/dashkit/ImageExpander/ImageExpander.test.tsx b/src/dashkit/ImageExpander/ImageExpander.test.tsx index 93f96dcc..8d0837f1 100644 --- a/src/dashkit/ImageExpander/ImageExpander.test.tsx +++ b/src/dashkit/ImageExpander/ImageExpander.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { ImageExpander } from './ImageExpander'; describe('ImageExpander', () => { @@ -51,6 +51,26 @@ describe('ImageExpander', () => { fireEvent.keyDown(window, { key: 'Escape' }); // State change is internal, in a real env it would close. }); + + it('closes when clicking the expanded image', async () => { + render( + } + > + Thumbnail + + ); + + fireEvent.click(screen.getByAltText('Thumbnail')); + const expandedImg = screen.getByAltText('Expanded'); + expect(expandedImg).toBeInTheDocument(); + + fireEvent.click(expandedImg); + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); }); diff --git a/src/dashkit/ImageExpander/ImageExpander.tsx b/src/dashkit/ImageExpander/ImageExpander.tsx index 355fbaab..cbdb4078 100644 --- a/src/dashkit/ImageExpander/ImageExpander.tsx +++ b/src/dashkit/ImageExpander/ImageExpander.tsx @@ -70,39 +70,35 @@ export function ImageExpander({ children, full, caption, className }: ImageExpan animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} transition={{ type: 'spring', damping: 25, stiffness: 300 }} - className="relative z-[105] w-full h-full flex flex-col items-center justify-center overflow-auto no-scrollbar scroll-smooth selection:bg-ds-500/30" - onClick={(e: React.MouseEvent) => e.stopPropagation()} + className="relative z-[105] w-full h-full flex flex-col items-center justify-center p-4 md:p-8 overflow-auto no-scrollbar scroll-smooth selection:bg-ds-500/30 pointer-events-none" > -
-
+
e.stopPropagation()} + > +
setIsOpen(false)} + > {displayFull}
-
- - {caption && ( - <> - + {caption && ( {caption} - - )} + )} +
)} diff --git a/src/dashkit/PieChart/PieChart.test.tsx b/src/dashkit/PieChart/PieChart.test.tsx index 5aa78fee..0686fd3c 100644 --- a/src/dashkit/PieChart/PieChart.test.tsx +++ b/src/dashkit/PieChart/PieChart.test.tsx @@ -40,4 +40,19 @@ describe('PieChart', () => { const paths = container.querySelectorAll('path'); expect(paths).toHaveLength(3); }); + + it('renders texture overlay paths when global or per-slice texture is set', () => { + const { container } = render( + + ); + const paths = container.querySelectorAll('path'); + // 2 base slice paths + 1 texture overlay path for slice A + expect(paths).toHaveLength(3); + expect(paths[1].getAttribute('fill')).toMatch(/^url\(#pie-texture-striped-/); + }); }); diff --git a/src/dashkit/PieChart/PieChart.tsx b/src/dashkit/PieChart/PieChart.tsx index 5317ae87..26d763ba 100644 --- a/src/dashkit/PieChart/PieChart.tsx +++ b/src/dashkit/PieChart/PieChart.tsx @@ -1,12 +1,16 @@ import * as React from 'react'; +import { useId, Fragment } from 'react'; import { motion } from 'framer-motion'; import { cn } from '../utils/cn'; import { usePieChart } from './usePieChart'; +export type PieChartTexture = 'none' | 'striped' | 'dots' | 'grid' | 'gradient'; + export interface PieChartDataItem { label: string; value: number; color?: string; + texture?: PieChartTexture; } const DEFAULT_COLORS = [ @@ -24,6 +28,7 @@ export interface PieChartProps { showLabels?: boolean; showTooltip?: boolean; animate?: boolean; + texture?: PieChartTexture; } export function PieChart({ @@ -33,11 +38,14 @@ export function PieChart({ showLabels = true, showTooltip = true, animate = true, + texture = 'none', }: PieChartProps) { + const chartId = useId().replace(/[^a-zA-Z0-9-_]/g, ''); const validData = React.useMemo(() => data.map((d, i) => ({ ...d, - color: d.color || DEFAULT_COLORS[i % DEFAULT_COLORS.length] - })), [data]); + color: d.color || DEFAULT_COLORS[i % DEFAULT_COLORS.length], + texture: d.texture || texture, + })), [data, texture]); const { hoveredIndex, @@ -64,29 +72,122 @@ export function PieChart({ viewBox={`0 0 ${size} ${size}`} className="w-full h-full overflow-visible" > - {slices.map((slice, i) => ( - handleMouseMove(e, i)} - onMouseLeave={() => setHoveredIndex(null)} - /> - ))} + + + + + + + + + + + + + + + + + + {slices.map((slice, i) => { + const sliceTexture = slice.texture || texture; + return ( + + handleMouseMove(e, i)} + onMouseLeave={() => setHoveredIndex(null)} + /> + {sliceTexture && sliceTexture !== 'none' && ( + + )} + + ); + })} {showTooltip && hoveredIndex !== null && ( diff --git a/src/pages/image-expander.mdx b/src/pages/image-expander.mdx index 65b5fed6..72707c98 100644 --- a/src/pages/image-expander.mdx +++ b/src/pages/image-expander.mdx @@ -30,7 +30,7 @@ export const ImageExpanderDemo = () => { {img.alt} } > diff --git a/src/pages/pie-chart.mdx b/src/pages/pie-chart.mdx index 8b10706a..8610928b 100644 --- a/src/pages/pie-chart.mdx +++ b/src/pages/pie-chart.mdx @@ -56,6 +56,32 @@ Transform any pie chart into a donut chart by setting the `innerRadius` property /> ``` +### Textured Slices + +Apply SVG patterns across the entire chart with the `texture` prop (`'striped' | 'dots' | 'grid' | 'gradient'`) or mix different textures per slice inside `data`. + + + + + +```tsx + +``` + ## API Reference ### PieChart @@ -93,6 +119,12 @@ Transform any pie chart into a donut chart by setting the `innerRadius` property "defaultValue": "true", "description": "Enable entrance animations." }, + { + "prop": "texture", + "type": "'none' | 'striped' | 'dots' | 'grid' | 'gradient'", + "defaultValue": "'none'", + "description": "SVG pattern texture applied over the slices (can also be overridden per item in data)." + }, { "prop": "className", "type": "string", diff --git a/src/partials/Sidebar.tsx b/src/partials/Sidebar.tsx index 0c826701..ea986236 100644 --- a/src/partials/Sidebar.tsx +++ b/src/partials/Sidebar.tsx @@ -106,22 +106,22 @@ export function Sidebar({ to={link.to} onClick={link.disabled ? (e) => e.preventDefault() : onItemClick} className={clsx( - "relative px-3 py-1.5 rounded-full text-sm transition-colors duration-150 flex items-center gap-2.5 group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", + "relative px-3 py-1.5 rounded-lg text-[13px] transition-colors duration-150 flex items-center gap-2.5 group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", active - ? "bg-ds-900 text-ds-50 dark:bg-ds-100 dark:text-ds-950" - : "text-muted-foreground font-medium hover:text-foreground hover:bg-card dark:hover:bg-ds-800/50", + ? "bg-muted text-foreground font-semibold dark:bg-muted/70" + : "text-muted-foreground font-medium hover:text-foreground hover:bg-muted/50", link.disabled && "opacity-50 cursor-not-allowed hover:bg-transparent" )} >
{link.icon}
- {link.label} + {link.label} ); })}