mithril-materialized 3.8.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/badge.d.ts +129 -0
- package/dist/circular-progress.d.ts +43 -0
- package/dist/components.css +227 -0
- package/dist/index.css +702 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +277 -1
- package/dist/index.js +279 -0
- package/dist/index.min.css +1 -1
- package/dist/index.umd.js +279 -0
- package/dist/linear-progress.d.ts +40 -0
- package/package.json +1 -1
- package/sass/components/_badge-component.scss +203 -0
- package/sass/components/_circular-progress.scss +220 -0
- package/sass/components/_linear-progress.scss +183 -0
- package/sass/materialize.scss +4 -0
package/dist/index.umd.js
CHANGED
|
@@ -509,6 +509,106 @@
|
|
|
509
509
|
};
|
|
510
510
|
};
|
|
511
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Badge component
|
|
514
|
+
*
|
|
515
|
+
* Displays a badge anchored to a child element. Commonly used for notifications,
|
|
516
|
+
* counts, or status indicators. Supports flexible positioning, colors, and variants.
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* ```typescript
|
|
520
|
+
* // Basic notification badge
|
|
521
|
+
* m(Badge, { badgeContent: 5 },
|
|
522
|
+
* m('button.btn', 'Messages')
|
|
523
|
+
* )
|
|
524
|
+
*
|
|
525
|
+
* // Dot badge on avatar
|
|
526
|
+
* m(Badge, {
|
|
527
|
+
* variant: 'dot',
|
|
528
|
+
* color: 'green',
|
|
529
|
+
* overlap: 'circular'
|
|
530
|
+
* },
|
|
531
|
+
* m('img.circle', { src: 'avatar.jpg' })
|
|
532
|
+
* )
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
const Badge = () => {
|
|
536
|
+
return {
|
|
537
|
+
view: ({ attrs, children }) => {
|
|
538
|
+
const { badgeContent, max, anchorOrigin = { vertical: 'top', horizontal: 'right' }, overlap = 'rectangular', variant = 'standard', color = 'red', colorIntensity, invisible = false, showZero = false, 'aria-label': ariaLabel, badgeClassName = '', className = '' } = attrs, params = __rest(attrs, ["badgeContent", "max", "anchorOrigin", "overlap", "variant", "color", "colorIntensity", "invisible", "showZero", 'aria-label', "badgeClassName", "className"]);
|
|
539
|
+
// === VALIDATION: Single child element ===
|
|
540
|
+
const childArray = Array.isArray(children) ? children : children ? [children] : [];
|
|
541
|
+
if (childArray.length === 0) {
|
|
542
|
+
console.warn('Badge component requires a child element');
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
if (childArray.length > 1) {
|
|
546
|
+
console.warn('Badge component should only wrap a single child element. Using first child only.');
|
|
547
|
+
}
|
|
548
|
+
const child = childArray[0];
|
|
549
|
+
// === VISIBILITY LOGIC ===
|
|
550
|
+
// Hide badge if:
|
|
551
|
+
// 1. invisible prop is true, OR
|
|
552
|
+
// 2. For standard variant: badgeContent is undefined/null OR (badgeContent is 0 AND !showZero)
|
|
553
|
+
const shouldHideBadge = invisible ||
|
|
554
|
+
(variant === 'standard' &&
|
|
555
|
+
(badgeContent === undefined ||
|
|
556
|
+
badgeContent === null ||
|
|
557
|
+
(badgeContent === 0 && !showZero)));
|
|
558
|
+
// === BADGE CONTENT FORMATTING ===
|
|
559
|
+
// Apply max capping: if badgeContent > max, show "max+"
|
|
560
|
+
const getDisplayContent = () => {
|
|
561
|
+
if (variant === 'dot')
|
|
562
|
+
return '';
|
|
563
|
+
if (typeof badgeContent === 'number' && max !== undefined && badgeContent > max) {
|
|
564
|
+
return `${max}+`;
|
|
565
|
+
}
|
|
566
|
+
return String(badgeContent !== null && badgeContent !== void 0 ? badgeContent : '');
|
|
567
|
+
};
|
|
568
|
+
const displayContent = getDisplayContent();
|
|
569
|
+
// === CSS CLASS ASSEMBLY ===
|
|
570
|
+
// Wrapper classes
|
|
571
|
+
const wrapperClasses = ['badge-wrapper', className].filter(Boolean).join(' ').trim() || undefined;
|
|
572
|
+
// Badge element classes - using m-badge prefix to avoid Materialize conflicts
|
|
573
|
+
const positionClass = `m-badge--${anchorOrigin.vertical}-${anchorOrigin.horizontal}`;
|
|
574
|
+
const badgeClasses = [
|
|
575
|
+
'm-badge',
|
|
576
|
+
`m-badge--${variant}`,
|
|
577
|
+
positionClass,
|
|
578
|
+
`m-badge--${overlap}`,
|
|
579
|
+
`m-badge--${color}`,
|
|
580
|
+
colorIntensity ? `m-badge--${colorIntensity}` : '',
|
|
581
|
+
shouldHideBadge ? 'm-badge--invisible' : '',
|
|
582
|
+
badgeClassName,
|
|
583
|
+
]
|
|
584
|
+
.filter(Boolean)
|
|
585
|
+
.join(' ')
|
|
586
|
+
.trim();
|
|
587
|
+
// === ARIA ATTRIBUTES ===
|
|
588
|
+
const badgeAriaLabel = ariaLabel ||
|
|
589
|
+
(variant === 'dot'
|
|
590
|
+
? 'notification indicator'
|
|
591
|
+
: displayContent
|
|
592
|
+
? `${displayContent} notifications`
|
|
593
|
+
: 'notification badge');
|
|
594
|
+
// === RENDER ===
|
|
595
|
+
return m('.badge-wrapper', Object.assign(Object.assign({}, params), { className: wrapperClasses }), [
|
|
596
|
+
// Child element
|
|
597
|
+
child,
|
|
598
|
+
// Badge element - only render if not hidden
|
|
599
|
+
!shouldHideBadge
|
|
600
|
+
? m('span', {
|
|
601
|
+
className: badgeClasses,
|
|
602
|
+
'aria-label': badgeAriaLabel,
|
|
603
|
+
role: 'status',
|
|
604
|
+
'aria-live': 'polite',
|
|
605
|
+
}, variant === 'standard' ? displayContent : null)
|
|
606
|
+
: null,
|
|
607
|
+
]);
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
|
|
512
612
|
/**
|
|
513
613
|
* A simple material icon, defined by its icon name.
|
|
514
614
|
*
|
|
@@ -10835,6 +10935,182 @@
|
|
|
10835
10935
|
};
|
|
10836
10936
|
};
|
|
10837
10937
|
|
|
10938
|
+
/** Size dimensions in pixels */
|
|
10939
|
+
const SIZE_MAP = {
|
|
10940
|
+
small: 36,
|
|
10941
|
+
medium: 50,
|
|
10942
|
+
large: 64,
|
|
10943
|
+
};
|
|
10944
|
+
/** Stroke width in pixels */
|
|
10945
|
+
const STROKE_WIDTH = 3;
|
|
10946
|
+
/** Create a CircularProgress component */
|
|
10947
|
+
const CircularProgress = () => {
|
|
10948
|
+
const state = {
|
|
10949
|
+
id: uniqueId(),
|
|
10950
|
+
};
|
|
10951
|
+
/**
|
|
10952
|
+
* Calculate SVG stroke properties for determinate progress
|
|
10953
|
+
*/
|
|
10954
|
+
const calculateStrokeProperties = (size, value, max) => {
|
|
10955
|
+
const radius = (size - STROKE_WIDTH) / 2;
|
|
10956
|
+
const circumference = 2 * Math.PI * radius;
|
|
10957
|
+
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
10958
|
+
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
|
10959
|
+
return {
|
|
10960
|
+
radius,
|
|
10961
|
+
circumference,
|
|
10962
|
+
strokeDashoffset,
|
|
10963
|
+
percentage,
|
|
10964
|
+
};
|
|
10965
|
+
};
|
|
10966
|
+
/**
|
|
10967
|
+
* Get size class name
|
|
10968
|
+
*/
|
|
10969
|
+
const getSizeClass = (size = 'medium') => {
|
|
10970
|
+
return `circular-progress--${size}`;
|
|
10971
|
+
};
|
|
10972
|
+
/**
|
|
10973
|
+
* Get color class name
|
|
10974
|
+
*/
|
|
10975
|
+
const getColorClass = (color, intensity) => {
|
|
10976
|
+
if (!color)
|
|
10977
|
+
return '';
|
|
10978
|
+
return intensity ? `circular-progress--${color} circular-progress--${intensity}` : `circular-progress--${color}`;
|
|
10979
|
+
};
|
|
10980
|
+
return {
|
|
10981
|
+
view: ({ attrs }) => {
|
|
10982
|
+
const { mode = 'indeterminate', value = 0, max = 100, size = 'medium', color = 'teal', colorIntensity, label, showPercentage = false, className = '', style = {}, id = state.id, 'aria-label': ariaLabel, 'aria-valuemin': ariaValueMin = 0, 'aria-valuemax': ariaValueMax = max, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = attrs, params = __rest(attrs, ["mode", "value", "max", "size", "color", "colorIntensity", "label", "showPercentage", "className", "style", "id", 'aria-label', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']);
|
|
10983
|
+
const isDeterminate = mode === 'determinate';
|
|
10984
|
+
const sizePixels = SIZE_MAP[size];
|
|
10985
|
+
const { radius, circumference, strokeDashoffset, percentage } = isDeterminate
|
|
10986
|
+
? calculateStrokeProperties(sizePixels, value, max)
|
|
10987
|
+
: { radius: 0, circumference: 0, strokeDashoffset: 0, percentage: 0 };
|
|
10988
|
+
// Determine label content
|
|
10989
|
+
const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
|
|
10990
|
+
// Build class names
|
|
10991
|
+
const classNames = [
|
|
10992
|
+
'circular-progress',
|
|
10993
|
+
getSizeClass(size),
|
|
10994
|
+
getColorClass(color, colorIntensity),
|
|
10995
|
+
`circular-progress--${mode}`,
|
|
10996
|
+
className,
|
|
10997
|
+
]
|
|
10998
|
+
.filter(Boolean)
|
|
10999
|
+
.join(' ');
|
|
11000
|
+
// ARIA attributes
|
|
11001
|
+
const ariaAttrs = isDeterminate
|
|
11002
|
+
? {
|
|
11003
|
+
'aria-valuenow': ariaValueNow !== undefined ? ariaValueNow : value,
|
|
11004
|
+
'aria-valuemin': ariaValueMin,
|
|
11005
|
+
'aria-valuemax': ariaValueMax,
|
|
11006
|
+
'aria-valuetext': ariaValueText || `${Math.round(percentage)}%`,
|
|
11007
|
+
}
|
|
11008
|
+
: {
|
|
11009
|
+
'aria-valuetext': ariaValueText || label || 'Loading',
|
|
11010
|
+
};
|
|
11011
|
+
return m('.circular-progress', Object.assign(Object.assign(Object.assign({}, params), { className: classNames, style: Object.assign({ width: `${sizePixels}px`, height: `${sizePixels}px` }, style), id, role: 'progressbar', 'aria-label': ariaLabel || (isDeterminate ? `Progress: ${Math.round(percentage)}%` : 'Loading') }), ariaAttrs), [
|
|
11012
|
+
// SVG circle
|
|
11013
|
+
m('svg.circular-progress__svg', {
|
|
11014
|
+
viewBox: `0 0 ${sizePixels} ${sizePixels}`,
|
|
11015
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
11016
|
+
}, [
|
|
11017
|
+
// Background track circle
|
|
11018
|
+
m('circle.circular-progress__circle.circular-progress__circle--track', {
|
|
11019
|
+
cx: sizePixels / 2,
|
|
11020
|
+
cy: sizePixels / 2,
|
|
11021
|
+
r: radius || (sizePixels - STROKE_WIDTH) / 2,
|
|
11022
|
+
fill: 'none',
|
|
11023
|
+
stroke: 'currentColor',
|
|
11024
|
+
'stroke-width': STROKE_WIDTH,
|
|
11025
|
+
}),
|
|
11026
|
+
// Progress indicator circle
|
|
11027
|
+
m('circle.circular-progress__circle.circular-progress__circle--indicator', {
|
|
11028
|
+
cx: sizePixels / 2,
|
|
11029
|
+
cy: sizePixels / 2,
|
|
11030
|
+
r: radius || (sizePixels - STROKE_WIDTH) / 2,
|
|
11031
|
+
fill: 'none',
|
|
11032
|
+
stroke: 'currentColor',
|
|
11033
|
+
'stroke-width': STROKE_WIDTH,
|
|
11034
|
+
'stroke-dasharray': isDeterminate ? circumference : undefined,
|
|
11035
|
+
'stroke-dashoffset': isDeterminate ? strokeDashoffset : undefined,
|
|
11036
|
+
'stroke-linecap': 'round',
|
|
11037
|
+
}),
|
|
11038
|
+
]),
|
|
11039
|
+
// Label inside circle
|
|
11040
|
+
labelContent &&
|
|
11041
|
+
m('.circular-progress__label', {
|
|
11042
|
+
'aria-hidden': 'true',
|
|
11043
|
+
}, labelContent),
|
|
11044
|
+
]);
|
|
11045
|
+
},
|
|
11046
|
+
};
|
|
11047
|
+
};
|
|
11048
|
+
|
|
11049
|
+
/** Create a LinearProgress component */
|
|
11050
|
+
const LinearProgress = () => {
|
|
11051
|
+
const state = {
|
|
11052
|
+
id: uniqueId(),
|
|
11053
|
+
};
|
|
11054
|
+
/**
|
|
11055
|
+
* Get size class name
|
|
11056
|
+
*/
|
|
11057
|
+
const getSizeClass = (size = 'medium') => {
|
|
11058
|
+
return `linear-progress__track--${size}`;
|
|
11059
|
+
};
|
|
11060
|
+
/**
|
|
11061
|
+
* Get color class name
|
|
11062
|
+
*/
|
|
11063
|
+
const getColorClass = (color, intensity) => {
|
|
11064
|
+
if (!color)
|
|
11065
|
+
return '';
|
|
11066
|
+
return intensity ? `linear-progress--${color} linear-progress--${intensity}` : `linear-progress--${color}`;
|
|
11067
|
+
};
|
|
11068
|
+
return {
|
|
11069
|
+
view: ({ attrs }) => {
|
|
11070
|
+
const { mode = 'indeterminate', value = 0, max = 100, size = 'medium', color = 'teal', colorIntensity, label, showPercentage = false, className = '', style = {}, id = state.id, 'aria-label': ariaLabel, 'aria-valuemin': ariaValueMin = 0, 'aria-valuemax': ariaValueMax = max, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = attrs, params = __rest(attrs, ["mode", "value", "max", "size", "color", "colorIntensity", "label", "showPercentage", "className", "style", "id", 'aria-label', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']);
|
|
11071
|
+
const isDeterminate = mode === 'determinate';
|
|
11072
|
+
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
11073
|
+
// Determine label content
|
|
11074
|
+
const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
|
|
11075
|
+
// Build class names
|
|
11076
|
+
const classNames = [
|
|
11077
|
+
'linear-progress',
|
|
11078
|
+
getColorClass(color, colorIntensity),
|
|
11079
|
+
className,
|
|
11080
|
+
]
|
|
11081
|
+
.filter(Boolean)
|
|
11082
|
+
.join(' ');
|
|
11083
|
+
// ARIA attributes
|
|
11084
|
+
const ariaAttrs = isDeterminate
|
|
11085
|
+
? {
|
|
11086
|
+
'aria-valuenow': ariaValueNow !== undefined ? ariaValueNow : value,
|
|
11087
|
+
'aria-valuemin': ariaValueMin,
|
|
11088
|
+
'aria-valuemax': ariaValueMax,
|
|
11089
|
+
'aria-valuetext': ariaValueText || `${Math.round(percentage)}%`,
|
|
11090
|
+
}
|
|
11091
|
+
: {
|
|
11092
|
+
'aria-valuetext': ariaValueText || label || 'Loading',
|
|
11093
|
+
};
|
|
11094
|
+
return m('.linear-progress', Object.assign(Object.assign({}, params), { className: classNames, style,
|
|
11095
|
+
id }), [
|
|
11096
|
+
// Progress track container
|
|
11097
|
+
m('.linear-progress__track', Object.assign({ className: `linear-progress__track ${getSizeClass(size)}`, role: 'progressbar', 'aria-label': ariaLabel || (isDeterminate ? `Progress: ${Math.round(percentage)}%` : 'Loading') }, ariaAttrs), [
|
|
11098
|
+
// Progress bar
|
|
11099
|
+
m('.linear-progress__bar', {
|
|
11100
|
+
className: `linear-progress__bar ${isDeterminate ? '' : 'linear-progress__bar--indeterminate'}`,
|
|
11101
|
+
style: isDeterminate ? { width: `${percentage}%` } : undefined,
|
|
11102
|
+
}),
|
|
11103
|
+
]),
|
|
11104
|
+
// Label at the end (right side)
|
|
11105
|
+
labelContent &&
|
|
11106
|
+
m('.linear-progress__label', {
|
|
11107
|
+
'aria-hidden': 'true',
|
|
11108
|
+
}, labelContent),
|
|
11109
|
+
]);
|
|
11110
|
+
},
|
|
11111
|
+
};
|
|
11112
|
+
};
|
|
11113
|
+
|
|
10838
11114
|
/**
|
|
10839
11115
|
* @fileoverview Core TypeScript utility types for mithril-materialized library
|
|
10840
11116
|
* These types improve type safety and developer experience across all components
|
|
@@ -10859,6 +11135,7 @@
|
|
|
10859
11135
|
exports.AnalogClock = AnalogClock;
|
|
10860
11136
|
exports.AnchorItem = AnchorItem;
|
|
10861
11137
|
exports.Autocomplete = Autocomplete;
|
|
11138
|
+
exports.Badge = Badge;
|
|
10862
11139
|
exports.Breadcrumb = Breadcrumb;
|
|
10863
11140
|
exports.BreadcrumbManager = BreadcrumbManager;
|
|
10864
11141
|
exports.Button = Button;
|
|
@@ -10866,6 +11143,7 @@
|
|
|
10866
11143
|
exports.Carousel = Carousel;
|
|
10867
11144
|
exports.CharacterCounter = CharacterCounter;
|
|
10868
11145
|
exports.Chips = Chips;
|
|
11146
|
+
exports.CircularProgress = CircularProgress;
|
|
10869
11147
|
exports.CodeBlock = CodeBlock;
|
|
10870
11148
|
exports.Collapsible = Collapsible;
|
|
10871
11149
|
exports.CollapsibleItem = CollapsibleItem;
|
|
@@ -10888,6 +11166,7 @@
|
|
|
10888
11166
|
exports.InputCheckbox = InputCheckbox;
|
|
10889
11167
|
exports.Label = Label;
|
|
10890
11168
|
exports.LargeButton = LargeButton;
|
|
11169
|
+
exports.LinearProgress = LinearProgress;
|
|
10891
11170
|
exports.ListItem = ListItem;
|
|
10892
11171
|
exports.Mandatory = Mandatory;
|
|
10893
11172
|
exports.Masonry = Masonry;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FactoryComponent, Attributes } from 'mithril';
|
|
2
|
+
import { MaterialColor, ColorIntensity } from './types';
|
|
3
|
+
import { ProgressMode, ProgressSize } from './circular-progress';
|
|
4
|
+
/** LinearProgress component attributes */
|
|
5
|
+
export interface LinearProgressAttrs extends Attributes {
|
|
6
|
+
/** Progress mode (default: 'indeterminate') */
|
|
7
|
+
mode?: ProgressMode;
|
|
8
|
+
/** Current progress value (0-100) for determinate mode */
|
|
9
|
+
value?: number;
|
|
10
|
+
/** Maximum progress value (default: 100) */
|
|
11
|
+
max?: number;
|
|
12
|
+
/** Size variant (default: 'medium') */
|
|
13
|
+
size?: ProgressSize;
|
|
14
|
+
/** Materialize color (default: 'teal') */
|
|
15
|
+
color?: MaterialColor;
|
|
16
|
+
/** Color intensity modifier */
|
|
17
|
+
colorIntensity?: ColorIntensity;
|
|
18
|
+
/** Label to display at the end (right side) */
|
|
19
|
+
label?: string | number;
|
|
20
|
+
/** Auto-show percentage at the end for determinate mode (default: false) */
|
|
21
|
+
showPercentage?: boolean;
|
|
22
|
+
/** Additional CSS class names */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Additional CSS styles */
|
|
25
|
+
style?: any;
|
|
26
|
+
/** HTML ID for the component */
|
|
27
|
+
id?: string;
|
|
28
|
+
/** ARIA label for accessibility */
|
|
29
|
+
'aria-label'?: string;
|
|
30
|
+
/** ARIA valuemin (default: 0) */
|
|
31
|
+
'aria-valuemin'?: number;
|
|
32
|
+
/** ARIA valuemax (default: 100) */
|
|
33
|
+
'aria-valuemax'?: number;
|
|
34
|
+
/** ARIA valuenow (current value) */
|
|
35
|
+
'aria-valuenow'?: number;
|
|
36
|
+
/** ARIA valuetext (custom text description) */
|
|
37
|
+
'aria-valuetext'?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Create a LinearProgress component */
|
|
40
|
+
export declare const LinearProgress: FactoryComponent<LinearProgressAttrs>;
|
package/package.json
CHANGED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
@use 'sass:color';
|
|
2
|
+
@use "variables";
|
|
3
|
+
@use "color-variables";
|
|
4
|
+
|
|
5
|
+
/* Badge Component
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
// Badge component variables
|
|
9
|
+
$badge-size-standard: 20px !default;
|
|
10
|
+
$badge-size-dot: 8px !default;
|
|
11
|
+
$badge-font-size: 12px !default;
|
|
12
|
+
$badge-font-weight: 500 !default;
|
|
13
|
+
$badge-default-color: color-variables.color("red", "base") !default;
|
|
14
|
+
$badge-text-color: #fff !default;
|
|
15
|
+
$badge-border-radius: 10px !default;
|
|
16
|
+
$badge-dot-border-radius: 50% !default;
|
|
17
|
+
|
|
18
|
+
// Wrapper - relative positioning container
|
|
19
|
+
.badge-wrapper {
|
|
20
|
+
position: relative;
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
vertical-align: middle;
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Badge element - using .m-badge to avoid conflicts with Materialize's span.badge
|
|
27
|
+
.m-badge {
|
|
28
|
+
position: absolute;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
font-family: Roboto, sans-serif;
|
|
34
|
+
font-weight: $badge-font-weight;
|
|
35
|
+
line-height: 1;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
text-align: center;
|
|
38
|
+
border-radius: $badge-border-radius;
|
|
39
|
+
background-color: $badge-default-color;
|
|
40
|
+
color: $badge-text-color;
|
|
41
|
+
z-index: 1;
|
|
42
|
+
transition: transform 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
43
|
+
|
|
44
|
+
// Standard variant (shows content)
|
|
45
|
+
&.m-badge--standard {
|
|
46
|
+
min-width: $badge-size-standard;
|
|
47
|
+
height: $badge-size-standard;
|
|
48
|
+
padding: 0 6px;
|
|
49
|
+
font-size: $badge-font-size;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Dot variant (minimal indicator)
|
|
53
|
+
&.m-badge--dot {
|
|
54
|
+
width: $badge-size-dot;
|
|
55
|
+
height: $badge-size-dot;
|
|
56
|
+
min-width: $badge-size-dot;
|
|
57
|
+
padding: 0;
|
|
58
|
+
border-radius: $badge-dot-border-radius;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Invisible state
|
|
62
|
+
&.m-badge--invisible {
|
|
63
|
+
transform: scale(0);
|
|
64
|
+
opacity: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// === POSITIONING: Top-Right (default) ===
|
|
68
|
+
&.m-badge--top-right {
|
|
69
|
+
top: 0;
|
|
70
|
+
right: 0;
|
|
71
|
+
transform: scale(1) translate(50%, -50%);
|
|
72
|
+
transform-origin: 100% 0%;
|
|
73
|
+
|
|
74
|
+
&.m-badge--rectangular {
|
|
75
|
+
transform: scale(1) translate(50%, -50%);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&.m-badge--circular {
|
|
79
|
+
transform: scale(1) translate(30%, -30%);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&.m-badge--invisible {
|
|
83
|
+
transform: scale(0) translate(50%, -50%);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// === POSITIONING: Top-Left ===
|
|
88
|
+
&.m-badge--top-left {
|
|
89
|
+
top: 0;
|
|
90
|
+
left: 0;
|
|
91
|
+
transform: scale(1) translate(-50%, -50%);
|
|
92
|
+
transform-origin: 0% 0%;
|
|
93
|
+
|
|
94
|
+
&.m-badge--rectangular {
|
|
95
|
+
transform: scale(1) translate(-50%, -50%);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&.m-badge--circular {
|
|
99
|
+
transform: scale(1) translate(-30%, -30%);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&.m-badge--invisible {
|
|
103
|
+
transform: scale(0) translate(-50%, -50%);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// === POSITIONING: Bottom-Right ===
|
|
108
|
+
&.m-badge--bottom-right {
|
|
109
|
+
bottom: 0;
|
|
110
|
+
right: 0;
|
|
111
|
+
transform: scale(1) translate(50%, 50%);
|
|
112
|
+
transform-origin: 100% 100%;
|
|
113
|
+
|
|
114
|
+
&.m-badge--rectangular {
|
|
115
|
+
transform: scale(1) translate(50%, 50%);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&.m-badge--circular {
|
|
119
|
+
transform: scale(1) translate(30%, 30%);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&.m-badge--invisible {
|
|
123
|
+
transform: scale(0) translate(50%, 50%);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// === POSITIONING: Bottom-Left ===
|
|
128
|
+
&.m-badge--bottom-left {
|
|
129
|
+
bottom: 0;
|
|
130
|
+
left: 0;
|
|
131
|
+
transform: scale(1) translate(-50%, 50%);
|
|
132
|
+
transform-origin: 0% 100%;
|
|
133
|
+
|
|
134
|
+
&.m-badge--rectangular {
|
|
135
|
+
transform: scale(1) translate(-50%, 50%);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&.m-badge--circular {
|
|
139
|
+
transform: scale(1) translate(-30%, 30%);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&.m-badge--invisible {
|
|
143
|
+
transform: scale(0) translate(-50%, 50%);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// === COLOR VARIANTS ===
|
|
149
|
+
// Generate color classes for all MaterialColor options
|
|
150
|
+
.m-badge--red { background-color: color-variables.color("red", "base"); }
|
|
151
|
+
.m-badge--pink { background-color: color-variables.color("pink", "base"); }
|
|
152
|
+
.m-badge--purple { background-color: color-variables.color("purple", "base"); }
|
|
153
|
+
.m-badge--deep-purple { background-color: color-variables.color("deep-purple", "base"); }
|
|
154
|
+
.m-badge--indigo { background-color: color-variables.color("indigo", "base"); }
|
|
155
|
+
.m-badge--blue { background-color: color-variables.color("blue", "base"); }
|
|
156
|
+
.m-badge--light-blue { background-color: color-variables.color("light-blue", "base"); }
|
|
157
|
+
.m-badge--cyan { background-color: color-variables.color("cyan", "base"); }
|
|
158
|
+
.m-badge--teal { background-color: color-variables.color("teal", "base"); }
|
|
159
|
+
.m-badge--green { background-color: color-variables.color("green", "base"); }
|
|
160
|
+
.m-badge--light-green { background-color: color-variables.color("light-green", "base"); }
|
|
161
|
+
.m-badge--lime { background-color: color-variables.color("lime", "base"); }
|
|
162
|
+
.m-badge--yellow {
|
|
163
|
+
background-color: color-variables.color("yellow", "base");
|
|
164
|
+
color: #000; // Dark text for light background
|
|
165
|
+
}
|
|
166
|
+
.m-badge--amber { background-color: color-variables.color("amber", "base"); }
|
|
167
|
+
.m-badge--orange { background-color: color-variables.color("orange", "base"); }
|
|
168
|
+
.m-badge--deep-orange { background-color: color-variables.color("deep-orange", "base"); }
|
|
169
|
+
.m-badge--brown { background-color: color-variables.color("brown", "base"); }
|
|
170
|
+
.m-badge--grey { background-color: color-variables.color("grey", "base"); }
|
|
171
|
+
.m-badge--blue-grey { background-color: color-variables.color("blue-grey", "base"); }
|
|
172
|
+
|
|
173
|
+
// === COLOR INTENSITY MODIFIERS ===
|
|
174
|
+
// Use CSS filters for intensity adjustments (matching CircularProgress pattern)
|
|
175
|
+
.m-badge--lighten-5 { filter: brightness(1.4); }
|
|
176
|
+
.m-badge--lighten-4 { filter: brightness(1.3); }
|
|
177
|
+
.m-badge--lighten-3 { filter: brightness(1.2); }
|
|
178
|
+
.m-badge--lighten-2 { filter: brightness(1.1); }
|
|
179
|
+
.m-badge--lighten-1 { filter: brightness(1.05); }
|
|
180
|
+
.m-badge--darken-1 { filter: brightness(0.95); }
|
|
181
|
+
.m-badge--darken-2 { filter: brightness(0.9); }
|
|
182
|
+
.m-badge--darken-3 { filter: brightness(0.8); }
|
|
183
|
+
.m-badge--darken-4 { filter: brightness(0.7); }
|
|
184
|
+
|
|
185
|
+
// === ACCESSIBILITY ===
|
|
186
|
+
// Ensure badge is not announced multiple times by screen readers
|
|
187
|
+
.m-badge[role="status"] {
|
|
188
|
+
// Screen reader will announce changes due to aria-live="polite"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// === REDUCED MOTION ===
|
|
192
|
+
@media (prefers-reduced-motion: reduce) {
|
|
193
|
+
.m-badge {
|
|
194
|
+
transition: none;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// === HIGH CONTRAST MODE ===
|
|
199
|
+
@media (prefers-contrast: high) {
|
|
200
|
+
.m-badge {
|
|
201
|
+
border: 2px solid currentColor;
|
|
202
|
+
}
|
|
203
|
+
}
|