Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
87c4553
Draft an alert component
JayGeorge Jan 19, 2026
3be6edd
Fix icons
JayGeorge Jan 19, 2026
0cfb821
Alert - improve markup
JayGeorge Jan 19, 2026
ef27349
Alert - tweak alignment
JayGeorge Jan 19, 2026
d9952d8
Alert - message styling
JayGeorge Jan 19, 2026
136176a
Alert - Style code snippets
JayGeorge Jan 19, 2026
e571449
Alert / Style code snippets - remove unnecessary borders
JayGeorge Jan 19, 2026
2fafc24
Alert / Style code snippets - fix dark mode legibility
JayGeorge Jan 19, 2026
7b36101
Alert / Style code snippets - improve
JayGeorge Jan 19, 2026
6987890
Dashboard demo
JayGeorge Jan 19, 2026
573a8e8
Merge branch 'master' into alert-component
JayGeorge Jan 19, 2026
8f69599
Move the demo to the playground
JayGeorge Jan 19, 2026
46e755b
Improve realism of demo messages
JayGeorge Jan 19, 2026
0f8e853
Add alert component to the test's expected exports list
JayGeorge Jan 19, 2026
312826d
Add Alert to the package export file
JayGeorge Jan 19, 2026
fa29d92
Alert - make code size smaller
JayGeorge Jan 19, 2026
f5e79e3
Alert - add storybook component
JayGeorge Jan 20, 2026
02739de
Add some rich content examples
JayGeorge Jan 20, 2026
97c436c
Fix icon
JayGeorge Jan 20, 2026
9d8b22e
Add docs
JayGeorge Jan 20, 2026
6230291
Add instruction
JayGeorge Jan 20, 2026
84be7e9
Simplify the Alert component to use Vue's slot fallback syntax
JayGeorge Jan 20, 2026
9c8b80b
Alerts should work with the Heading and Description components
JayGeorge Jan 21, 2026
dd46b21
Add some demos
JayGeorge Jan 21, 2026
37f1143
Tweak variations
JayGeorge Jan 21, 2026
684c786
Merge branch 'master' into alert-component
JayGeorge Jan 21, 2026
5f2ddb3
Finish documenting using Alerts with Heading/Description components
JayGeorge Jan 21, 2026
127ffdc
heading prop and use heading/description components
jasonvarga Jan 21, 2026
15544e7
Fix color inconsistencies
JayGeorge Jan 22, 2026
52a474b
Fix color inconsistencies
JayGeorge Jan 22, 2026
c4dc1ae
Fix dark icon colors
JayGeorge Jan 22, 2026
f4cb913
Dark mode fix
JayGeorge Jan 22, 2026
cc09401
Fix inconsistency
JayGeorge Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cms/src/ui.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const {
Alert,
AuthCard,
Avatar,
Badge,
Expand Down
1 change: 1 addition & 0 deletions resources/js/bootstrap/cms/ui.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {
Alert,
AuthCard,
Avatar,
Badge,
Expand Down
118 changes: 118 additions & 0 deletions resources/js/components/ui/Alert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<script setup>
import { computed } from 'vue';
import { cva } from 'cva';
import { Icon, Heading, Description } from '@ui';

const props = defineProps({
/** The alert message to display */
text: { type: [String, Number, Boolean, null], default: null },
/** The alert heading to display */
heading: { type: [String, null], default: null },
/** Controls the appearance of the alert. <br><br> Options: `default`, `warning`, `error`, `success` */
variant: { type: String, default: 'default' },
/** Icon name to display. [Browse available icons](/?path=/story/components-icon--all-icons) */
icon: { type: String, default: null },
});

const alertRole = computed(() => {
// Use 'alert' for urgent messages that need immediate attention
// Use 'status' for informational messages
return props.variant === 'error' || props.variant === 'warning' ? 'alert' : 'status';
});

const ariaLive = computed(() => {
// 'assertive' for urgent, 'polite' for informational
return props.variant === 'error' || props.variant === 'warning' ? 'assertive' : 'polite';
});

const alertClasses = computed(() => {
return cva({
base: [
'relative flex items-start gap-3 rounded-xl border p-4 [&:has(p)]:py-5 [&:has([data-ui-description])]:py-5 text-sm',
'[&_h1]:mb-1 [&_h1]:font-bold',
'[&_h2]:mb-1 [&_h2]:font-bold',
'[&_h3]:mb-1 [&_h3]:font-bold',
'[&_h4]:mb-1 [&_h4]:font-bold',
'[&_h5]:mb-1 [&_h5]:font-bold',
'[&_h6]:mb-1 [&_h6]:font-bold',
'[&_[data-ui-heading]]:mb-1 [&_[data-ui-heading]]:font-bold',
'[&_[data-ui-heading]]:-mt-0.5',
'[&_[data-ui-heading].text-lg]:-mt-1',
'[&_[data-ui-heading].text-2xl]:mt-[-0.45rem] [&_[data-ui-heading].text-2xl]:mb-2',
'[&_p:not(:last-child)]:mb-3',
'[&_[data-ui-description]:not(:last-child)]:mb-3',
'[&_code]:px-0.75 [&_code]:mx-0.5 [&_code]:py-0.5 [&_code]:rounded-sm [&_code]:text-xs',
].join(' '),
variants: {
variant: {
default: [
'bg-gray-50 dark:bg-gray-900/50 border-gray-200 dark:border-gray-700/80 text-gray-800 dark:text-gray-100',
'[&_code]:bg-gray-200! [&_code]:text-gray-800! dark:[&_code]:bg-gray-800! dark:[&_code]:text-gray-200!',
'[&_[data-ui-heading]]:text-gray-800! dark:[&_[data-ui-heading]]:text-gray-100!',
'[&_[data-ui-description]]:text-gray-800! dark:[&_[data-ui-description]]:text-gray-300!',
'dark:[&_svg]:text-gray-400',
],
warning: [
'bg-amber-50 dark:bg-amber-300/6 border-amber-200 dark:border-amber-400/25 text-amber-800 dark:text-amber-200',
'[&_code]:bg-amber-200/50! [&_code]:text-amber-800! dark:[&_code]:bg-amber-300/8! dark:[&_code]:text-amber-200!',
'[&_[data-ui-heading]]:text-amber-800! dark:[&_[data-ui-heading]]:text-amber-300!',
'[&_[data-ui-description]]:text-amber-800! dark:[&_[data-ui-description]]:text-amber-200!',
'dark:[&_svg]:text-amber-300',
],
error: [
'bg-red-50 dark:bg-red-300/6 border-red-200 dark:border-red-400/25 text-red-800 dark:text-red-300',
'[&_code]:bg-red-200/50! [&_code]:text-red-800! dark:[&_code]:bg-red-300/8! dark:[&_code]:text-red-200!',
'[&_[data-ui-heading]]:text-red-800! dark:[&_[data-ui-heading]]:text-red-300!',
'[&_[data-ui-description]]:text-red-800! dark:[&_[data-ui-description]]:text-red-200!',
'dark:[&_svg]:text-red-300',
],
success: [
'bg-emerald-50 dark:bg-emerald-300/6 border-emerald-200 dark:border-emerald-400/25 text-emerald-800 dark:text-emerald-300',
'[&_code]:bg-emerald-200/50! [&_code]:text-emerald-800! dark:[&_code]:bg-emerald-300/8! dark:[&_code]:text-emerald-200!',
'[&_[data-ui-heading]]:text-emerald-800! dark:[&_[data-ui-heading]]:text-emerald-300!',
'[&_[data-ui-description]]:text-emerald-800! dark:[&_[data-ui-description]]:text-emerald-200!',
'dark:[&_svg]:text-emerald-300',
],
},
},
})({ variant: props.variant });
});

const defaultIcon = computed(() => {
if (props.icon) return props.icon;

switch (props.variant) {
case 'warning':
return 'warning-diamond';
case 'error':
return 'alert-alarm-bell';
case 'success':
return 'checkmark';
default:
return 'info';
}
});
</script>

<template>
<div
:class="alertClasses"
:role="alertRole"
:aria-live="ariaLive"
data-ui-alert
:data-variant="variant"
>
<Icon
v-if="defaultIcon"
:name="defaultIcon"
class="size-5 shrink-0 opacity-70"
aria-hidden="true"
/>
<div class="flex-1 min-w-0">
<slot>
<Heading v-if="heading" :text="heading" />
<Description :text="text" />
</slot>
</div>
</div>
</template>
1 change: 1 addition & 0 deletions resources/js/components/ui/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as Alert } from './Alert.vue';
export { default as AuthCard } from './AuthCard.vue';
export { default as Badge } from './Badge.vue';
export { default as Button } from './Button/Button.vue';
Expand Down
62 changes: 62 additions & 0 deletions resources/js/pages/Playground.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup>
import Head from '@/pages/layout/Head.vue';
import { Alert, Heading, Description } from '@ui';

defineProps(['icons']);
</script>
Expand Down Expand Up @@ -298,6 +299,67 @@ defineProps(['icons']);
</div>
</section>

<section class="mb-10 space-y-3">
<ui-heading size="lg">Alerts</ui-heading>

<Alert variant="default" text="This is a default alert message" />
<Alert variant="warning" text="This is a warning alert message" />
<Alert variant="error" text="This is an error alert message" />
<Alert variant="success" text="This is a success alert message" />
<Alert variant="warning" icon="git" text="This alert has a custom icon" />

<Alert variant="default" heading="Alert Heading" text="This is a default alert message" />
<Alert variant="warning" heading="Alert Heading" text="This is a warning alert message" />
<Alert variant="error" heading="Alert Heading" text="This is an error alert message" />
<Alert variant="success" heading="Alert Heading" text="This is a success alert message" />
<Alert variant="warning" heading="Alert Heading" icon="git" text="This alert has a custom icon" />

<Alert variant="success">
<strong>Success!</strong> This alert uses a slot for custom content.
</Alert>
<Alert variant="warning">
<h1>Please run your migrations</h1>
<p>The importer uses Laravel's job batching feature to keep track of the import progress, however, it requires a <code>job_batches</code> table in your database. Before you can run the importer, you will need to run <code>php artisan migrate</code>. This alert uses a heading for the title and a paragraph for the message.</p>
</Alert>
<Alert variant="default">
<h2>New Feature Available</h2>
<p>We've added support for custom field types. You can now create your own field types by extending the <code>Fieldtype</code> class. Check out the documentation for more details.</p>
</Alert>
<Alert variant="success">
<h3>Backup Completed Successfully</h3>
<p>Your site backup has been created and saved to <code>/storage/backups/site-2032-01-15.tar.gz</code>. The backup includes all content, assets, and configuration files.</p>
</Alert>
<Alert variant="error">
<h4>Failed to Connect to Database</h4>
<p>Unable to establish a connection to the database server. Please check your database configuration in <code>.env</code> and ensure the database server is running.</p>
</Alert>
<hr class="my-10">
<Alert variant="default">
<Heading>Using Heading Component</Heading>
<Description>This alert uses the Heading and Description components instead of native HTML elements. The styles should match the variant colors.</Description>
</Alert>
<Alert variant="warning">
<Heading size="lg">Warning: Action Required</Heading>
<Description>This is a heading size <code>lg</code> example, a warning alert with a larger heading. The Heading component supports different sizes and should inherit the alert's color scheme.</Description>
</Alert>
<Alert variant="success">
<Heading size="xl">Backup Completed Successfully</Heading>
<Description>Your site backup has been created and saved to <a href="https://statamic.dev"><code>/storage/backups/site-2032-01-15.tar.gz</code></a>. The backup includes all content, assets, and configuration files.</Description>
</Alert>
<Alert variant="success">
<Heading size="2xl">Backup Completed Very Successfully</Heading>
<Description>Such a massive heading isn't recommended, but here it is for testing purposes. Your site backup has been created and saved to <a href="https://statamic.dev"><code>/storage/backups/site-2032-01-15.tar.gz</code></a>. The backup includes all content, assets, and configuration files.</Description>
</Alert>
<Alert variant="error">
<Heading level="2">Database Connection Failed</Heading>
<Description>This is a heading level <code>2</code> example, with no heading size difference. Unable to establish a connection to the database server. Please check your database configuration in <code>.env</code> and ensure the database server is running.</Description>
</Alert>
<Alert variant="warning">
<Heading>Migration Required</Heading>
<Description>The importer uses Laravel's job batching feature to keep track of the import progress, however, it requires a <code>job_batches</code> table in your database. Before you can run the importer, you will need to run <code>php artisan migrate</code>.</Description>
</Alert>
</section>

<section class="space-y-4">
<ui-heading size="lg">Calendar</ui-heading>
<div class="flex flex-wrap gap-6">
Expand Down
Loading