Compare commits

...

4 Commits

Author SHA1 Message Date
brian fdf0d5865b adding some more cards 2026-06-02 08:34:59 -06:00
brian 8ad6132dfa tweaking minor stuff 2026-06-01 17:37:12 -06:00
brian db2ddadff3 finished up the components, added showcase view, and documentation 2026-06-01 17:32:42 -06:00
brian d34d61458b adding a table component 2026-06-01 17:25:47 -06:00
20 changed files with 1536 additions and 10 deletions
+117 -3
View File
@@ -1,5 +1,119 @@
# Vue Material Kit Design System
# MK Design System
This is a fork/derivative of [Creative Tim's Vue Material Kit 2](https://www.creative-tim.com/product/vue-material-kit), source found [here](https://github.com/creativetimofficial/vue-material-kit).
A Vue 3 + Tailwind CSS v4 design system derived from [Creative Tim's Vue Material Kit 2](https://github.com/creativetimofficial/vue-material-kit). Built to be handed off to another Claude instance for consistent UI work across projects.
I am going to iterate over it with an AI agent and extract styles and functionality into a design system that can be plugged in to another project. The end goal is to have a working directory than can be handed off to another AI agent that is used when building a new site to keep a consistent and clean design.
**30+ components** · **Design tokens in CSS** · **Roboto variable font** · **Material Icons Round**
---
## Using in your project
### Option A — Copy components (simplest)
1. Copy `src/components/`, `src/composables/`, and `src/style.css` into your project.
2. Ensure your project has Tailwind CSS v4 with `@tailwindcss/vite`. Your `style.css` must contain the `@import "tailwindcss"` line and the full `@theme` block from this project's `src/style.css`.
3. Get the font files (4 woff2 files — see **Fonts** below) and place them in `public/fonts/`.
4. Import components directly:
```js
import MkButton from '@/components/MkButton.vue'
import MkInput from '@/components/MkInput.vue'
```
### Option B — Build as a library
```bash
pnpm build:lib
```
This outputs `dist/mk-design-system.es.js`, `dist/mk-design-system.cjs.js`, and `dist/style.css`.
In the consuming project:
```js
// main.js — global registration
import { MkDesignSystem } from './mk-design-system.es.js'
import './mk-design-system.css' // design tokens + @font-face
app.use(MkDesignSystem)
```
Or import components individually:
```js
import { MkButton, MkInput, MkNavbar } from './mk-design-system.es.js'
```
---
## Development
```bash
pnpm install
pnpm dev # dashboard + showcase at localhost:5173
pnpm build # production app build → dist/
pnpm build:lib # library build → dist/mk-design-system.*
```
---
## Fonts
Place these woff2 files in `public/fonts/` before running the dev server:
```
public/fonts/roboto/
Roboto-VariableFont_wdth-wght.woff2
Roboto-Italic-VariableFont_wdth-wght.woff2
public/fonts/roboto-slab/
RobotoSlab-VariableFont_wght.woff2
public/fonts/material-icons-round/
material-icons-round-latin-400-normal.woff2
```
Download script (run from `/app`):
```bash
# Roboto (official Google Fonts variable build)
curl -L "https://github.com/google/fonts/raw/main/apache/roboto/Roboto%5Bwdth%2Cwght%5D.ttf" \
-o public/fonts/roboto/Roboto-VariableFont_wdth-wght.woff2
# Material Icons Round
curl -L "https://cdn.jsdelivr.net/npm/@fontsource/material-icons-round@5/files/material-icons-round-latin-400-normal.woff2" \
-o public/fonts/material-icons-round/material-icons-round-latin-400-normal.woff2
```
---
## Design reference
| File | Purpose |
|------|---------|
| `design-system.md` | Token reference, component patterns, 12 quick-start rules |
| `conformity.md` | 17-rule checklist — feed this + generated code to Claude to score conformity |
| `tokens.json` | Machine-readable design token values |
| `src/style.css` | Live source of truth — `@theme` block defines all tokens |
---
## For Claude
When starting a new project using this design system, provide Claude with:
1. `design-system.md` — the rules to follow
2. `conformity.md` — to score and verify generated code
3. The component list from `src/index.js` — so it knows what's available
Prompt template:
> "Use the MK Design System (design-system.md attached). Available components are listed in src/index.js. Build [feature]. After generating, score it against conformity.md."
---
## Notes
- `MkSocialButton` renders Font Awesome brand icons (`fab fa-*`). Load Font Awesome Free 6 externally if social icons are needed.
- `MkRotatingCard` requires its parent to set an explicit height (e.g. `style="height: 22rem"`).
- `MkNavbar` scroll-blur is automatic in `transparent` mode — no JS required.
+38
View File
@@ -0,0 +1,38 @@
# MK Design System — Conformity Checklist
## How to Use
Provide this file plus the code to review to Claude with this prompt:
"Review the following code against the conformity checklist below. For each rule, output PASS or FAIL with a specific code callout. Then provide a conformity score."
## Rules
### Visual Token Conformity
- [ ] **C-V1**: Colors use semantic Tailwind classes (`bg-primary`, `text-success`, `border-danger`) — no raw hex values in HTML class attributes or `:class` bindings
- [ ] **C-V2**: No hardcoded hex in inline `:style` attributes or `@layer` CSS rules; use `var(--color-*)` or Tailwind classes instead
- [ ] **C-V3**: Spacing uses Tailwind scale classes (`mt-4`, `px-6`, `py-16`) — no inline `style="margin: ..."` or `style="padding: ..."`
- [ ] **C-V4**: Shadows use `shadow-soft-*` or `shadow-{color}` utilities — no custom `box-shadow` values in class attributes
- [ ] **C-V5**: Border radius uses `rounded-*` utilities (`rounded-lg`, `rounded-2xl`, `rounded-full`) — no inline `border-radius` style values
### Component Conformity
- [ ] **C-C1**: All buttons use `MkButton` component or the manual gradient pattern (`bg-gradient-{color} shadow-{color} text-white rounded-lg px-6 py-2.5`); dark-section buttons use `color="white"`; icon-only buttons are square with `p-2`
- [ ] **C-C2**: Cards follow `rounded-2xl bg-white shadow-soft-md` base; card image headers use `-mt-6 mx-4 overflow-hidden rounded-xl` floating pattern; interactive cards use `move-on-hover`
- [ ] **C-C3**: Form inputs use `MkInput` with `v-model`; raw `<input>` elements (if used) must have `rounded-lg border border-gray-200 px-3 py-2 focus:border-primary focus:ring-1 focus:ring-primary`
- [ ] **C-C4**: Navigation uses `MkNavbar` with `navItems` prop; custom navbars must replicate scroll-blur behavior (`bg-white/90 backdrop-blur-sm`) and responsive collapse
- [ ] **C-C5**: Alerts use `MkAlert` component or `rounded-lg px-4 py-3 text-sm bg-{color} text-white` — no plain unstyled `<div role="alert">`
### Layout Conformity
- [ ] **C-L1**: Marketing pages use `max-w-7xl mx-auto px-4` as the page container — not full-bleed or custom `max-width` values
- [ ] **C-L2**: Responsive breakpoints use Tailwind prefixes (`sm:`, `md:`, `lg:`, `xl:`) — no CSS media queries in `<style>` blocks unless unavoidable
- [ ] **C-L3**: Content grids use `grid gap-6` with `grid-cols-{n}` and responsive column modifiers — not `flex flex-wrap` with manual width percentages
- [ ] **C-L4**: Flex layouts use Tailwind utilities (`flex`, `items-center`, `justify-between`, `gap-4`) — no `display: flex` in inline styles
### Naming Conformity
- [ ] **C-N1**: CSS custom properties follow the `@theme` naming convention: `--color-*`, `--shadow-*`, `--radius-*`, `--font-*`, `--background-image-*`
- [ ] **C-N2**: No Bootstrap class names present (`.container`, `.row`, `.col-*`, `.btn`, `.badge`, `.card`, `.navbar`, `.form-control`, etc.)
- [ ] **C-N3**: Vue components use PascalCase with `Mk` prefix (`MkButton`, `MkNavbar`, `MkDefaultCounterCard`); custom utility classes use kebab-case (`move-on-hover`, `text-gradient`)
## Scoring Guide
- 1517 rules pass → **Fully conformant** — ready for production
- 1214 rules pass → **Mostly conformant** — minor cleanup needed
- 811 rules pass → **Partially conformant** — significant drift, review required
- Below 8 → **Non-conformant** — major rework needed before shipping
+212
View File
@@ -0,0 +1,212 @@
# MK Design System
## Overview
- **Framework**: Vue 3.5+
- **Styling**: Tailwind CSS v4 (CSS-first config, no tailwind.config.js)
- **Font**: Roboto variable (self-hosted woff2), Roboto Slab, Material Icons Round
- **Icons**: Material Icons Round via `.material-icons` class
- **Color mode**: Light (CSS custom properties ready for dark mode extension)
- **Token source**: `tokens.json``src/style.css` `@theme` block is the live source of truth
---
## Visual Tokens
### Colors
| Token | CSS var | Hex | Usage |
|-------|---------|-----|-------|
| primary | `--color-primary` | #e91e63 | Buttons, links, key UI (pink/rose) |
| secondary | `--color-secondary` | #7b809a | Secondary actions, muted text |
| success | `--color-success` | #4caf50 | Positive states |
| warning | `--color-warning` | #fb8c00 | Warning states |
| danger | `--color-danger` | #f44335 | Error / destructive |
| info | `--color-info` | #1a73e8 | Informational |
| light | `--color-light` | #f0f2f5 | Page backgrounds |
| dark | `--color-dark` | #344767 | Dark text / navy |
All generate `bg-*`, `text-*`, `border-*`, `ring-*` utilities automatically.
### Gradients
195° linear gradients — this angle is the signature of the kit. **Do not change it.**
| Class | Direction |
|-------|-----------|
| `bg-gradient-primary` | #EC407A#D81B60 |
| `bg-gradient-success` | #66bb6a#43a047 |
| `bg-gradient-info` | #49a3f1#1a73e8 |
| `bg-gradient-dark` | #42424a#191919 |
| …and 4 more (secondary/warning/danger/light) | |
Pair gradient classes with their colored shadow for the floating-card effect:
```html
<div class="bg-gradient-primary shadow-primary rounded-xl p-4 text-white">
```
### Shadows
| Class | Use |
|-------|-----|
| `shadow-soft-xs` | Subtle hover feedback |
| `shadow-soft-sm` | Default card lift |
| `shadow-soft-md` | Standard card |
| `shadow-soft-lg` | Elevated panels |
| `shadow-blur` | Frosted-glass containers |
| `shadow-primary` / `shadow-success` / … | Colored glow — always paired with matching gradient |
### Typography
| Class | Value | Use |
|-------|-------|-----|
| `font-sans` | Roboto variable | Body and UI |
| `font-serif` | Roboto Slab variable | Display/editorial |
| `font-mono` | SFMono / system | Code |
| `font-light` | 300 | Large display headings |
| `font-normal` | 400 | Body copy |
| `font-medium` | 500 | Labels, nav |
| `font-bold` | 700 | Headings, emphasis |
| `font-black` | 900 | Counter cards, hero numbers |
### Spacing
Use Tailwind's default scale. Common values: `p-4` (1rem), `p-6` (1.5rem), `py-16` (4rem) for section padding. No custom spacing tokens.
### Border Radius
| Class | Value | Use |
|-------|-------|-----|
| `rounded-xs` | 0.1rem | Micro elements |
| `rounded-lg` | 0.5rem | Inputs, small cards |
| `rounded-xl` | 0.75rem | Card images, avatars |
| `rounded-2xl` | 1rem | Cards, panels |
| `rounded-full` | 9999px | Badges (pill), avatars (circle) |
---
## Component Patterns
### Button
```html
<!-- Via component (recommended) -->
<MkButton variant="gradient" color="primary">Get Started</MkButton>
<MkButton variant="outline" color="primary" size="sm">Learn More</MkButton>
<!-- Manual (dark sections) -->
<button class="bg-white text-dark rounded-lg px-6 py-2.5 text-sm font-medium">
Contact
</button>
```
**Variants**: `contained` (flat fill) · `gradient` (195° gradient + colored shadow) · `outline`
**Colors**: primary / secondary / success / warning / danger / info / light / white / dark
**Sizes**: sm · md (default) · lg
**Do**: Use `variant="gradient"` for primary CTAs. Use `color="white"` on dark/gradient backgrounds.
**Don't**: Use `bg-primary` manually when `MkButton` is available.
### Card (marketing)
```html
<div class="rounded-2xl bg-white shadow-soft-md">
<!-- Floating image header (overlaps card top) -->
<div class="-mt-6 mx-4 z-10 overflow-hidden rounded-xl shadow-soft-lg">
<img src="..." class="h-48 w-full object-cover" />
</div>
<div class="px-6 pb-6 pt-4">
<h5 class="font-semibold text-dark">Title</h5>
<p class="text-sm text-secondary">Body text.</p>
</div>
</div>
```
### Navbar
```html
<MkNavbar
:brand="{ name: 'My App', route: '/' }"
:nav-items="[
{ label: 'Home', href: '/' },
{ label: 'Pages', icon: 'dashboard', children: [
{ label: 'About', href: '/about', description: 'Our story' },
]},
]"
:action="{ label: 'Get Started', href: '/signup', color: 'primary' }"
:transparent="true"
/>
```
Scroll-triggered frosted glass is automatic in transparent mode.
### Form Inputs
```html
<MkInput label="Full Name" v-model="name" placeholder="e.g. Jane Doe" />
<MkInput label="Email" type="email" v-model="email" :error="true" />
<MkInput label="Search" icon="search" v-model="query" />
<MkTextArea label="Message" v-model="msg" :rows="4" />
<MkCheckbox id="terms" v-model="agreed" color="primary">I agree</MkCheckbox>
<MkSwitch id="notify" v-model="notify" color="primary">Email notifications</MkSwitch>
```
### Hero Section
```html
<MkHeader
image="/img/hero.jpg"
:title="{ text: 'Build Faster', variant: 'h1' }"
description="A design system for Vue 3 and Tailwind v4."
mask="dark"
:mask-opacity="0.55"
:center="true"
min-height="80vh"
>
<MkButton variant="gradient" color="primary" class="mt-6">Get Started</MkButton>
</MkHeader>
```
### Gradient text
```html
<h1 class="text-gradient bg-gradient-primary text-5xl font-black">42,000+</h1>
```
`text-gradient` clips the background image to the text shape. Always pair with a `bg-gradient-*` class.
### Move-on-hover (interactive cards)
```html
<div class="move-on-hover rounded-2xl bg-white shadow-soft-md p-6">
<!-- Lifts 4px on hover with ease transition -->
</div>
```
---
## Layout & Grid
| Element | Classes |
|---------|---------|
| Page container | `max-w-7xl mx-auto px-4` |
| Section spacing | `py-16` (4rem) or `py-24` (6rem) for hero |
| Grid (3-col) | `grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3` |
| Grid (4-col) | `grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-4` |
| Flex row | `flex items-center gap-4` |
| Flex wrap | `flex flex-wrap gap-3` |
Breakpoints (Tailwind v4 defaults): `sm` 640px · `md` 768px · `lg` 1024px · `xl` 1280px.
---
## Naming Conventions
- **CSS custom properties**: `--color-*`, `--shadow-*`, `--radius-*`, `--font-*`, `--background-image-*`
- **Vue components**: PascalCase with `Mk` prefix — `MkButton`, `MkNavbar`, `MkDefaultCounterCard`
- **Custom CSS classes**: kebab-case — `move-on-hover`, `text-gradient`, `rotating-card-container`
- **No Bootstrap class names** anywhere in the codebase
---
## Quick-Start Rules for Code Generation
When generating code using this design system:
1. Use Tailwind v4 utility classes only — no Bootstrap, no custom SCSS
2. Reference colors via semantic class names (`bg-primary`, `text-success`) — never raw hex in HTML
3. Spacing: use Tailwind scale (`mt-4`, `px-6`, `py-16`) — no inline `style` for spacing
4. Primary CTAs: `MkButton variant="gradient" color="primary"` or `bg-gradient-primary shadow-primary text-white rounded-lg px-6 py-2.5`
5. Dark-section buttons: `color="white"` (white background, dark text) — not `color="light"`
6. Cards: `rounded-2xl bg-white shadow-soft-md` as the base; floating headers use `-mt-6 mx-4 rounded-xl overflow-hidden`
7. Page layout: `max-w-7xl mx-auto px-4` container with `grid gap-6` inside
8. Form fields: always use `MkInput`, `MkTextArea`, `MkCheckbox`, `MkSwitch` — not raw `<input>`
9. Images inside cards: `rounded-xl object-cover` for consistent soft appearance
10. Interactive cards: add `move-on-hover` class for hover lift effect
11. Use `Mk*` components throughout — consult `src/components/` for available primitives
12. Navbar scroll blur is automatic; pass `navItems` as data, not hardcoded HTML
+1
View File
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"build:lib": "BUILD_TARGET=lib vite build",
"preview": "vite preview"
},
"dependencies": {
+2
View File
@@ -68,6 +68,8 @@ const classes = computed(() => [
.alert-fade-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease, max-height 0.25s ease;
overflow: hidden;
/* Must be a numeric value — CSS cannot transition from 'auto' to 0 */
max-height: 200px;
}
.alert-fade-leave-to {
opacity: 0;
+139
View File
@@ -0,0 +1,139 @@
<script setup>
defineProps({
headers: {
type: Array,
default: () => ['Author', 'Function', 'Status', 'Employed', 'Action'],
},
/**
* Each row: {
* image?: string — avatar URL (falls back to initials)
* initials?: string — shown when no image
* color?: string — gradient color for initials avatar
* name: string
* email: string
* position: [string, string] — [label, sublabel]
* status: boolean — true = Online, false = Offline
* date: string
* action: { label, route }
* }
*/
rows: {
type: Array,
required: true,
},
})
const avatarGradients = {
primary: 'bg-gradient-primary shadow-primary',
secondary: 'bg-gradient-secondary shadow-secondary',
success: 'bg-gradient-success shadow-success',
warning: 'bg-gradient-warning shadow-warning',
danger: 'bg-gradient-danger shadow-danger',
info: 'bg-gradient-info shadow-info',
dark: 'bg-gradient-dark shadow-dark',
}
</script>
<template>
<div class="overflow-hidden rounded-2xl bg-white shadow-soft-md">
<!-- Optional slot for a card header (title, actions, etc.) -->
<div v-if="$slots.header" class="border-b border-gray-100 px-6 py-4">
<slot name="header" />
</div>
<div class="overflow-x-auto">
<table class="w-full text-left text-sm">
<thead>
<tr class="border-b border-gray-100">
<th
v-for="(header, i) in headers"
:key="header"
class="px-6 py-3 text-xs font-bold uppercase tracking-wide text-secondary"
:class="i > 1 ? 'text-center' : ''"
>
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(row, i) in rows"
:key="i"
:class="i < rows.length - 1 ? 'border-b border-gray-100' : ''"
>
<!-- Author: avatar + name + email -->
<td class="px-6 py-3">
<div class="flex items-center gap-3">
<!-- Photo avatar -->
<img
v-if="row.image"
:src="row.image"
:alt="row.name"
class="h-9 w-9 shrink-0 rounded-xl object-cover"
/>
<!-- Initials avatar -->
<div
v-else
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl text-xs font-bold text-white"
:class="avatarGradients[row.color] ?? avatarGradients.dark"
>
{{ row.initials ?? row.name?.slice(0, 2).toUpperCase() }}
</div>
<div>
<p class="font-medium text-dark">{{ row.name }}</p>
<p class="text-xs text-secondary">{{ row.email }}</p>
</div>
</div>
</td>
<!-- Position: two-line label -->
<td class="px-6 py-3">
<p class="font-medium text-dark">{{ row.position?.[0] ?? '—' }}</p>
<p class="text-xs text-secondary">{{ row.position?.[1] ?? '' }}</p>
</td>
<!-- Status badge -->
<td class="px-6 py-3 text-center">
<span
class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium"
:class="row.status
? 'bg-success/10 text-success'
: 'bg-gray-100 text-secondary'"
>
<span
class="h-1.5 w-1.5 rounded-full"
:class="row.status ? 'bg-success' : 'bg-gray-400'"
/>
{{ row.status ? 'Online' : 'Offline' }}
</span>
</td>
<!-- Date -->
<td class="px-6 py-3 text-center text-xs text-secondary">
{{ row.date }}
</td>
<!-- Action -->
<td class="px-6 py-3 text-center">
<a
:href="row.action?.route ?? '#'"
class="text-xs font-medium text-secondary no-underline transition-colors hover:text-primary"
>
{{ row.action?.label ?? 'Edit' }}
</a>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Optional slot for pagination or footer -->
<div v-if="$slots.footer" class="border-t border-gray-100 px-6 py-4">
<slot name="footer" />
</div>
</div>
</template>
+47
View File
@@ -0,0 +1,47 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({
title: { type: String, default: '' },
// Accepts HTML — e.g. "<span class='font-bold'>+15%</span> increase today"
subtitle: { type: String, default: '' },
// Timestamp label shown in footer — e.g. "updated 4 min ago"
update: { type: String, default: '' },
color: { type: String, default: 'primary' },
})
const gradientMap = {
primary: 'bg-gradient-primary shadow-primary',
secondary: 'bg-gradient-secondary shadow-secondary',
success: 'bg-gradient-success shadow-success',
warning: 'bg-gradient-warning shadow-warning',
danger: 'bg-gradient-danger shadow-danger',
info: 'bg-gradient-info shadow-info',
dark: 'bg-gradient-dark shadow-dark',
light: 'bg-gradient-light shadow-soft-sm',
}
const headerClass = computed(() => gradientMap[props.color] ?? gradientMap.primary)
</script>
<template>
<div class="rounded-2xl bg-white shadow-soft-md overflow-visible">
<!-- Floating chart header slot content sits inside the gradient area -->
<div class="relative -mt-6 mx-4 z-10 overflow-hidden rounded-xl px-3 py-3" :class="headerClass">
<slot />
</div>
<!-- Body -->
<div class="px-4 pt-3 pb-4">
<h6 class="mb-0 font-bold text-dark">{{ title }}</h6>
<p class="text-sm text-secondary mt-0.5" v-html="subtitle" />
<div class="my-3 border-t border-gray-100" />
<div class="flex items-center gap-1 text-sm text-secondary">
<span class="material-icons text-base leading-none">schedule</span>
{{ update }}
</div>
</div>
</div>
</template>
@@ -1,6 +1,9 @@
<script setup>
import { computed } from 'vue'
// v-bind="$attrs" in the template passes attrs to the info wrapper, not the root
defineOptions({ inheritAttrs: false })
const props = defineProps({
// String icon name, or { component, color, size }
icon: { type: [String, Object], default: '' },
@@ -0,0 +1,61 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({
// { text: 'Label', value: '$53k' }
title: { type: Object, required: true },
// HTML string — e.g. "<span class='text-success'>+55%</span> than last week"
detail: { type: String, default: '' },
// { name: 'weekend', color: 'white'|color-name, background: color-name }
icon: {
type: Object,
default: () => ({ name: 'bar_chart', color: 'white', background: 'dark' }),
},
// Swap icon to the right, value to the left
directionReverse: { type: Boolean, default: false },
})
const gradientMap = {
primary: 'bg-gradient-primary shadow-primary',
secondary: 'bg-gradient-secondary shadow-secondary',
success: 'bg-gradient-success shadow-success',
warning: 'bg-gradient-warning shadow-warning',
danger: 'bg-gradient-danger shadow-danger',
info: 'bg-gradient-info shadow-info',
dark: 'bg-gradient-dark shadow-dark',
light: 'bg-gradient-light shadow-soft-sm',
}
const iconClass = computed(() => gradientMap[props.icon.background] ?? gradientMap.dark)
const iconTextClass = computed(() =>
props.icon.color === 'white' ? 'text-white' : `text-${props.icon.color}`
)
</script>
<template>
<div class="rounded-2xl bg-white shadow-soft-md">
<!-- Header: floating icon + metric -->
<div class="relative px-4 pt-2 pb-3">
<div
class="absolute -top-5 flex h-14 w-14 items-center justify-center rounded-xl transition-all"
:class="[iconClass, directionReverse ? 'right-4' : 'left-4']"
>
<span class="material-icons text-2xl opacity-90" :class="iconTextClass">
{{ icon.name }}
</span>
</div>
<div class="pt-1" :class="directionReverse ? 'text-left pl-16' : 'text-right'">
<p class="mb-0 text-sm text-secondary capitalize">{{ title.text }}</p>
<h4 class="mb-0 text-xl font-bold text-dark">{{ title.value }}</h4>
</div>
</div>
<div class="mx-4 border-t border-gray-100" />
<!-- Footer: detail text -->
<div class="px-4 py-3">
<p class="mb-0 text-sm text-secondary" v-html="detail" />
</div>
</div>
</template>
+64
View File
@@ -0,0 +1,64 @@
<script setup>
import { inject, computed } from 'vue'
const props = defineProps({
// Material icon name, e.g. 'notifications'
icon: { type: String, default: 'circle' },
// Design-system color name for the icon dot
color: { type: String, default: 'success' },
title: { type: String, required: true },
dateTime: { type: String, default: '' },
description: { type: String, default: '' },
// Pass true on the last item to hide the connector line
last: { type: Boolean, default: false },
})
const dark = inject('mk-timeline-dark', false)
// Explicit map — avoids dynamic `bg-${color}/10` which Tailwind can't scan
const dotMap = {
primary: 'bg-primary/10 text-primary',
secondary: 'bg-secondary/10 text-secondary',
success: 'bg-success/10 text-success',
warning: 'bg-warning/10 text-warning',
danger: 'bg-danger/10 text-danger',
info: 'bg-info/10 text-info',
dark: 'bg-dark/10 text-dark',
}
const dotClass = computed(() => dotMap[props.color] ?? dotMap.success)
</script>
<template>
<div class="flex gap-4" :class="last ? '' : 'mb-1'">
<!-- Icon dot + vertical connector -->
<div class="flex flex-col items-center">
<div
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"
:class="dotClass"
>
<span class="material-icons text-base leading-none">{{ icon }}</span>
</div>
<!-- Connector line hidden on last item -->
<div v-if="!last" class="mt-1 w-px flex-1 bg-gray-200" />
</div>
<!-- Content -->
<div class="pb-4" :class="last ? 'pb-1' : ''">
<h6
class="mb-0 text-sm font-bold leading-snug"
:class="dark ? 'text-white' : 'text-dark'"
>
{{ title }}
</h6>
<p class="mt-0.5 mb-0 text-xs text-secondary">{{ dateTime }}</p>
<p
v-if="description"
class="mt-2 mb-0 text-sm"
:class="dark ? 'text-white/80' : 'text-secondary'"
>
{{ description }}
</p>
</div>
</div>
</template>
+30
View File
@@ -0,0 +1,30 @@
<script setup>
import { provide } from 'vue'
const props = defineProps({
title: { type: String, default: '' },
description: { type: String, default: '' },
dark: { type: Boolean, default: false },
})
// MkTimelineItem reads this to adapt its text colors
provide('mk-timeline-dark', props.dark)
</script>
<template>
<div
class="rounded-2xl shadow-soft-md h-full"
:class="dark ? 'bg-gradient-dark' : 'bg-white'"
>
<!-- Header -->
<div class="px-4 pt-4 pb-0">
<h6 class="font-bold" :class="dark ? 'text-white' : 'text-dark'">{{ title }}</h6>
<p class="text-sm text-secondary" v-html="description" />
</div>
<!-- Timeline items -->
<div class="px-4 pt-2 pb-4">
<slot />
</div>
</div>
</template>
+94
View File
@@ -0,0 +1,94 @@
// ── Core UI ───────────────────────────────────────────────────
export { default as MkAlert } from './components/MkAlert.vue'
export { default as MkAvatar } from './components/MkAvatar.vue'
export { default as MkBadge } from './components/MkBadge.vue'
export { default as MkButton } from './components/MkButton.vue'
export { default as MkCheckbox } from './components/MkCheckbox.vue'
export { default as MkInput } from './components/MkInput.vue'
export { default as MkPagination } from './components/MkPagination.vue'
export { default as MkPaginationItem } from './components/MkPaginationItem.vue'
export { default as MkProgress } from './components/MkProgress.vue'
export { default as MkSocialButton } from './components/MkSocialButton.vue'
export { default as MkSwitch } from './components/MkSwitch.vue'
export { default as MkTable } from './components/MkTable.vue'
export { default as MkTextArea } from './components/MkTextArea.vue'
// ── Cards ─────────────────────────────────────────────────────
export { default as MkBackgroundBlogCard } from './components/cards/MkBackgroundBlogCard.vue'
export { default as MkCenteredBlogCard } from './components/cards/MkCenteredBlogCard.vue'
export { default as MkDefaultCounterCard } from './components/cards/MkDefaultCounterCard.vue'
export { default as MkDefaultInfoCard } from './components/cards/MkDefaultInfoCard.vue'
export { default as MkDefaultReviewCard } from './components/cards/MkDefaultReviewCard.vue'
export { default as MkFilledInfoCard } from './components/cards/MkFilledInfoCard.vue'
export { default as MkHorizontalTeamCard } from './components/cards/MkHorizontalTeamCard.vue'
export { default as MkRotatingCard } from './components/cards/MkRotatingCard.vue'
export { default as MkRotatingCardBack } from './components/cards/MkRotatingCardBack.vue'
export { default as MkRotatingCardFront } from './components/cards/MkRotatingCardFront.vue'
export { default as MkTransparentBlogCard } from './components/cards/MkTransparentBlogCard.vue'
export { default as MkMiniStatisticsCard } from './components/cards/MkMiniStatisticsCard.vue'
export { default as MkChartCard } from './components/cards/MkChartCard.vue'
export { default as MkTimelineList } from './components/cards/MkTimelineList.vue'
export { default as MkTimelineItem } from './components/cards/MkTimelineItem.vue'
// ── Layout ────────────────────────────────────────────────────
export { default as MkBreadcrumbs } from './components/layout/MkBreadcrumbs.vue'
export { default as MkFooter } from './components/layout/MkFooter.vue'
export { default as MkFooterCentered } from './components/layout/MkFooterCentered.vue'
export { default as MkHeader } from './components/layout/MkHeader.vue'
export { default as MkNavbar } from './components/layout/MkNavbar.vue'
// ── Composables ───────────────────────────────────────────────
export { useCountUp } from './composables/useCountUp.js'
// ── Vue plugin (global registration) ─────────────────────────
import MkAlert from './components/MkAlert.vue'
import MkAvatar from './components/MkAvatar.vue'
import MkBadge from './components/MkBadge.vue'
import MkButton from './components/MkButton.vue'
import MkCheckbox from './components/MkCheckbox.vue'
import MkInput from './components/MkInput.vue'
import MkPagination from './components/MkPagination.vue'
import MkPaginationItem from './components/MkPaginationItem.vue'
import MkProgress from './components/MkProgress.vue'
import MkSocialButton from './components/MkSocialButton.vue'
import MkSwitch from './components/MkSwitch.vue'
import MkTable from './components/MkTable.vue'
import MkTextArea from './components/MkTextArea.vue'
import MkBackgroundBlogCard from './components/cards/MkBackgroundBlogCard.vue'
import MkCenteredBlogCard from './components/cards/MkCenteredBlogCard.vue'
import MkDefaultCounterCard from './components/cards/MkDefaultCounterCard.vue'
import MkDefaultInfoCard from './components/cards/MkDefaultInfoCard.vue'
import MkDefaultReviewCard from './components/cards/MkDefaultReviewCard.vue'
import MkFilledInfoCard from './components/cards/MkFilledInfoCard.vue'
import MkHorizontalTeamCard from './components/cards/MkHorizontalTeamCard.vue'
import MkRotatingCard from './components/cards/MkRotatingCard.vue'
import MkRotatingCardBack from './components/cards/MkRotatingCardBack.vue'
import MkRotatingCardFront from './components/cards/MkRotatingCardFront.vue'
import MkTransparentBlogCard from './components/cards/MkTransparentBlogCard.vue'
import MkMiniStatisticsCard from './components/cards/MkMiniStatisticsCard.vue'
import MkChartCard from './components/cards/MkChartCard.vue'
import MkTimelineList from './components/cards/MkTimelineList.vue'
import MkTimelineItem from './components/cards/MkTimelineItem.vue'
import MkBreadcrumbs from './components/layout/MkBreadcrumbs.vue'
import MkFooter from './components/layout/MkFooter.vue'
import MkFooterCentered from './components/layout/MkFooterCentered.vue'
import MkHeader from './components/layout/MkHeader.vue'
import MkNavbar from './components/layout/MkNavbar.vue'
const components = [
MkAlert, MkAvatar, MkBadge, MkButton, MkCheckbox, MkInput,
MkPagination, MkPaginationItem, MkProgress, MkSocialButton,
MkSwitch, MkTable, MkTextArea,
MkBackgroundBlogCard, MkCenteredBlogCard, MkDefaultCounterCard,
MkDefaultInfoCard, MkDefaultReviewCard, MkFilledInfoCard,
MkHorizontalTeamCard, MkRotatingCard, MkRotatingCardBack,
MkRotatingCardFront, MkTransparentBlogCard,
MkMiniStatisticsCard, MkChartCard, MkTimelineList, MkTimelineItem,
MkBreadcrumbs, MkFooter, MkFooterCentered, MkHeader, MkNavbar,
]
export const MkDesignSystem = {
install(app) {
components.forEach(c => app.component(c.__name, c))
},
}
+1
View File
@@ -12,6 +12,7 @@ const navItems = [
{ label: 'Icons', path: '/icons', icon: 'auto_awesome' },
{ label: 'Maps', path: '/maps', icon: 'map' },
{ label: 'Notifications', path: '/notifications',icon: 'notifications' },
{ label: 'Showcase', path: '/showcase', icon: 'widgets' },
]
function isActive(path) {
+1
View File
@@ -11,5 +11,6 @@ export default createRouter({
{ path: '/maps', component: () => import('../views/MapsView.vue'), meta: { title: 'Maps' } },
{ path: '/notifications',component: () => import('../views/NotificationsView.vue'), meta: { title: 'Notifications' } },
{ path: '/profile', component: () => import('../views/ProfileView.vue'), meta: { title: 'Profile' } },
{ path: '/showcase', component: () => import('../views/ShowcaseView.vue'), meta: { title: 'Component Showcase' } },
],
})
-3
View File
@@ -23,9 +23,6 @@ const statusClass = {
cancelled: 'bg-danger/10 text-danger',
}
const gradientProgress = (value, gradient) => ({
width: `${value}%`,
})
</script>
<template>
+1 -1
View File
@@ -346,7 +346,7 @@ const memberColor = { PC: 'primary', JD: 'info', SR: 'success', ML: 'warning', A
</div>
<div class="flex justify-end">
<button class="rounded-lg bg-gradient-primary px-6 py-2.5 text-sm font-medium text-white shadow-primary transition-shadow hover:shadow-soft-md">
<button class="rounded-lg bg-gradient-primary px-6 py-2.5 text-sm font-medium text-white shadow-primary hover:-translate-y-0.5 active:translate-y-0 transition-all">
Save Changes
</button>
</div>
+579
View File
@@ -0,0 +1,579 @@
<script setup>
import { ref } from 'vue'
import DashboardLayout from '../layouts/DashboardLayout.vue'
// Core
import MkButton from '../components/MkButton.vue'
import MkBadge from '../components/MkBadge.vue'
import MkAlert from '../components/MkAlert.vue'
import MkAvatar from '../components/MkAvatar.vue'
import MkProgress from '../components/MkProgress.vue'
import MkCheckbox from '../components/MkCheckbox.vue'
import MkSwitch from '../components/MkSwitch.vue'
import MkInput from '../components/MkInput.vue'
import MkTextArea from '../components/MkTextArea.vue'
import MkPagination from '../components/MkPagination.vue'
import MkPaginationItem from '../components/MkPaginationItem.vue'
import MkTable from '../components/MkTable.vue'
// Cards
import MkBackgroundBlogCard from '../components/cards/MkBackgroundBlogCard.vue'
import MkCenteredBlogCard from '../components/cards/MkCenteredBlogCard.vue'
import MkTransparentBlogCard from '../components/cards/MkTransparentBlogCard.vue'
import MkDefaultInfoCard from '../components/cards/MkDefaultInfoCard.vue'
import MkFilledInfoCard from '../components/cards/MkFilledInfoCard.vue'
import MkDefaultCounterCard from '../components/cards/MkDefaultCounterCard.vue'
import MkDefaultReviewCard from '../components/cards/MkDefaultReviewCard.vue'
import MkHorizontalTeamCard from '../components/cards/MkHorizontalTeamCard.vue'
import MkRotatingCard from '../components/cards/MkRotatingCard.vue'
import MkRotatingCardFront from '../components/cards/MkRotatingCardFront.vue'
import MkRotatingCardBack from '../components/cards/MkRotatingCardBack.vue'
import MkMiniStatisticsCard from '../components/cards/MkMiniStatisticsCard.vue'
import MkChartCard from '../components/cards/MkChartCard.vue'
import MkTimelineList from '../components/cards/MkTimelineList.vue'
import MkTimelineItem from '../components/cards/MkTimelineItem.vue'
// Layout
import MkBreadcrumbs from '../components/layout/MkBreadcrumbs.vue'
import MkNavbar from '../components/layout/MkNavbar.vue'
import MkHeader from '../components/layout/MkHeader.vue'
import MkFooterCentered from '../components/layout/MkFooterCentered.vue'
// ── Reactive state ────────────────────────────────────────────
const checkbox1 = ref(true)
const checkbox2 = ref(false)
const switch1 = ref(true)
const switch2 = ref(false)
const inputVal = ref('')
const textareaVal = ref('')
const activePage = ref(3)
// ── Data ──────────────────────────────────────────────────────
const colors = ['primary','secondary','success','warning','danger','info','dark']
const img = (seed, w = 600, h = 400) => `https://picsum.photos/seed/${seed}/${w}/${h}`
const tableRows = [
{ initials:'EC', color:'primary', name:'Esthera Carter', email:'e.carter@mk.dev', position:['Lead Designer','Creative'], status:true, date:'23/04/18', action:{label:'Edit', route:'#'} },
{ initials:'JD', color:'info', name:'James Donovan', email:'j.donovan@mk.dev',position:['Developer','Engineering'], status:true, date:'11/01/19', action:{label:'Edit', route:'#'} },
{ initials:'SR', color:'success', name:'Sofia Reyes', email:'s.reyes@mk.dev', position:['Product Manager','Operations'], status:false, date:'19/09/20', action:{label:'Edit', route:'#'} },
{ initials:'ML', color:'warning', name:'Marco Lin', email:'m.lin@mk.dev', position:['QA Engineer','Quality'], status:true, date:'24/12/21', action:{label:'Edit', route:'#'} },
{ initials:'AM', color:'secondary', name:'Ally Maria', email:'a.maria@mk.dev', position:['UX Researcher','Design'], status:false, date:'04/10/22', action:{label:'Edit', route:'#'} },
]
const navItems = [
{ label:'Home', href:'#', icon:'home' },
{ label:'Components', icon:'widgets', children:[
{ label:'Buttons', href:'#', description:'Variants, sizes and states' },
{ label:'Cards', href:'#', description:'Blog, info, counter, review' },
{ label:'Forms', href:'#', description:'Inputs, selects, checkboxes' },
]},
{ label:'Docs', href:'#', icon:'article' },
]
const breadcrumbRoutes = [
{ route:'/', label:'Dashboard' },
{ route:'/showcase', label:'Components' },
{ route:'#', label:'Showcase' },
]
</script>
<template>
<DashboardLayout>
<div class="space-y-12">
<!-- SECTION HELPER -->
<!-- Each section follows: title / subtitle / content card -->
<!-- Buttons -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Buttons</h2>
<p class="text-sm text-secondary mb-4">variant × color × size</p>
<div class="rounded-2xl bg-white p-6 shadow-soft-md space-y-6">
<!-- Gradient -->
<div>
<p class="mb-3 text-xs font-medium uppercase tracking-wide text-secondary">Gradient</p>
<div class="flex flex-wrap gap-3">
<MkButton v-for="c in colors" :key="c" variant="gradient" :color="c">{{ c }}</MkButton>
</div>
</div>
<!-- Contained -->
<div>
<p class="mb-3 text-xs font-medium uppercase tracking-wide text-secondary">Contained</p>
<div class="flex flex-wrap gap-3">
<MkButton v-for="c in colors" :key="c" variant="contained" :color="c">{{ c }}</MkButton>
</div>
</div>
<!-- Outline -->
<div>
<p class="mb-3 text-xs font-medium uppercase tracking-wide text-secondary">Outline</p>
<div class="flex flex-wrap gap-3">
<MkButton v-for="c in colors" :key="c" variant="outline" :color="c">{{ c }}</MkButton>
</div>
</div>
<!-- Sizes + states -->
<div>
<p class="mb-3 text-xs font-medium uppercase tracking-wide text-secondary">Sizes & states</p>
<div class="flex flex-wrap items-center gap-3">
<MkButton variant="gradient" color="primary" size="sm">Small</MkButton>
<MkButton variant="gradient" color="primary" size="md">Medium</MkButton>
<MkButton variant="gradient" color="primary" size="lg">Large</MkButton>
<MkButton variant="gradient" color="primary" :disabled="true">Disabled</MkButton>
<MkButton variant="outline" color="primary" :full-width="true" class="max-w-xs">Full width</MkButton>
</div>
</div>
</div>
</section>
<!-- Badges -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Badges</h2>
<p class="text-sm text-secondary mb-4">fill · gradient · sizes · rounded</p>
<div class="rounded-2xl bg-white p-6 shadow-soft-md space-y-4">
<div class="flex flex-wrap gap-2">
<MkBadge v-for="c in colors" :key="c" variant="fill" :color="c">{{ c }}</MkBadge>
</div>
<div class="flex flex-wrap gap-2">
<MkBadge v-for="c in colors" :key="c" variant="gradient" :color="c">{{ c }}</MkBadge>
</div>
<div class="flex flex-wrap items-center gap-2">
<MkBadge color="primary" size="sm" :rounded="true">sm pill</MkBadge>
<MkBadge color="primary" size="md" :rounded="true">md pill</MkBadge>
<MkBadge color="primary" size="lg" :rounded="true">lg pill</MkBadge>
</div>
</div>
</section>
<!-- Alerts -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Alerts</h2>
<p class="text-sm text-secondary mb-4">all colors · dismissible</p>
<div class="rounded-2xl bg-white p-6 shadow-soft-md space-y-3">
<MkAlert color="success">Success action completed.</MkAlert>
<MkAlert color="info">Info something you should know.</MkAlert>
<MkAlert color="warning">Warning check before proceeding.</MkAlert>
<MkAlert color="danger" :dismissible="true">Danger (dismissible) click × to close.</MkAlert>
<MkAlert color="dark">Dark a neutral message.</MkAlert>
</div>
</section>
<!-- Avatars -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Avatars</h2>
<p class="text-sm text-secondary mb-4">xxs xxl · borderRadius variants</p>
<div class="rounded-2xl bg-white p-6 shadow-soft-md">
<div class="flex flex-wrap items-end gap-4">
<div v-for="s in ['xxs','xs','sm','md','lg','xl','xxl']" :key="s" class="flex flex-col items-center gap-2">
<MkAvatar :image="img('av1',200,200)" alt="Avatar" :size="s" />
<span class="text-xs text-secondary">{{ s }}</span>
</div>
<div class="flex flex-col items-center gap-2">
<MkAvatar :image="img('av2',200,200)" alt="Circle" size="lg" border-radius="full" />
<span class="text-xs text-secondary">circle</span>
</div>
</div>
</div>
</section>
<!-- Progress -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Progress</h2>
<p class="text-sm text-secondary mb-4">contained · gradient</p>
<div class="rounded-2xl bg-white p-6 shadow-soft-md space-y-3">
<div v-for="c in colors" :key="c" class="flex items-center gap-4">
<span class="w-20 shrink-0 text-xs text-secondary">{{ c }}</span>
<div class="flex-1"><MkProgress variant="gradient" :color="c" :value="Math.round(30 + colors.indexOf(c) * 10)" /></div>
</div>
</div>
</section>
<!-- Form Controls -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Form Controls</h2>
<p class="text-sm text-secondary mb-4">input · textarea · checkbox · switch</p>
<div class="grid gap-6 lg:grid-cols-2">
<!-- Inputs -->
<div class="rounded-2xl bg-white p-6 shadow-soft-md space-y-4">
<MkInput label="Default input" v-model="inputVal" placeholder="Type something…" />
<MkInput label="With icon" icon="search" v-model="inputVal" placeholder="Search…" />
<MkInput label="Error state" v-model="inputVal" :error="true" placeholder="Required field" />
<MkInput label="Success state" v-model="inputVal" :success="true" placeholder="Looks good" />
<MkInput label="Disabled" v-model="inputVal" :is-disabled="true" placeholder="Not editable" />
<MkTextArea v-model="textareaVal" placeholder="Your message…" :rows="3">Message</MkTextArea>
</div>
<!-- Toggles -->
<div class="rounded-2xl bg-white p-6 shadow-soft-md">
<p class="mb-4 text-xs font-medium uppercase tracking-wide text-secondary">Checkboxes</p>
<div class="space-y-3 mb-6">
<MkCheckbox id="c1" v-model="checkbox1" color="primary">Primary (checked)</MkCheckbox>
<MkCheckbox id="c2" v-model="checkbox2" color="success">Success (unchecked)</MkCheckbox>
<MkCheckbox id="c3" v-model="checkbox1" color="danger">Danger</MkCheckbox>
</div>
<p class="mb-4 text-xs font-medium uppercase tracking-wide text-secondary">Switches</p>
<div class="space-y-3">
<MkSwitch id="s1" v-model="switch1" color="primary">Primary (on)</MkSwitch>
<MkSwitch id="s2" v-model="switch2" color="success">Success (off)</MkSwitch>
<MkSwitch id="s3" v-model="switch1" color="info">Info</MkSwitch>
</div>
</div>
</div>
</section>
<!-- Pagination -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Pagination</h2>
<p class="text-sm text-secondary mb-4">color variants · active state</p>
<div class="rounded-2xl bg-white p-6 shadow-soft-md space-y-4">
<div v-for="c in ['primary','success','info','danger']" :key="c">
<MkPagination :color="c">
<MkPaginationItem :prev="true" @click="activePage > 1 && activePage--" />
<MkPaginationItem v-for="n in 5" :key="n" :label="String(n)" :active="activePage === n" @click="activePage = n" />
<MkPaginationItem :next="true" @click="activePage < 5 && activePage++" />
</MkPagination>
</div>
</div>
</section>
<!-- Blog Cards -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Blog Cards</h2>
<p class="text-sm text-secondary mb-4">background · centered · transparent</p>
<div class="grid gap-6 md:grid-cols-3">
<MkBackgroundBlogCard
:image="img('bg1')"
title="Background Blog Card"
description="Full-bleed image with gradient overlay. Content sits above the image."
:action="{ route:'#', label:'Read more', color:'white' }"
/>
<MkCenteredBlogCard
:image="img('cb1')"
title="Centered Blog Card"
description="Floating image header overlaps the card top edge for a layered effect."
:action="{ color:'success', label:'Find Out More', route:'#' }"
/>
<MkTransparentBlogCard
:image="img('tb1')"
title="Transparent Blog Card"
description="No card background — just the image with a soft shadow and plain text below."
:action="{ route:'#', color:'primary', label:'Read more' }"
/>
</div>
</section>
<!-- Info Cards -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Info Cards</h2>
<p class="text-sm text-secondary mb-4">default · filled</p>
<div class="grid gap-6 md:grid-cols-2">
<div class="rounded-2xl bg-white p-6 shadow-soft-md">
<div class="grid gap-6 sm:grid-cols-2">
<MkDefaultInfoCard
v-for="item in [
{icon:{component:'bolt',color:'primary',size:'3xl'},title:'Fast Build',description:'Zero-config Vite setup with Tailwind v4.'},
{icon:{component:'palette',color:'success',size:'3xl'},title:'Design Tokens',description:'All values live in @theme CSS custom properties.'},
{icon:{component:'widgets',color:'info',size:'3xl'},title:'30+ Components',description:'Buttons, cards, forms, navbars and more.'},
{icon:{component:'devices',color:'warning',size:'3xl'},title:'Responsive',description:'Mobile-first layouts using Tailwind breakpoints.'},
]"
:key="item.title"
:icon="item.icon"
:title="item.title"
:description="item.description"
/>
</div>
</div>
<div class="space-y-4">
<MkFilledInfoCard
:color="{ background:'primary', text:'white' }"
:icon="{ component:'rocket_launch', color:'white' }"
title="Ship faster"
description="Pre-built components mean less time on UI and more time on features."
:action="{ route:'#', label:{ text:'Learn More', color:'white' } }"
/>
<MkFilledInfoCard
:color="{ background:'', text:'' }"
:icon="{ component:'verified', color:'success' }"
title="Design conformity"
description="The conformity checklist helps keep every PR on-brand."
:action="{ route:'#', label:{ text:'View Checklist', color:'success' } }"
/>
</div>
</div>
</section>
<!-- ⑩ Dashboard Cards ───────────────────────────────────── -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Dashboard Cards</h2>
<p class="text-sm text-secondary mb-4">mini statistics · chart holder · timeline</p>
<!-- Mini statistics row (matches Material Dashboard layout) -->
<div class="grid gap-6 sm:grid-cols-2 xl:grid-cols-4 mb-8 mt-8">
<MkMiniStatisticsCard
:title="{ text: 'Today\'s Money', value: '$53k' }"
detail="<span class='text-success font-medium'>+55%</span> than last week"
:icon="{ name: 'weekend', color: 'white', background: 'primary' }"
/>
<MkMiniStatisticsCard
:title="{ text: 'Today\'s Users', value: '2,300' }"
detail="<span class='text-success font-medium'>+3%</span> than last month"
:icon="{ name: 'leaderboard', color: 'white', background: 'info' }"
/>
<MkMiniStatisticsCard
:title="{ text: 'New Clients', value: '3,462' }"
detail="<span class='text-danger font-medium'>-2%</span> than yesterday"
:icon="{ name: 'person', color: 'white', background: 'success' }"
/>
<MkMiniStatisticsCard
:title="{ text: 'Sales', value: '$103k' }"
detail="<span class='text-success font-medium'>+5%</span> just updated"
:icon="{ name: 'store', color: 'white', background: 'warning' }"
/>
</div>
<!-- Chart cards row -->
<div class="grid gap-6 md:grid-cols-3 mb-8 mt-12">
<!-- Bar chart placeholder -->
<MkChartCard
title="Website Views"
subtitle="Last campaign performance"
update="campaign sent 2 days ago"
color="primary"
>
<svg viewBox="0 0 140 60" class="w-full h-20" preserveAspectRatio="none">
<rect x="8" y="30" width="12" height="30" rx="2" fill="white" opacity="0.7"/>
<rect x="28" y="42" width="12" height="18" rx="2" fill="white" opacity="0.7"/>
<rect x="48" y="20" width="12" height="40" rx="2" fill="white" opacity="0.7"/>
<rect x="68" y="10" width="12" height="50" rx="2" fill="white" opacity="0.7"/>
<rect x="88" y="35" width="12" height="25" rx="2" fill="white" opacity="0.7"/>
<rect x="108" y="22" width="12" height="38" rx="2" fill="white" opacity="0.7"/>
<rect x="128" y="15" width="12" height="45" rx="2" fill="white" opacity="0.7"/>
</svg>
</MkChartCard>
<!-- Line chart placeholder -->
<MkChartCard
title="Daily Sales"
subtitle="<span class='font-bold'>+15%</span> increase in today's sales"
update="updated 4 min ago"
color="success"
>
<svg viewBox="0 0 140 60" class="w-full h-20" preserveAspectRatio="none">
<polyline
points="0,50 18,38 35,42 52,20 70,25 88,12 105,18 122,8 140,14"
fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.9"
/>
<polyline
points="0,50 18,38 35,42 52,20 70,25 88,12 105,18 122,8 140,14 140,60 0,60"
fill="white" opacity="0.15"
/>
</svg>
</MkChartCard>
<!-- Line chart placeholder dark -->
<MkChartCard
title="Completed Tasks"
subtitle="Last campaign performance"
update="just updated"
color="dark"
>
<svg viewBox="0 0 140 60" class="w-full h-20" preserveAspectRatio="none">
<polyline
points="0,45 18,36 35,48 52,22 70,32 88,18 105,28 122,10 140,20"
fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.9"
/>
<polyline
points="0,45 18,36 35,48 52,22 70,32 88,18 105,28 122,10 140,20 140,60 0,60"
fill="white" opacity="0.15"
/>
</svg>
</MkChartCard>
</div>
<!-- Timeline card -->
<div class="grid gap-6 md:grid-cols-2">
<MkTimelineList
title="Orders Overview"
description="<span class='text-success font-medium'>+24%</span> this month"
>
<MkTimelineItem icon="notifications" color="success" title="$2,400 Design changes" date-time="22 DEC 7:20 PM" />
<MkTimelineItem icon="code" color="danger" title="New order #1832412" date-time="21 DEC 11:00 PM" />
<MkTimelineItem icon="shopping_cart" color="info" title="Server payments for April" date-time="21 DEC 9:34 PM" />
<MkTimelineItem icon="credit_card" color="warning" title="New card added for order #4395133" date-time="20 DEC 2:20 AM" />
<MkTimelineItem icon="vpn_key" color="primary" title="Unlock packages for development" date-time="18 DEC 4:54 AM" :last="true" />
</MkTimelineList>
<!-- Dark variant -->
<MkTimelineList
title="Recent Activity"
description="<span class='text-white font-medium'>12 events</span> this week"
:dark="true"
>
<MkTimelineItem icon="check_circle" color="success" title="Design system shipped" date-time="Today 10:00 AM" />
<MkTimelineItem icon="build" color="info" title="Phase 4 components merged" date-time="Yesterday 3:40 PM" />
<MkTimelineItem icon="bug_report" color="danger" title="Alert transition bug fixed" date-time="2 days ago" />
<MkTimelineItem icon="star" color="warning" title="v0.1.0 tagged" date-time="3 days ago" :last="true" />
</MkTimelineList>
</div>
</section>
<!-- ⑪ Counter · Review · Team ───────────────────────────── -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Counter, Review & Team Cards</h2>
<p class="text-sm text-secondary mb-4">animated counters · star ratings · horizontal profile</p>
<div class="grid gap-6 lg:grid-cols-3">
<!-- Counter cards -->
<div class="rounded-2xl bg-white p-8 shadow-soft-md">
<div class="grid grid-cols-2 gap-6">
<MkDefaultCounterCard :count="240" suffix="+" color="primary" title="Projects" description="Completed this year" divider="horizontal" />
<MkDefaultCounterCard :count="4500" suffix="+" color="success" title="Users" description="Active accounts" divider="horizontal" />
<MkDefaultCounterCard :count="99" suffix="%" color="info" title="Uptime" description="Last 12 months" divider="horizontal" />
<MkDefaultCounterCard :count="18" color="warning" title="Countries" description="Worldwide reach" divider="horizontal" />
</div>
</div>
<!-- Review card -->
<MkDefaultReviewCard
:image="img('rev1',200,200)"
name="Sofia Reyes"
date="2 days ago"
review="This design system saved us weeks of setup. The components are clean and the token system makes theming trivial."
:rating="5"
/>
<!-- Team card -->
<MkHorizontalTeamCard
:image="img('team1',400,500)"
:profile="{ name:'James Donovan', link:'#' }"
:position="{ label:'Lead Engineer', color:'primary' }"
description="James leads the frontend platform team and maintains the component library. Open source contributor and design systems enthusiast."
/>
</div>
</section>
<!-- ⑪ Rotating Card ─────────────────────────────────────── -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Rotating Card</h2>
<p class="text-sm text-secondary mb-4">hover to flip — CSS 3D, no JS</p>
<div class="max-w-xs">
<div style="height: 22rem">
<MkRotatingCard color="primary" min-height="22rem">
<MkRotatingCardFront
:image="img('rot-front',600,800)"
icon="layers"
label="Design System"
title="Material Kit 2"
description="Vue 3 + Tailwind v4"
/>
<MkRotatingCardBack
:image="img('rot-back',600,800)"
title="Get Started"
description="Build beautiful interfaces with a consistent, token-driven system."
:action="[
{ route:'#', label:'Docs', color:'white' },
{ route:'#', label:'GitHub', color:'white' },
]"
/>
</MkRotatingCard>
</div>
</div>
</section>
<!-- ⑫ Table ──────────────────────────────────────────────── -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Table</h2>
<p class="text-sm text-secondary mb-4">avatar + status badge + action</p>
<MkTable :rows="tableRows">
<template #header>
<div class="flex items-center justify-between">
<h6 class="text-sm font-bold text-dark">Authors</h6>
<MkBadge color="success" :rounded="true">5 active</MkBadge>
</div>
</template>
<template #footer>
<MkPagination color="primary" size="sm">
<MkPaginationItem :prev="true" />
<MkPaginationItem label="1" :active="true" />
<MkPaginationItem label="2" />
<MkPaginationItem label="3" />
<MkPaginationItem :next="true" />
</MkPagination>
</template>
</MkTable>
</section>
<!-- ⑬ Layout Components ──────────────────────────────────── -->
<section>
<h2 class="text-lg font-bold text-dark mb-0.5">Layout Components</h2>
<p class="text-sm text-secondary mb-4">navbar · header · breadcrumbs · footer</p>
<div class="space-y-6">
<!-- Breadcrumbs -->
<div class="rounded-2xl bg-white p-6 shadow-soft-md">
<p class="mb-3 text-xs font-medium uppercase tracking-wide text-secondary">MkBreadcrumbs</p>
<MkBreadcrumbs :routes="breadcrumbRoutes" />
</div>
<!-- Navbar preview -->
<div class="rounded-2xl shadow-soft-md overflow-hidden">
<p class="bg-gray-50 px-4 py-2 text-xs font-medium text-secondary border-b border-gray-100">MkNavbar — light mode (hover items for dropdown)</p>
<MkNavbar
:brand="{ name:'MK Design', route:'#' }"
:nav-items="navItems"
:action="{ label:'Get Started', href:'#', color:'primary' }"
:sticky="false"
/>
</div>
<!-- Header preview -->
<div class="rounded-2xl shadow-soft-md overflow-hidden">
<p class="bg-gray-50 px-4 py-2 text-xs font-medium text-secondary border-b border-gray-100">MkHeader — background image + gradient mask</p>
<MkHeader
:image="img('header1',1200,600)"
:title="{ text:'Build Something Beautiful', variant:'h2' }"
description="A design system for Vue 3 and Tailwind v4."
mask="dark"
:mask-opacity="0.6"
:center="true"
min-height="200px"
/>
</div>
<!-- Footer preview -->
<div class="rounded-2xl shadow-soft-md overflow-hidden">
<p class="bg-gray-50 px-4 py-2 text-xs font-medium text-secondary border-b border-gray-100">MkFooterCentered</p>
<MkFooterCentered
:links="[
{ name:'Company', href:'#' },
{ name:'About', href:'#' },
{ name:'Blog', href:'#' },
{ name:'License', href:'#' },
]"
:socials="[
{ icon:'language', link:'#', label:'Website' },
{ icon:'code', link:'#', label:'GitHub' },
{ icon:'mail', link:'#', label:'Email' },
]"
:copyright="`© ${new Date().getFullYear()} MK Design System`"
/>
</div>
</div>
</section>
</div>
</DashboardLayout>
</template>
+1 -1
View File
@@ -17,7 +17,7 @@ const rows = [
<!-- Header -->
<div class="flex items-center justify-between border-b border-gray-100 px-6 py-4">
<h6 class="text-sm font-bold text-dark">Authors Table</h6>
<button class="rounded-lg bg-gradient-primary px-4 py-2 text-xs font-medium text-white shadow-primary hover:shadow-soft-md transition-shadow">
<button class="rounded-lg bg-gradient-primary px-4 py-2 text-xs font-medium text-white shadow-primary hover:-translate-y-0.5 active:translate-y-0 transition-all">
+ New Entry
</button>
</div>
+121
View File
@@ -0,0 +1,121 @@
{
"meta": {
"project": "mk-design-system",
"sourceApproach": "tailwindcss-v4",
"primaryFont": "Roboto",
"fontType": "variable-woff2",
"colorMode": "light",
"version": "0.1.0"
},
"colors": {
"primary": { "DEFAULT": "#e91e63", "gradientStart": "#EC407A", "gradientEnd": "#D81B60" },
"secondary": { "DEFAULT": "#7b809a", "gradientStart": "#747b8a", "gradientEnd": "#495361" },
"semantic": {
"success": { "DEFAULT": "#4caf50", "gradientStart": "#66bb6a", "gradientEnd": "#43a047" },
"warning": { "DEFAULT": "#fb8c00", "gradientStart": "#ffa726", "gradientEnd": "#fb8c00" },
"danger": { "DEFAULT": "#f44335", "gradientStart": "#ef5350", "gradientEnd": "#e53935" },
"info": { "DEFAULT": "#1a73e8", "gradientStart": "#49a3f1", "gradientEnd": "#1a73e8" }
},
"neutrals": {
"white": "#ffffff",
"black": "#000000",
"light": "#f0f2f5",
"dark": "#344767",
"gray": {
"100": "#f8f9fa",
"200": "#f0f2f5",
"300": "#dee2e6",
"500": "#adb5bd",
"600": "#6c757d",
"900": "#212529"
}
}
},
"typography": {
"fontFamilies": {
"sans": "Roboto, Helvetica, Arial, sans-serif",
"serif": "Roboto Slab, serif",
"mono": "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"
},
"fontFiles": {
"roboto": "public/fonts/roboto/Roboto-VariableFont_wdth-wght.woff2",
"robotoItalic":"public/fonts/roboto/Roboto-Italic-VariableFont_wdth-wght.woff2",
"robotoSlab": "public/fonts/roboto-slab/RobotoSlab-VariableFont_wght.woff2",
"materialIcons":"public/fonts/material-icons-round/material-icons-round-latin-400-normal.woff2"
},
"weightRange": "100900 (variable font)",
"sizes": {
"xs": "0.75rem",
"sm": "0.875rem",
"base": "1rem",
"lg": "1.125rem",
"xl": "1.25rem",
"2xl": "1.5rem",
"3xl": "1.875rem",
"4xl": "2.25rem",
"5xl": "3rem"
},
"lineHeights": { "base": "1.5", "tight": "1.25", "relaxed": "1.75" }
},
"spacing": {
"note": "Tailwind v4 default scale — rem-based, no custom overrides needed",
"common": {
"1": "0.25rem", "2": "0.5rem", "3": "0.75rem",
"4": "1rem", "6": "1.5rem", "8": "2rem",
"12": "3rem", "16": "4rem", "24": "6rem"
}
},
"shadows": {
"soft": {
"xs": "0 2px 9px -5px rgba(0,0,0,0.15)",
"sm": "0 0.3125rem 0.625rem 0 rgba(0,0,0,0.12)",
"md": "0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06)",
"lg": "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05)"
},
"blur": "0 20px 27px rgba(0,0,0,0.05)",
"colored": {
"note": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba({r},{g},{b},0.4)",
"primary": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba(233,30,99,0.4)",
"secondary": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba(210,210,210,0.4)",
"success": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba(76,175,80,0.4)",
"warning": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba(251,140,0,0.4)",
"danger": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba(244,67,54,0.4)",
"info": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba(26,115,232,0.4)",
"dark": "0 4px 20px 0 rgba(0,0,0,0.14), 0 7px 10px -5px rgba(64,64,64,0.4)"
}
},
"borders": {
"radius": {
"xs": "0.1rem",
"sm": "0.125rem",
"md": "0.375rem",
"lg": "0.5rem",
"xl": "0.75rem",
"2xl": "1rem",
"full":"9999px"
},
"width": { "default": "1px" }
},
"gradients": {
"angle": "195deg",
"note": "All gradients use the 195deg angle — do not change",
"variants": {
"primary": "linear-gradient(195deg, #EC407A, #D81B60)",
"secondary": "linear-gradient(195deg, #747b8a, #495361)",
"success": "linear-gradient(195deg, #66bb6a, #43a047)",
"warning": "linear-gradient(195deg, #ffa726, #fb8c00)",
"danger": "linear-gradient(195deg, #ef5350, #e53935)",
"info": "linear-gradient(195deg, #49a3f1, #1a73e8)",
"dark": "linear-gradient(195deg, #42424a, #191919)",
"light": "linear-gradient(195deg, #ebeff4, #ced4da)"
}
},
"breakpoints": {
"note": "Tailwind v4 defaults",
"sm": "640px",
"md": "768px",
"lg": "1024px",
"xl": "1280px",
"2xl": "1536px"
}
}
+24 -2
View File
@@ -1,10 +1,32 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
import { resolve } from 'path'
const isLib = process.env.BUILD_TARGET === 'lib'
export default defineConfig({
plugins: [
tailwindcss(),
// Tailwind only for the app build — consuming projects bring their own
!isLib && tailwindcss(),
vue(),
],
].filter(Boolean),
build: isLib
? {
lib: {
entry: resolve(__dirname, 'src/index.js'),
name: 'MkDesignSystem',
fileName: format => `mk-design-system.${format}.js`,
formats: ['es', 'cjs'],
},
rollupOptions: {
// Vue must be provided by the consuming project
external: ['vue'],
output: { globals: { vue: 'Vue' } },
},
// Emit a separate CSS file (design-system.css) consumers can import
cssCodeSplit: false,
}
: {},
})