canny-ui 1.0.0 → 2.0.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.
@@ -127,7 +127,7 @@
127
127
  .cny-navbar-actions {
128
128
  display: flex;
129
129
  align-items: center;
130
- gap: $space-2;
130
+ gap: 6px;
131
131
  margin-left: auto;
132
132
  flex-shrink: 0;
133
133
  }
@@ -135,8 +135,8 @@
135
135
  .cny-navbar-divider {
136
136
  width: 1px;
137
137
  height: 20px;
138
- background: var(--hairline-color);
139
- margin: 0 $space-1;
138
+ background: linear-gradient(180deg, transparent, var(--hairline-strong, var(--hairline-color)), transparent);
139
+ margin: 0 4px;
140
140
  flex-shrink: 0;
141
141
  }
142
142
 
@@ -31,7 +31,7 @@
31
31
  }
32
32
 
33
33
  .cny-sidebar-section {
34
- margin-bottom: $space-6;
34
+ margin-bottom: $space-5;
35
35
 
36
36
  &:last-child {
37
37
  margin-bottom: 0;
@@ -39,13 +39,26 @@
39
39
  }
40
40
 
41
41
  .cny-sidebar-title {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 8px;
42
45
  font-size: 0.6875rem;
43
46
  font-weight: 650;
44
- letter-spacing: 0.08em;
47
+ letter-spacing: 0.1em;
45
48
  text-transform: uppercase;
46
49
  color: var(--text-muted);
47
- padding: 0 $space-2;
48
- margin: 0 0 $space-3;
50
+ padding: 0 10px;
51
+ margin: 0 0 10px;
52
+
53
+ &::before {
54
+ content: '';
55
+ width: 3px;
56
+ height: 3px;
57
+ border-radius: 50%;
58
+ background: currentColor;
59
+ opacity: 0.55;
60
+ flex-shrink: 0;
61
+ }
49
62
  }
50
63
 
51
64
  .cny-sidebar-nav {
@@ -58,34 +71,44 @@
58
71
  display: flex;
59
72
  align-items: center;
60
73
  gap: $space-2;
61
- padding: 6px $space-2;
74
+ padding: 7px 10px;
62
75
  border-radius: var(--radius-sm);
63
- font-size: 0.84375rem;
76
+ font-size: 0.8125rem;
64
77
  color: var(--text-secondary);
65
78
  text-decoration: none;
66
79
  position: relative;
67
- transition: background-color var(--transition-fast), color var(--transition-fast);
80
+ transition:
81
+ background-color 160ms ease,
82
+ color 160ms ease,
83
+ transform 160ms ease;
68
84
 
69
85
  &:hover {
70
86
  background: var(--bg-surface-low);
71
87
  color: var(--text-primary);
72
88
  }
73
89
 
90
+ &:active { transform: translateX(0.5px); }
91
+
74
92
  &.active {
75
93
  color: var(--color-primary);
76
94
  font-weight: 550;
77
- background: color-mix(in srgb, var(--color-primary) 8%, transparent);
95
+ background: linear-gradient(
96
+ 90deg,
97
+ color-mix(in srgb, var(--color-primary) 13%, transparent),
98
+ color-mix(in srgb, var(--color-primary) 4%, transparent)
99
+ );
78
100
 
79
101
  &::before {
80
102
  content: '';
81
103
  position: absolute;
82
- left: calc(-1 * #{$space-5});
104
+ left: 0;
83
105
  top: 50%;
84
106
  transform: translateY(-50%);
85
- width: 2px;
86
- height: 16px;
107
+ width: 3px;
108
+ height: 14px;
87
109
  background: var(--color-primary);
88
110
  border-radius: 0 2px 2px 0;
111
+ box-shadow: 0 0 8px color-mix(in srgb, var(--color-primary) 50%, transparent);
89
112
  }
90
113
  }
91
114
 
@@ -104,6 +127,28 @@
104
127
 
105
128
  .cny-sidebar-item-badge {
106
129
  margin-left: auto;
130
+ font-size: 9.5px;
131
+ font-weight: 700;
132
+ letter-spacing: 0.04em;
133
+ padding: 1px 6px;
134
+ line-height: 1.4;
135
+ text-transform: lowercase;
136
+ }
137
+ }
138
+
139
+ // Themed scrollbar for sidebar content
140
+ .cny-sidebar {
141
+ scrollbar-width: thin;
142
+ scrollbar-color: var(--border-color) transparent;
143
+
144
+ &::-webkit-scrollbar { width: 6px; }
145
+ &::-webkit-scrollbar-track { background: transparent; }
146
+ &::-webkit-scrollbar-thumb {
147
+ background: var(--border-color);
148
+ border-radius: 3px;
149
+ }
150
+ &::-webkit-scrollbar-thumb:hover {
151
+ background: var(--border-color-strong);
107
152
  }
108
153
  }
109
154
 
@@ -0,0 +1,271 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Toast / Snackbar — clean white card with a wavy SVG decoration peeking
4
+ // from the left edge in the variant accent color. Body stays neutral
5
+ // for readability; variant signal lives in the wave + icon chip + title.
6
+
7
+ @keyframes cny-toast-in-right {
8
+ from { opacity: 0; transform: translate(8px, 0); }
9
+ to { opacity: 1; transform: translate(0, 0); }
10
+ }
11
+ @keyframes cny-toast-in-left {
12
+ from { opacity: 0; transform: translate(-8px, 0); }
13
+ to { opacity: 1; transform: translate(0, 0); }
14
+ }
15
+ @keyframes cny-toast-in-top {
16
+ from { opacity: 0; transform: translate(0, -8px); }
17
+ to { opacity: 1; transform: translate(0, 0); }
18
+ }
19
+ @keyframes cny-toast-in-bottom {
20
+ from { opacity: 0; transform: translate(0, 10px) scale(0.985); }
21
+ to { opacity: 1; transform: translate(0, 0) scale(1); }
22
+ }
23
+ @keyframes cny-toast-out {
24
+ from { opacity: 1; transform: translate(0, 0); }
25
+ to { opacity: 0; transform: translate(0, -6px) scale(0.97); }
26
+ }
27
+ @keyframes cny-toast-spin {
28
+ to { transform: rotate(360deg); }
29
+ }
30
+
31
+ // ── Viewport ────────────────────────────────────────────────────
32
+ .cny-toast-viewport {
33
+ --cny-toast-offset: 1rem;
34
+ position: fixed;
35
+ z-index: 1100;
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 12px;
39
+ padding: 0;
40
+ margin: 0;
41
+ list-style: none;
42
+ pointer-events: none;
43
+ width: min(360px, calc(100vw - var(--cny-toast-offset) * 2));
44
+
45
+ > * { pointer-events: auto; }
46
+
47
+ &.pos-bottom-right { right: var(--cny-toast-offset); bottom: var(--cny-toast-offset); align-items: flex-end; }
48
+ &.pos-bottom-left { left: var(--cny-toast-offset); bottom: var(--cny-toast-offset); align-items: flex-start; }
49
+ &.pos-bottom-center {
50
+ left: 50%; bottom: var(--cny-toast-offset);
51
+ transform: translateX(-50%); align-items: center;
52
+ }
53
+ &.pos-top-right { right: var(--cny-toast-offset); top: var(--cny-toast-offset); align-items: flex-end; }
54
+ &.pos-top-left { left: var(--cny-toast-offset); top: var(--cny-toast-offset); align-items: flex-start; }
55
+ &.pos-top-center {
56
+ left: 50%; top: var(--cny-toast-offset);
57
+ transform: translateX(-50%); align-items: center;
58
+ }
59
+ }
60
+
61
+ // ── Toast surface — flat white card ─────────────────────────────
62
+ .cny-toast {
63
+ --toast-accent: var(--color-primary);
64
+
65
+ position: relative;
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 14px;
69
+ width: min(360px, 100%);
70
+ // 80 px = one full wave cycle. Tall toasts repeat the wave, so this is
71
+ // a floor, not a clamp.
72
+ min-height: 80px;
73
+ padding: 14px 16px 14px 28px; // extra left to clear the tiled wave
74
+ background: var(--bg-surface-lowest);
75
+ color: var(--text-primary);
76
+ border: 1px solid color-mix(in srgb, var(--border-color) 60%, transparent);
77
+ border-radius: 10px;
78
+ box-shadow:
79
+ 0 1px 2px rgba(15, 23, 42, 0.04),
80
+ 0 8px 24px rgba(149, 157, 165, 0.18);
81
+ font-family: var(--font-body);
82
+ font-size: 0.875rem;
83
+ line-height: 1.5;
84
+ overflow: hidden;
85
+ pointer-events: auto;
86
+ transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1),
87
+ box-shadow 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
88
+ animation: cny-toast-in-bottom 220ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
89
+
90
+ &:hover {
91
+ transform: translateY(-2px);
92
+ box-shadow:
93
+ 0 1px 2px rgba(15, 23, 42, 0.04),
94
+ 0 14px 32px rgba(149, 157, 165, 0.24);
95
+ }
96
+
97
+ // Direction-aware entrance
98
+ .cny-toast-viewport.pos-bottom-right &,
99
+ .cny-toast-viewport.pos-top-right & { animation-name: cny-toast-in-right; }
100
+ .cny-toast-viewport.pos-bottom-left &,
101
+ .cny-toast-viewport.pos-top-left & { animation-name: cny-toast-in-left; }
102
+ .cny-toast-viewport.pos-top-center &,
103
+ .cny-toast-viewport.pos-top-right &,
104
+ .cny-toast-viewport.pos-top-left & { animation-name: cny-toast-in-top; }
105
+
106
+ &.is-exiting {
107
+ animation: cny-toast-out 200ms cubic-bezier(0.4, 0, 1, 1) both;
108
+ }
109
+
110
+ @media (prefers-reduced-motion: reduce) {
111
+ animation: none;
112
+ &.is-exiting { animation: none; opacity: 0; }
113
+ }
114
+
115
+ // ── Variants — only --toast-accent shifts ──
116
+ &.success { --toast-accent: var(--color-success); }
117
+ &.info { --toast-accent: var(--color-info); }
118
+ &.warning { --toast-accent: var(--color-warning); }
119
+ &.danger { --toast-accent: var(--color-danger); }
120
+ &.loading { --toast-accent: var(--color-primary); }
121
+ }
122
+
123
+ // ── Wave decoration on the left edge ─────────────────────────────
124
+ // Seamless vertical tile: full width at y=0 and y=80, single cubic dip in
125
+ // the middle. Both tile boundaries match at the right edge (x=80) so
126
+ // repeat-y has no discontinuity even on tall toasts. Path is filled (left
127
+ // half) for the colored strip, with the wave shape forming the inner edge.
128
+ .cny-toast::before {
129
+ content: '';
130
+ position: absolute;
131
+ left: 0;
132
+ top: 0;
133
+ bottom: 0;
134
+ width: 18px;
135
+ background-color: color-mix(in srgb, var(--toast-accent) 23%, transparent);
136
+ mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' preserveAspectRatio='none'><path d='M0,0 L80,0 C20,20 20,60 80,80 L0,80 Z'/></svg>");
137
+ -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' preserveAspectRatio='none'><path d='M0,0 L80,0 C20,20 20,60 80,80 L0,80 Z'/></svg>");
138
+ mask-repeat: repeat-y;
139
+ -webkit-mask-repeat: repeat-y;
140
+ mask-size: 18px 80px;
141
+ -webkit-mask-size: 18px 80px;
142
+ pointer-events: none;
143
+ }
144
+
145
+ // ── Icon chip — circular, filled tonal ──
146
+ .cny-toast-icon {
147
+ flex: none;
148
+ display: inline-flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ width: 36px;
152
+ height: 36px;
153
+ border-radius: 50%;
154
+ background: color-mix(in srgb, var(--toast-accent) 26%, transparent);
155
+ color: var(--toast-accent);
156
+ position: relative;
157
+ z-index: 1;
158
+
159
+ > svg {
160
+ width: 18px;
161
+ height: 18px;
162
+ }
163
+ }
164
+
165
+ .cny-toast-spin { animation: cny-toast-spin 1s linear infinite; }
166
+ @media (prefers-reduced-motion: reduce) { .cny-toast-spin { animation: none; } }
167
+
168
+ // ── Body ───────────────────────────────────────────────────────
169
+ .cny-toast-body {
170
+ flex: 1 1 auto;
171
+ min-width: 0;
172
+ display: flex;
173
+ flex-direction: column;
174
+ justify-content: center;
175
+ align-items: center;
176
+ text-align: center;
177
+ position: relative;
178
+ z-index: 1;
179
+ }
180
+
181
+ .cny-toast-title {
182
+ font-weight: 700;
183
+ letter-spacing: -0.01em;
184
+ line-height: 1.25;
185
+ margin: 0;
186
+ color: var(--toast-accent);
187
+ font-size: 16px;
188
+ text-align: center;
189
+ cursor: default;
190
+ }
191
+
192
+ .cny-toast-description {
193
+ margin: 4px 0 0;
194
+ color: var(--text-muted);
195
+ font-size: 13.5px;
196
+ line-height: 1.45;
197
+ text-align: center;
198
+ cursor: default;
199
+ }
200
+
201
+ // ── Action slot ────────────────────────────────────────────────
202
+ .cny-toast-action {
203
+ flex: none;
204
+ display: inline-flex;
205
+ align-items: center;
206
+ align-self: center;
207
+ position: relative;
208
+ z-index: 1;
209
+ }
210
+
211
+ // ── Close button ───────────────────────────────────────────────
212
+ .cny-toast-close {
213
+ flex: none;
214
+ display: inline-flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ width: 26px;
218
+ height: 26px;
219
+ padding: 0;
220
+ background: transparent;
221
+ border: 0;
222
+ color: var(--text-muted);
223
+ cursor: pointer;
224
+ border-radius: var(--radius-sm);
225
+ transition: color 140ms ease, background-color 140ms ease, transform 140ms ease;
226
+ position: relative;
227
+ z-index: 1;
228
+
229
+ > svg { width: 16px; height: 16px; }
230
+
231
+ &:hover {
232
+ color: var(--text-primary);
233
+ background: color-mix(in srgb, var(--toast-accent) 8%, var(--bg-surface-low));
234
+ transform: scale(1.04);
235
+ }
236
+ &:active {
237
+ transform: scale(0.96);
238
+ }
239
+ &:focus-visible {
240
+ outline: 2px solid var(--toast-accent);
241
+ outline-offset: 2px;
242
+ }
243
+ }
244
+
245
+ // ── Mobile ─────────────────────────────────────────────────────
246
+ @media (max-width: 480px) {
247
+ .cny-toast-viewport {
248
+ --cny-toast-offset: 12px;
249
+ width: calc(100vw - var(--cny-toast-offset) * 2);
250
+ }
251
+ .cny-toast {
252
+ width: 100%;
253
+ min-height: 64px;
254
+ padding: 12px 14px 12px 26px;
255
+ gap: 12px;
256
+ border-radius: 9px;
257
+ }
258
+ .cny-toast::before {
259
+ width: 14px;
260
+ mask-size: 14px 64px;
261
+ -webkit-mask-size: 14px 64px;
262
+ }
263
+ .cny-toast-icon {
264
+ width: 32px;
265
+ height: 32px;
266
+ > svg { width: 16px; height: 16px; }
267
+ }
268
+ .cny-toast-title { font-size: 15px; }
269
+ .cny-toast-description { font-size: 12.5px; }
270
+ .cny-toast-close { width: 24px; height: 24px; > svg { width: 14px; height: 14px; } }
271
+ }
@@ -112,8 +112,10 @@
112
112
  }
113
113
 
114
114
  // ── Dark theme ──────────────────────────────────────────────────────────
115
+ // Auto-dark when the OS prefers dark, UNLESS .cny-light is explicitly set.
116
+ // Manual toggle classes (.cny-dark / .cny-light) always win over system preference.
115
117
  @media (prefers-color-scheme: dark) {
116
- :root {
118
+ :root:not(.cny-light) {
117
119
  --bg-base: #{$zinc-950};
118
120
  --bg-surface-low: #{$zinc-900};
119
121
  --bg-surface-lowest: #{$zinc-800};