Skip to content

Commit 2107c0d

Browse files
authored
Merge pull request #109 from incluud/next
Next
2 parents fc20240 + 96dd271 commit 2107c0d

File tree

6 files changed

+123
-61
lines changed

6 files changed

+123
-61
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "accessible-astro-components",
33
"description": "A set of Accessible, easy to use, Front-end UI Components for Astro.",
4-
"version": "4.1.0",
4+
"version": "4.1.1",
55
"author": "Incluud",
66
"license": "MIT",
77
"homepage": "https://accessible-astro.incluud.dev/components/overview/",

src/components/badge/Badge.astro

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ interface Props {
3333
* Type of animation to apply on hover (when isButton is true)
3434
* @default "boop"
3535
*/
36-
animationType?: 'rotate' | 'boop' | 'bounce' | 'none'
36+
animationType?: 'rotate' | 'boop' | 'bouncing' | 'none'
3737
/**
3838
* Animation intensity (1-10)
3939
* @default 5
@@ -81,15 +81,18 @@ const {
8181
...rest
8282
} = Astro.props
8383
84+
// Clamp animation intensity between 1-10
85+
const clampedIntensity = Math.max(1, Math.min(10, animationIntensity))
86+
8487
// Calculate animation values based on intensity
85-
const scaleAmount = 1 + animationIntensity * 0.02 // 1.02 to 1.2
86-
const rotateAmount = animationIntensity * 2 // 2deg to 20deg
88+
const scaleAmount = 1 + clampedIntensity * 0.02 // 1.02 to 1.2
89+
const rotateAmount = clampedIntensity * 2 // 2deg to 20deg
8790
88-
// Only apply animation class if it's a button
89-
const shouldAnimate = isButton && animateOnHover
91+
// Only apply animation class if it's a button and not 'none' type
92+
const shouldAnimate = isButton && animateOnHover && animationType !== 'none'
9093
9194
// Determine animation class based on type
92-
const animationClass = shouldAnimate ? `animate-${animationType}` : ''
95+
const animationClass = shouldAnimate ? `animate-${animationType}-svg` : ''
9396
9497
// Determine if we should apply the circular class
9598
const circularClass = isCircular ? 'circular' : ''
@@ -98,7 +101,7 @@ const circularClass = isCircular ? 'circular' : ''
98101
const pillClass = isPill ? 'pill' : ''
99102
100103
// Determine if we should apply the pulse class
101-
const pulseClass = pulse ? 'pulse' : ''
104+
const pulseClass = pulse ? 'animation-pulse' : ''
102105
103106
// Map logical size values to CSS class names
104107
const sizeClass = `size-${size}`
@@ -117,6 +120,7 @@ const sizeClass = `size-${size}`
117120
pulseClass,
118121
className,
119122
]}
123+
style={`--scaleAmount: ${scaleAmount}; --rotateAmount: ${rotateAmount}deg;`}
120124
{...rest}
121125
>
122126
{label && <span class="sr-only">{label}</span>}
@@ -141,12 +145,7 @@ const sizeClass = `size-${size}`
141145
)
142146
}
143147

144-
<style
145-
define:vars={{
146-
scaleAmount,
147-
rotateAmount: `${rotateAmount}deg`,
148-
}}
149-
>
148+
<style>
150149
:where(.badge) {
151150
--transition-duration: 0.3s;
152151
--transition-easing: cubic-bezier(0.165, 0.84, 0.44, 1);
@@ -269,56 +268,23 @@ const sizeClass = `size-${size}`
269268
}
270269

271270
/* Set pulse colors based on badge type */
272-
:where(.pulse.type-default) {
271+
:where(.animation-pulse.type-default) {
273272
--pulse-color: var(--color-pulse-default);
274273
}
275274

276-
:where(.pulse.type-info) {
275+
:where(.animation-pulse.type-info) {
277276
--pulse-color: var(--color-pulse-info);
278277
}
279278

280-
:where(.pulse.type-success) {
279+
:where(.animation-pulse.type-success) {
281280
--pulse-color: var(--color-pulse-success);
282281
}
283282

284-
:where(.pulse.type-warning) {
283+
:where(.animation-pulse.type-warning) {
285284
--pulse-color: var(--color-pulse-warning);
286285
}
287286

288-
:where(.pulse.type-error) {
287+
:where(.animation-pulse.type-error) {
289288
--pulse-color: var(--color-pulse-error);
290289
}
291-
292-
/* Apply pulse animation */
293-
:where(.pulse) {
294-
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) 3;
295-
}
296-
297-
/* Animation classes - apply only to SVG icons */
298-
button.animate-boop:where(:hover, :focus-visible) :global(svg) {
299-
animation: boop 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
300-
}
301-
302-
button.animate-bounce:where(:hover, :focus-visible) :global(svg) {
303-
animation: bounce 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
304-
}
305-
306-
button.animate-rotate:where(:hover, :focus-visible) :global(svg) {
307-
animation: rotate 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
308-
}
309-
310-
/* Respect user preferences */
311-
@media (prefers-reduced-motion: reduce) {
312-
button.animate-boop:where(:hover, :focus-visible) :global(svg),
313-
button.animate-bounce:where(:hover, :focus-visible) :global(svg),
314-
button.animate-rotate:where(:hover, :focus-visible) :global(svg) {
315-
transform: none;
316-
animation: none;
317-
}
318-
319-
:where(.pulse) {
320-
animation: none;
321-
box-shadow: 0 0 0 2px rgba(var(--pulse-color, 0, 0, 0), 0.3);
322-
}
323-
}
324290
</style>

src/components/card/Card.astro

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
*
55
* @description A versatile card component with image, title, and content areas
66
*/
7+
import { Image } from 'astro:assets'
8+
import type { ImageMetadata } from 'astro'
9+
710
interface Props extends astroHTML.JSX.HTMLAttributes {
811
/**
912
* Additional classes to apply to the card
@@ -19,6 +22,32 @@ interface Props extends astroHTML.JSX.HTMLAttributes {
1922
* @default "https://fakeimg.pl/640x360"
2023
*/
2124
img?: string
25+
/**
26+
* Optional Astro Image component to use instead of a regular img
27+
* Can be a direct ImageMetadata object or a Promise from import()
28+
* @default undefined
29+
*/
30+
imageComponent?: ImageMetadata | Promise<{ default: ImageMetadata }>
31+
/**
32+
* Alt text for the image
33+
* @default ""
34+
*/
35+
imageAlt?: string
36+
/**
37+
* Width of the image (required for remote images with Image component)
38+
* @default 640
39+
*/
40+
width?: number
41+
/**
42+
* Height of the image (required for remote images with Image component)
43+
* @default 360
44+
*/
45+
height?: number
46+
/**
47+
* Whether to infer the image size (for remote images)
48+
* @default false
49+
*/
50+
inferSize?: boolean
2251
/**
2352
* URL for the card's link
2453
* @default "#"
@@ -40,6 +69,11 @@ const {
4069
class: className,
4170
title = 'Default title',
4271
img = 'https://fakeimg.pl/640x360',
72+
imageComponent,
73+
imageAlt = '',
74+
width = 640,
75+
height = 360,
76+
inferSize = false,
4377
url = '#',
4478
tagName = 'h3',
4579
footer = '',
@@ -51,7 +85,13 @@ const Tag = tagName
5185

5286
<article class:list={['card', className]} {...rest}>
5387
<div class="image">
54-
<img src={img} alt="" />
88+
{
89+
imageComponent ? (
90+
<Image src={imageComponent} alt={imageAlt} width={width} height={height} />
91+
) : (
92+
<img src={img} alt={imageAlt} width={width} height={height} />
93+
)
94+
}
5595
</div>
5696
<div class="content">
5797
<div class="title">

src/components/darkmode/DarkMode.astro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ interface Props {
1919
* @default "auto"
2020
*/
2121
initialMode?: 'light' | 'dark' | 'auto'
22+
/**
23+
* Children elements for slots
24+
*/
25+
children?: any
2226
/**
2327
* HTML attributes to spread on the dark mode toggle
2428
*/
25-
[key: string]: string | number | boolean | undefined
29+
[key: string]: string | number | boolean | undefined | any
2630
}
2731
2832
const { class: className, label = 'Toggle Dark Mode', initialMode = 'auto', ...rest } = Astro.props

src/styles/animations.css

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
* Common animations used across components
44
*/
55

6+
/* Common animation variables */
7+
:root {
8+
--animation-timing: cubic-bezier(0.165, 0.84, 0.44, 1);
9+
--animation-duration-hover: 0.5s;
10+
--animation-duration-pulse: 2s;
11+
}
12+
613
/* Pulse animation for badges and notifications */
714
@keyframes pulse {
815
0% {
@@ -37,8 +44,8 @@
3744
}
3845
}
3946

40-
/* Bounce animation for buttons and interactive elements */
41-
@keyframes bounce {
47+
/* Bouncing animation for buttons and interactive elements */
48+
@keyframes bouncing {
4249
0% {
4350
transform: translateY(0);
4451
}
@@ -69,16 +76,54 @@
6976
}
7077
}
7178

79+
/* Animation utility classes */
80+
@media (prefers-reduced-motion: no-preference) {
81+
.animate-boop:where(:hover, :focus-visible) {
82+
animation: boop var(--animation-duration-hover) var(--animation-timing) forwards;
83+
}
84+
85+
.animate-bouncing:where(:hover, :focus-visible) {
86+
animation: bouncing var(--animation-duration-hover) var(--animation-timing) forwards;
87+
}
88+
89+
.animate-rotate:where(:hover, :focus-visible) {
90+
animation: rotate var(--animation-duration-hover) var(--animation-timing) forwards;
91+
}
92+
93+
/* Classes specifically for SVG animation */
94+
.animate-boop-svg:where(:hover, :focus-visible) svg {
95+
animation: boop var(--animation-duration-hover) var(--animation-timing) forwards;
96+
}
97+
98+
.animate-bouncing-svg:where(:hover, :focus-visible) svg {
99+
animation: bouncing var(--animation-duration-hover) var(--animation-timing) forwards;
100+
}
101+
102+
.animate-rotate-svg:where(:hover, :focus-visible) svg {
103+
animation: rotate var(--animation-duration-hover) var(--animation-timing) forwards;
104+
}
105+
106+
/* Pulse animation */
107+
.animation-pulse {
108+
--pulse-iteration-count: var(--pulse-iterations, 3);
109+
animation: pulse var(--animation-duration-pulse) var(--animation-timing)
110+
var(--pulse-iteration-count);
111+
}
112+
}
113+
72114
/* Respect user preferences */
73115
@media (prefers-reduced-motion: reduce) {
74-
.animate-boop:where(:hover, :focus-visible) svg,
75-
.animate-bounce:where(:hover, :focus-visible) svg,
76-
.animate-rotate:where(:hover, :focus-visible) svg {
116+
.animate-boop:where(:hover, :focus-visible),
117+
.animate-bouncing:where(:hover, :focus-visible),
118+
.animate-rotate:where(:hover, :focus-visible),
119+
.animate-boop-svg:where(:hover, :focus-visible) svg,
120+
.animate-bouncing-svg:where(:hover, :focus-visible) svg,
121+
.animate-rotate-svg:where(:hover, :focus-visible) svg {
77122
transform: none;
78123
animation: none;
79124
}
80125

81-
.pulse {
126+
.animation-pulse {
82127
animation: none;
83128
box-shadow: 0 0 0 2px rgba(var(--pulse-color, 0, 0, 0), 0.3);
84129
}

src/types/index.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const AvatarGroup: AvatarGroup
7272
* @param _props.type - Type/color variant ('info' | 'success' | 'warning' | 'error' | 'default') - default: 'default'
7373
* @param _props.isButton - Whether to render as a button element - default: false
7474
* @param _props.animateOnHover - Whether to animate the icon on hover (only works with isButton) - default: false
75-
* @param _props.animationType - Type of animation to apply on hover ('rotate' | 'boop' | 'bounce' | 'none') - default: 'boop'
75+
* @param _props.animationType - Type of animation to apply on hover ('rotate' | 'boop' | 'bouncing' | 'none') - default: 'boop'
7676
* @param _props.animationIntensity - Animation intensity (1-10) - default: 5
7777
* @param _props.isCircular - Whether to display the badge as a circle (best for single numbers/characters) - default: false
7878
* @param _props.isPill - Whether to display the badge with fully rounded corners (pill style) - default: false
@@ -119,6 +119,11 @@ export const Breadcrumbs: Breadcrumbs
119119
* @param _props - astroHTML.JSX.HTMLAttributes
120120
* @param _props.url - `<a href={url}>` - default: "#"
121121
* @param _props.img - `<img src={img}>` - default value = placeholder
122+
* @param _props.imageComponent - Optional Astro Image component (supports both ImageMetadata and dynamic import)
123+
* @param _props.imageAlt - Alt text for the image - default: ""
124+
* @param _props.width - Width of the image (required for remote images) - default: 640
125+
* @param _props.height - Height of the image (required for remote images) - default: 360
126+
* @param _props.inferSize - Whether to infer the image size (for remote images) - default: false
122127
* @param _props.title - `<h3>` header > `<a>` text content
123128
* @param _props.footer - `<small>` text content
124129
* @param _props.tagName - HTML tag to use for the title - default: 'h3'
@@ -138,6 +143,8 @@ export const Card: Card
138143
* @param _props.initialMode - Optional: Sets initial theme mode ('light' | 'dark' | 'auto') - default: 'auto'
139144
* @param _props.label - Optional: Accessible label for the toggle button - default: 'Toggle Dark Mode'
140145
* @param _props.class - Optional CSS class names
146+
* @param _props.children - Optional: Custom elements for light/dark mode icons using named slots
147+
* @note Supports named slots: "light" for light mode icon and "dark" for dark mode icon
141148
* @note Additional HTML attributes can be passed and will be spread to the root element
142149
* ```
143150
* <style>

0 commit comments

Comments
 (0)