canny-ui 1.0.0 → 3.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.
@@ -0,0 +1,133 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Drawer / Sheet — slide-in panel from any edge.
4
+
5
+ @keyframes cny-drawer-overlay-in { from { opacity: 0; } to { opacity: 1; } }
6
+ @keyframes cny-drawer-in-right { from { transform: translateX(100%); } to { transform: translateX(0); } }
7
+ @keyframes cny-drawer-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }
8
+ @keyframes cny-drawer-in-top { from { transform: translateY(-100%); } to { transform: translateY(0); } }
9
+ @keyframes cny-drawer-in-bottom { from { transform: translateY(100%); } to { transform: translateY(0); } }
10
+
11
+ .cny-drawer-overlay {
12
+ position: fixed;
13
+ inset: 0;
14
+ background: rgba(9, 9, 11, 0.55);
15
+ z-index: 1000;
16
+ backdrop-filter: blur(2px);
17
+ -webkit-backdrop-filter: blur(2px);
18
+ animation: cny-drawer-overlay-in 160ms ease-out;
19
+ }
20
+ .cny-dark .cny-drawer-overlay { background: rgba(0, 0, 0, 0.7); }
21
+
22
+ .cny-drawer-content {
23
+ position: fixed;
24
+ z-index: 1001;
25
+ display: flex;
26
+ flex-direction: column;
27
+ background: var(--bg-surface-lowest);
28
+ border: 1px solid var(--border-color);
29
+ box-shadow: 0 24px 60px -24px rgba(0, 0, 0, 0.45);
30
+ font-family: var(--font-body);
31
+ color: var(--text-primary);
32
+ outline: none;
33
+
34
+ // ── Side anchoring ──
35
+ &.side-right {
36
+ top: 0; right: 0; bottom: 0;
37
+ border-left: 1px solid var(--border-color);
38
+ animation: cny-drawer-in-right 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
39
+ &.size-sm { width: min(320px, 90vw); }
40
+ &.size-md { width: min(420px, 90vw); }
41
+ &.size-lg { width: min(560px, 92vw); }
42
+ &.size-xl { width: min(720px, 96vw); }
43
+ }
44
+ &.side-left {
45
+ top: 0; left: 0; bottom: 0;
46
+ border-right: 1px solid var(--border-color);
47
+ animation: cny-drawer-in-left 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
48
+ &.size-sm { width: min(320px, 90vw); }
49
+ &.size-md { width: min(420px, 90vw); }
50
+ &.size-lg { width: min(560px, 92vw); }
51
+ &.size-xl { width: min(720px, 96vw); }
52
+ }
53
+ &.side-top {
54
+ top: 0; left: 0; right: 0;
55
+ border-bottom: 1px solid var(--border-color);
56
+ animation: cny-drawer-in-top 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
57
+ &.size-sm { height: min(40vh, 320px); }
58
+ &.size-md { height: min(50vh, 480px); }
59
+ &.size-lg { height: min(60vh, 640px); }
60
+ &.size-xl { height: min(80vh, 800px); }
61
+ }
62
+ &.side-bottom {
63
+ bottom: 0; left: 0; right: 0;
64
+ border-top: 1px solid var(--border-color);
65
+ animation: cny-drawer-in-bottom 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
66
+ &.size-sm { height: min(40vh, 320px); }
67
+ &.size-md { height: min(50vh, 480px); }
68
+ &.size-lg { height: min(60vh, 640px); }
69
+ &.size-xl { height: min(80vh, 800px); }
70
+ }
71
+ }
72
+
73
+ .cny-drawer-header {
74
+ display: flex;
75
+ align-items: flex-start;
76
+ gap: 12px;
77
+ padding: 18px 20px 14px;
78
+ border-bottom: 1px solid var(--hairline-color);
79
+ }
80
+
81
+ .cny-drawer-titles { flex: 1 1 auto; min-width: 0; }
82
+ .cny-drawer-title {
83
+ margin: 0;
84
+ font-size: 1rem;
85
+ font-weight: 650;
86
+ letter-spacing: -0.005em;
87
+ color: var(--text-primary);
88
+ }
89
+ .cny-drawer-description {
90
+ margin: 4px 0 0;
91
+ font-size: 0.875rem;
92
+ line-height: 1.55;
93
+ color: var(--text-secondary);
94
+ }
95
+
96
+ .cny-drawer-close {
97
+ flex: none;
98
+ display: inline-flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ width: 28px;
102
+ height: 28px;
103
+ background: transparent;
104
+ border: 0;
105
+ color: var(--text-muted);
106
+ cursor: pointer;
107
+ border-radius: var(--radius-xs);
108
+ transition: color 120ms ease, background-color 120ms ease;
109
+
110
+ &:hover { color: var(--text-primary); background: var(--bg-surface-low); }
111
+ &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 1px; }
112
+ }
113
+
114
+ .cny-drawer-body {
115
+ flex: 1 1 auto;
116
+ overflow-y: auto;
117
+ padding: 18px 20px;
118
+ color: var(--text-secondary);
119
+ }
120
+
121
+ .cny-drawer-footer {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: flex-end;
125
+ gap: 8px;
126
+ padding: 14px 20px;
127
+ border-top: 1px solid var(--hairline-color);
128
+ }
129
+
130
+ @media (prefers-reduced-motion: reduce) {
131
+ .cny-drawer-overlay,
132
+ .cny-drawer-content { animation: none; }
133
+ }
@@ -4,11 +4,19 @@
4
4
  // All use the native element with class-based styling (no JS required).
