@mandujs/core 0.18.15 → 0.18.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.18.15",
3
+ "version": "0.18.16",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -85,6 +85,25 @@ const baseStyles = `
85
85
  .mk-badge-container.panel-open {
86
86
  opacity: 0;
87
87
  pointer-events: none;
88
+ transform: scale(0.8);
89
+ }
90
+
91
+ @keyframes mk-badge-breathe {
92
+ 0%, 100% { transform: scale(1) translateY(0px); }
93
+ 50% { transform: scale(1.04) translateY(0px); }
94
+ }
95
+
96
+ @keyframes mk-badge-attention {
97
+ 0%, 100% { transform: scale(1) translateY(0px); }
98
+ 15% { transform: scale(1.08) translateY(0px); }
99
+ 30% { transform: scale(0.98) translateY(0px); }
100
+ 45% { transform: scale(1.04) translateY(0px); }
101
+ 60% { transform: scale(1) translateY(0px); }
102
+ }
103
+
104
+ @keyframes mk-badge-float {
105
+ 0%, 100% { transform: scale(1) translateY(0px); }
106
+ 50% { transform: scale(1) translateY(-3px); }
88
107
  }
89
108
  `;
90
109
 
@@ -203,37 +203,78 @@ export function ManduBadge({
203
203
  const character = MANDU_CHARACTERS[state];
204
204
  const stateColor = stateColors[state];
205
205
  const [isHovered, setIsHovered] = React.useState(false);
206
+ const [isPressed, setIsPressed] = React.useState(false);
206
207
 
207
208
  const badgeStyle: React.CSSProperties = {
209
+ position: 'relative',
208
210
  display: 'flex',
209
211
  alignItems: 'center',
210
212
  justifyContent: 'center',
211
- gap: '8px',
212
- padding: count > 0 ? '10px 16px' : '0',
213
- width: count > 0 ? 'auto' : '48px',
214
- height: '48px',
213
+ width: '52px',
214
+ height: '52px',
215
+ padding: 0,
215
216
  borderRadius: '9999px',
216
- backgroundColor: colors.background.dark,
217
- border: `2px solid ${isHovered ? colors.brand.accent : stateColor}`,
217
+ backgroundColor: isPressed
218
+ ? colors.background.light
219
+ : isHovered
220
+ ? colors.background.medium
221
+ : colors.background.dark,
222
+ border: `2px solid ${isPressed ? stateColor : isHovered ? colors.brand.accent : stateColor + '70'}`,
218
223
  cursor: 'pointer',
219
- transition: `all ${animation.duration.normal} ${animation.easing.spring}`,
220
- boxShadow: isHovered
221
- ? `0 8px 24px rgba(8, 6, 18, 0.4), 0 0 0 4px ${stateColor}33`
222
- : `0 4px 12px rgba(8, 6, 18, 0.3)`,
223
- fontSize: '22px',
224
+ transition: `all 200ms ${animation.easing.spring}`,
225
+ boxShadow: isPressed
226
+ ? `0 2px 8px rgba(8, 6, 18, 0.4), inset 0 2px 4px rgba(0, 0, 0, 0.15)`
227
+ : isHovered
228
+ ? `0 8px 32px rgba(8, 6, 18, 0.45), 0 0 20px ${stateColor}55, 0 0 0 3px ${stateColor}20`
229
+ : `0 4px 16px rgba(8, 6, 18, 0.35), 0 0 10px ${stateColor}30`,
224
230
  userSelect: 'none',
225
- transform: isHovered ? 'scale(1.08)' : 'scale(1)',
231
+ transform: isPressed
232
+ ? 'scale(0.92) translateY(1px)'
233
+ : isHovered
234
+ ? 'scale(1.1) translateY(-2px)'
235
+ : 'scale(1) translateY(0px)',
226
236
  outline: 'none',
227
237
  lineHeight: 1,
238
+ animation: isHovered || isPressed
239
+ ? 'none'
240
+ : state === 'normal'
241
+ ? 'mk-badge-breathe 3s ease-in-out infinite'
242
+ : state === 'error' && count > 0
243
+ ? 'mk-badge-attention 2s ease-in-out infinite'
244
+ : state === 'loading'
245
+ ? 'mk-badge-float 2s ease-in-out infinite'
246
+ : 'none',
228
247
  };
229
248
 
230
- const countStyle: React.CSSProperties = {
231
- fontSize: '13px',
249
+ const emojiStyle: React.CSSProperties = {
250
+ fontFamily: "'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif",
251
+ color: colors.text.primary,
252
+ fontSize: '24px',
253
+ lineHeight: 1,
254
+ transition: `transform 200ms ${animation.easing.spring}`,
255
+ transform: isHovered ? 'rotate(-8deg)' : 'rotate(0deg)',
256
+ };
257
+
258
+ const countBubbleStyle: React.CSSProperties = {
259
+ position: 'absolute',
260
+ top: '-4px',
261
+ right: '-4px',
262
+ minWidth: '20px',
263
+ height: '20px',
264
+ padding: '0 5px',
265
+ borderRadius: '9999px',
266
+ backgroundColor: stateColor,
267
+ color: colors.background.dark,
268
+ fontSize: '11px',
232
269
  fontWeight: 700,
233
- color: stateColor,
234
- minWidth: '18px',
235
- textAlign: 'center',
270
+ display: 'flex',
271
+ alignItems: 'center',
272
+ justifyContent: 'center',
236
273
  lineHeight: 1,
274
+ boxShadow: `0 2px 8px ${stateColor}66`,
275
+ border: `2px solid ${colors.background.dark}`,
276
+ transition: `all 200ms ${animation.easing.spring}`,
277
+ transform: isHovered ? 'scale(1.15)' : 'scale(1)',
237
278
  };
238
279
 
239
280
  return (
@@ -242,16 +283,19 @@ export function ManduBadge({
242
283
  style={badgeStyle}
243
284
  onClick={onClick}
244
285
  onMouseEnter={() => setIsHovered(true)}
245
- onMouseLeave={() => setIsHovered(false)}
286
+ onMouseLeave={() => { setIsHovered(false); setIsPressed(false); }}
287
+ onMouseDown={() => setIsPressed(true)}
288
+ onMouseUp={() => setIsPressed(false)}
246
289
  onFocus={() => setIsHovered(true)}
247
- onBlur={() => setIsHovered(false)}
290
+ onBlur={() => { setIsHovered(false); setIsPressed(false); }}
248
291
  aria-label={`Mandu Kitchen: ${character.message}${count > 0 ? `, ${count} issues` : ''}`}
249
292
  >
250
- <span
251
- aria-hidden="true"
252
- style={{ fontFamily: "'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif" }}
253
- >🥟</span>
254
- {count > 0 && <span style={countStyle}>{count}</span>}
293
+ <span aria-hidden="true" style={emojiStyle}>🥟</span>
294
+ {count > 0 && (
295
+ <span style={countBubbleStyle}>
296
+ {count > 99 ? '99+' : count}
297
+ </span>
298
+ )}
255
299
  </button>
256
300
  );
257
301
  }