5
5
 
6
6
  // ── Checkbox & Radio ─────────────────────────────────────────────────────
7
+ // Two markup patterns supported per element:
8
+ // 1. Legacy native input — <input type="checkbox|radio" class="checkbox|radio">
9
+ // uses :checked / :indeterminate.
10
+ // 2. Radix React component — <button class="checkbox|radio"
11
+ // data-state="checked|unchecked|indeterminate"> with a Radix Indicator child.
7
12
  .checkbox,
8
13
  .radio {
9
14
  appearance: none;
10
15
  -webkit-appearance: none;
11
- display: inline-block;
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ flex: none;
12
20
  width: 16px;
13
21
  height: 16px;
14
22
  margin: 0;
@@ -22,42 +30,77 @@
22
30
  border-color var(--transition-fast),
23
31
  box-shadow var(--transition-fast);
24
32
 
25
- &:hover:not(:disabled) { border-color: var(--color-primary); }
33
+ &:hover:not(:disabled):not([data-disabled]) { border-color: var(--color-primary); }
26
34
  &:focus-visible {
27
35
  outline: none;
28
36
  box-shadow: 0 0 0 var(--ring-width) var(--ring-color);
29
37
  }
30
- &:disabled { opacity: 0.5; cursor: not-allowed; }
38
+ &:disabled,
39
+ &[data-disabled] { opacity: 0.5; cursor: not-allowed; }
31
40
  }
32
41
 
33
42
  .checkbox {
34
43
  border-radius: var(--radius-xs);
35
44
 
36
- &:checked {
37
- background-color: var(--color-primary);
38
- border-color: var(--color-primary);
39
- background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'><path d='M3.5 8.5L6.5 11.5L12.5 5' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
40
- background-repeat: no-repeat;
41
- background-position: center;
45
+ // Native input pattern — no child possible, so the check is painted via
46
+ // background-image on a colored fill.
47
+ &:not([data-state]) {
48
+ &:checked {
49
+ background-color: var(--color-primary);
50
+ border-color: var(--color-primary);
51
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'><path d='M3.5 8.5L6.5 11.5L12.5 5' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
52
+ background-repeat: no-repeat;
53
+ background-position: center;
54
+ }
55
+ &:indeterminate {
56
+ background-color: var(--color-primary);
57
+ border-color: var(--color-primary);
58
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' fill='white' rx='1'/></svg>");
59
+ background-repeat: no-repeat;
60
+ background-position: center;
61
+ }
42
62
  }
43
63
 
44
- &:indeterminate {
64
+ // Radix pattern — Indicator child carries the Lucide check/minus icon.
65
+ &[data-state='checked'],
66
+ &[data-state='indeterminate'] {
45
67
  background-color: var(--color-primary);
46
68
  border-color: var(--color-primary);
47
- background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' fill='white' rx='1'/></svg>");
48
- background-repeat: no-repeat;
49
- background-position: center;
69
+ }
70
+
71
+ // Indicator wrapper (Radix renders this only when checked/indeterminate).
72
+ > * {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ color: #fff;
77
+ line-height: 0;
50
78
  }
51
79
  }
52
80
 
53
81
  .radio {
54
82
  border-radius: 50%;
83
+ position: relative;
55
84
 
56
- &:checked {
85
+ &:checked,
86
+ &[data-state='checked'] {
57
87
  border-color: var(--color-primary);
58
88
  background-color: var(--bg-surface-lowest);
89
+ }
90
+
91
+ // Native input uses a radial-gradient dot (no child element available).
92
+ &:not([data-state]):checked {
59
93
  background-image: radial-gradient(circle, var(--color-primary) 0 4px, transparent 5px);
60
94
  }
95
+
96
+ // Radix Indicator paints the dot — 8 px circle inside the 16 px ring.
97
+ > * {
98
+ display: block;
99
+ width: 8px;
100
+ height: 8px;
101
+ border-radius: 50%;
102
+ background: var(--color-primary);
103
+ }
61
104
  }
62
105
 
63
106
  // Label wrapper: <label class="control"><input class="checkbox"/> Label</label>
@@ -75,36 +118,55 @@
75
118
  }
76
119
 
77
120
  // ── Switch ──────────────────────────────────────────────────────────────
121
+ // Two markup patterns supported:
122
+ // 1. Legacy native input — <input type="checkbox" class="switch"> uses :checked
123
+ // and a ::before pseudo for the thumb.
124
+ // 2. Radix React component — <button class="switch" data-state="checked|unchecked">
125
+ // with a child <span class="switch-thumb">. Borders and animation are driven
126
+ // by [data-state] and the real .switch-thumb element.
78
127
  .switch {
128
+ // Track sizing / colors shared by both patterns.
129
+ --switch-w: 36px;
130
+ --switch-h: 20px;
131
+ --switch-pad: 2px;
132
+ --switch-thumb: calc(var(--switch-h) - var(--switch-pad) * 2);
133
+ --switch-travel: calc(var(--switch-w) - var(--switch-thumb) - var(--switch-pad) * 2);
134
+
79
135
  appearance: none;
80
136
  -webkit-appearance: none;
81
137
  position: relative;
82
- width: 32px;
83
- height: 18px;
138
+ display: inline-block;
139
+ flex: none;
140
+ width: var(--switch-w);
141
+ height: var(--switch-h);
84
142
  margin: 0;
85
143
  padding: 0;
86
144
  background: var(--border-color-strong);
145
+ border: 0;
87
146
  border-radius: var(--radius-full);
88
147
  cursor: pointer;
89
148
  vertical-align: middle;
90
149
  transition: background-color var(--transition-fast);
91
150
 
151
+ // Legacy input pattern — pseudo-thumb.
92
152
  &::before {
93
153
  content: '';
94
154
  position: absolute;
95
- top: 2px;
96
- left: 2px;
97
- width: 14px;
98
- height: 14px;
155
+ top: var(--switch-pad);
156
+ left: var(--switch-pad);
157
+ width: var(--switch-thumb);
158
+ height: var(--switch-thumb);
99
159
  background: #fff;
100
160
  border-radius: 50%;
101
161
  box-shadow: var(--shadow-xs);
102
162
  transition: transform var(--transition-fast);
103
163
  }
164
+ // Hide the pseudo-thumb when a real .switch-thumb child exists (Radix).
165
+ &:has(.switch-thumb)::before { content: none; }
104
166
 
105
167
  &:checked {
106
168
  background: var(--color-primary);
107
- &::before { transform: translateX(14px); }
169
+ &::before { transform: translateX(var(--switch-travel)); }
108
170
  }
109
171
 
110
172
  &:focus-visible {
@@ -113,6 +175,30 @@
113
175
  }
114
176
 
115
177
  &:disabled { opacity: 0.5; cursor: not-allowed; }
178
+
179
+ // Radix pattern — drive track color from [data-state]; thumb slides via .switch-thumb.
180
+ &[data-state='checked'] { background: var(--color-primary); }
181
+ &[data-disabled] { opacity: 0.5; cursor: not-allowed; }
182
+ }
183
+
184
+ .switch-thumb {
185
+ display: block;
186
+ width: var(--switch-thumb, 16px);
187
+ height: var(--switch-thumb, 16px);
188
+ margin: var(--switch-pad, 2px);
189
+ background: #fff;
190
+ border-radius: 50%;
191
+ box-shadow: var(--shadow-xs);
192
+ transform: translateX(0);
193
+ transition: transform var(--transition-fast);
194
+ will-change: transform;
195
+ pointer-events: none;
196
+
197
+ &[data-state='checked'] { transform: translateX(var(--switch-travel, 14px)); }
198
+ }
199
+
200
+ @media (prefers-reduced-motion: reduce) {
201
+ .switch, .switch::before, .switch-thumb { transition: none; }
116
202
  }
117
203
 
118
204
  // ── Select ──────────────────────────────────────────────────────────────
@@ -0,0 +1,129 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // List + ListItem — vertical row layout (participants, message lists, settings).
4
+
5
+ .cny-list {
6
+ list-style: none;
7
+ margin: 0;
8
+ padding: 0;
9
+ display: flex;
10
+ flex-direction: column;
11
+
12
+ &.bordered {
13
+ border: 1px solid var(--border-color);
14
+ border-radius: var(--radius-sm);
15
+ overflow: hidden;
16
+ background: var(--bg-surface-lowest);
17
+ }
18
+ &.dividers .cny-list-item:not(:last-child),
19
+ &.dividers > .cny-list-item-wrap:not(:last-child) {
20
+ border-bottom: 1px solid var(--hairline-color);
21
+ }
22
+ &.bordered.dividers .cny-list-item:not(:last-child),
23
+ &.bordered.dividers > .cny-list-item-wrap:not(:last-child) {
24
+ border-bottom: 1px solid var(--hairline-color);
25
+ }
26
+ }
27
+
28
+ .cny-list-item-wrap {
29
+ display: flex;
30
+ }
31
+
32
+ .cny-list-item {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: 12px;
36
+ width: 100%;
37
+ padding: 12px 14px;
38
+ background: transparent;
39
+ color: var(--text-primary);
40
+ text-decoration: none;
41
+ border: 0;
42
+ font-family: var(--font-body);
43
+ font-size: 0.875rem;
44
+ text-align: left;
45
+ transition: background-color 140ms ease, color 140ms ease;
46
+
47
+ // Sizes
48
+ .cny-list.size-sm & { padding: 8px 12px; font-size: 0.8125rem; gap: 10px; }
49
+ .cny-list.size-lg & { padding: 16px 18px; font-size: 0.9375rem; gap: 14px; }
50
+
51
+ &-leading {
52
+ flex: none;
53
+ display: inline-flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ color: var(--text-muted);
57
+ }
58
+ &-body {
59
+ flex: 1 1 auto;
60
+ min-width: 0;
61
+ display: flex;
62
+ flex-direction: column;
63
+ gap: 2px;
64
+ }
65
+ &-primary {
66
+ font-weight: 550;
67
+ line-height: 1.35;
68
+ color: var(--text-primary);
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ white-space: nowrap;
72
+ }
73
+ &-secondary {
74
+ font-size: 0.8125rem;
75
+ line-height: 1.4;
76
+ color: var(--text-muted);
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ white-space: nowrap;
80
+ }
81
+ &-trailing {
82
+ flex: none;
83
+ display: inline-flex;
84
+ align-items: center;
85
+ color: var(--text-muted);
86
+ }
87
+
88
+ // Interactive (button or link)
89
+ &.is-interactive {
90
+ cursor: pointer;
91
+ &:hover {
92
+ background: var(--bg-surface-low);
93
+ color: var(--text-primary);
94
+ }
95
+ &:focus-visible {
96
+ outline: 2px solid var(--border-focus);
97
+ outline-offset: -2px;
98
+ }
99
+ }
100
+
101
+ &.is-active {
102
+ background: color-mix(in srgb, var(--color-primary) 10%, var(--bg-surface-low));
103
+ color: var(--color-primary);
104
+
105
+ .cny-list-item-leading,
106
+ .cny-list-item-trailing { color: var(--color-primary); }
107
+ .cny-list-item-primary { color: var(--color-primary); font-weight: 600; }
108
+ }
109
+
110
+ &.is-disabled {
111
+ opacity: 0.55;
112
+ pointer-events: none;
113
+ }
114
+ }
115
+
116
+ .cny-list-separator {
117
+ height: 1px;
118
+ background: var(--hairline-color);
119
+ margin: 4px 0;
120
+ }
121
+
122
+ .cny-list-subheader {
123
+ padding: 10px 14px 6px;
124
+ font-size: 0.6875rem;
125
+ font-weight: 650;
126
+ letter-spacing: 0.08em;
127
+ text-transform: uppercase;
128
+ color: var(--text-muted);
129
+ }
@@ -0,0 +1,115 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Menu (DropdownMenu) — popup menu with items, checkboxes, radios, submenus.
4
+
5
+ @keyframes cny-menu-in {
6
+ from { opacity: 0; transform: translateY(-4px) scale(0.97); }
7
+ to { opacity: 1; transform: translateY(0) scale(1); }
8
+ }
9
+
10
+ .cny-menu-content {
11
+ z-index: 1010;
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 1px;
15
+ min-width: 180px;
16
+ padding: 4px;
17
+ background: var(--bg-surface-lowest);
18
+ border: 1px solid var(--border-color);
19
+ border-radius: var(--radius-sm);
20
+ box-shadow:
21
+ 0 1px 0 rgba(15, 23, 42, 0.04),
22
+ 0 8px 24px -8px rgba(15, 23, 42, 0.2);
23
+ font-family: var(--font-body);
24
+ outline: none;
25
+ animation: cny-menu-in 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
26
+
27
+ &.size-sm { min-width: 140px; padding: 3px; }
28
+
29
+ &[data-side='top'] { transform-origin: bottom; }
30
+ &[data-side='bottom'] { transform-origin: top; }
31
+ &[data-side='left'] { transform-origin: right; }
32
+ &[data-side='right'] { transform-origin: left; }
33
+ }
34
+
35
+ .cny-menu-item {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 10px;
39
+ padding: 7px 10px;
40
+ font-size: 0.875rem;
41
+ color: var(--text-primary);
42
+ border-radius: var(--radius-xs);
43
+ cursor: pointer;
44
+ outline: none;
45
+ user-select: none;
46
+ position: relative;
47
+ white-space: nowrap;
48
+
49
+ .cny-menu-content.size-sm & { padding: 6px 8px; font-size: 0.8125rem; }
50
+
51
+ &-icon {
52
+ flex: none;
53
+ display: inline-flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ width: 16px;
57
+ height: 16px;
58
+ color: var(--text-muted);
59
+ }
60
+ &-label { flex: 1 1 auto; }
61
+ &-shortcut {
62
+ flex: none;
63
+ margin-left: 16px;
64
+ font-family: var(--font-mono);
65
+ font-size: 0.6875rem;
66
+ color: var(--text-muted);
67
+ letter-spacing: 0.02em;
68
+ }
69
+ &-chevron {
70
+ flex: none;
71
+ display: inline-flex;
72
+ color: var(--text-muted);
73
+ }
74
+
75
+ &[data-highlighted] {
76
+ background: color-mix(in srgb, var(--color-primary) 10%, var(--bg-surface-low));
77
+ color: var(--text-primary);
78
+ }
79
+ &[data-highlighted] .cny-menu-item-icon,
80
+ &[data-highlighted] .cny-menu-item-chevron { color: var(--color-primary); }
81
+
82
+ &[data-disabled] {
83
+ color: var(--text-disabled);
84
+ cursor: not-allowed;
85
+ pointer-events: none;
86
+ }
87
+
88
+ &.is-destructive {
89
+ color: var(--color-danger);
90
+ .cny-menu-item-icon { color: var(--color-danger); }
91
+ &[data-highlighted] {
92
+ background: color-mix(in srgb, var(--color-danger) 10%, var(--bg-surface-low));
93
+ color: var(--color-danger);
94
+ }
95
+ }
96
+ }
97
+
98
+ .cny-menu-label {
99
+ padding: 8px 10px 4px;
100
+ font-size: 0.6875rem;
101
+ font-weight: 650;
102
+ letter-spacing: 0.08em;
103
+ text-transform: uppercase;
104
+ color: var(--text-muted);
105
+ }
106
+
107
+ .cny-menu-separator {
108
+ height: 1px;
109
+ background: var(--hairline-color);
110
+ margin: 4px -1px;
111
+ }
112
+
113
+ @media (prefers-reduced-motion: reduce) {
114
+ .cny-menu-content { animation: none; }
115
+ }
@@ -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
 
@@ -0,0 +1,62 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Pagination — flat numbered nav with prev/next chevrons.
4
+
5
+ .cny-pagination {
6
+ display: inline-flex;
7
+ align-items: center;
8
+ gap: 4px;
9
+ font-family: var(--font-body);
10
+
11
+ &-btn {
12
+ min-width: 32px;
13
+ height: 32px;
14
+ padding: 0 8px;
15
+ display: inline-flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ background: transparent;
19
+ color: var(--text-secondary);
20
+ border: 1px solid transparent;
21
+ border-radius: var(--radius-sm);
22
+ font-size: 0.8125rem;
23
+ font-weight: 500;
24
+ cursor: pointer;
25
+ transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
26
+
27
+ &:hover:not(:disabled):not(.is-active) {
28
+ background: var(--bg-surface-low);
29
+ color: var(--text-primary);
30
+ }
31
+ &:focus-visible {
32
+ outline: 2px solid var(--color-primary);
33
+ outline-offset: 1px;
34
+ }
35
+ &:disabled {
36
+ opacity: 0.4;
37
+ cursor: not-allowed;
38
+ }
39
+ &.is-active {
40
+ background: var(--color-primary);
41
+ color: var(--color-primary-on);
42
+ border-color: var(--color-primary);
43
+ }
44
+ }
45
+
46
+ &-control { color: var(--text-muted); }
47
+
48
+ &-ellipsis {
49
+ display: inline-flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ min-width: 24px;
53
+ color: var(--text-muted);
54
+ }
55
+
56
+ &-sm {
57
+ .cny-pagination-btn { min-width: 28px; height: 28px; font-size: 0.75rem; padding: 0 6px; }
58
+ }
59
+ &-lg {
60
+ .cny-pagination-btn { min-width: 36px; height: 36px; font-size: 0.875rem; padding: 0 10px; }
61
+ }
62
+ }
@@ -0,0 +1,39 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Popover — generic floating surface for inline forms, filters, info.
4
+
5
+ @keyframes cny-popover-in {
6
+ from { opacity: 0; transform: translateY(-4px) scale(0.97); }
7
+ to { opacity: 1; transform: translateY(0) scale(1); }
8
+ }
9
+
10
+ .cny-popover-content {
11
+ z-index: 1010;
12
+ padding: 14px;
13
+ background: var(--bg-surface-lowest);
14
+ border: 1px solid var(--border-color);
15
+ border-radius: var(--radius-sm);
16
+ box-shadow:
17
+ 0 1px 0 rgba(15, 23, 42, 0.04),
18
+ 0 12px 28px -12px rgba(15, 23, 42, 0.22);
19
+ font-family: var(--font-body);
20
+ color: var(--text-primary);
21
+ outline: none;
22
+ animation: cny-popover-in 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
23
+ max-width: calc(100vw - 16px);
24
+
25
+ &[data-side='top'] { transform-origin: bottom; }
26
+ &[data-side='bottom'] { transform-origin: top; }
27
+ &[data-side='left'] { transform-origin: right; }
28
+ &[data-side='right'] { transform-origin: left; }
29
+ }
30
+
31
+ .cny-popover-arrow {
32
+ fill: var(--bg-surface-lowest);
33
+ stroke: var(--border-color);
34
+ stroke-width: 1px;
35
+ }
36
+
37
+ @media (prefers-reduced-motion: reduce) {
38
+ .cny-popover-content { animation: none; }
39
+ }