canny-ui 1.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,151 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // ── Navbar ────────────────────────────────────────────────────
4
+ // Sticky top-of-app bar. Three slots: brand (left), nav (center), actions (right).
5
+ // Glass-morphism backdrop with a hairline that sharpens as the page scrolls.
6
+
7
+ .cny-navbar {
8
+ display: flex;
9
+ align-items: center;
10
+ gap: $space-4;
11
+ height: 56px;
12
+ padding: 0 $space-6;
13
+ background: color-mix(in srgb, var(--bg-base) 78%, transparent);
14
+ backdrop-filter: saturate(180%) blur(14px);
15
+ -webkit-backdrop-filter: saturate(180%) blur(14px);
16
+ border-bottom: 1px solid var(--hairline-color);
17
+ position: sticky;
18
+ top: 0;
19
+ z-index: 20;
20
+ transition: background-color var(--transition-fast), border-color var(--transition-fast);
21
+
22
+ &.solid {
23
+ background: var(--bg-base);
24
+ backdrop-filter: none;
25
+ -webkit-backdrop-filter: none;
26
+ }
27
+
28
+ &.bordered {
29
+ border-bottom-color: var(--border-color);
30
+ }
31
+ }
32
+
33
+ .cny-navbar-brand {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ gap: $space-2;
37
+ text-decoration: none;
38
+ color: var(--text-primary);
39
+ font-weight: 650;
40
+ letter-spacing: -0.012em;
41
+ font-size: 0.9375rem;
42
+ line-height: 1;
43
+ padding: 6px 8px;
44
+ margin-left: -8px;
45
+ border-radius: var(--radius-sm);
46
+ flex-shrink: 0;
47
+ transition: background-color var(--transition-fast);
48
+
49
+ &:hover { background: var(--bg-surface-low); }
50
+
51
+ &:focus-visible {
52
+ outline: none;
53
+ box-shadow: 0 0 0 var(--ring-width) var(--ring-color);
54
+ }
55
+
56
+ .brand-mark {
57
+ position: relative;
58
+ width: 22px;
59
+ height: 22px;
60
+ border-radius: 6px;
61
+ background:
62
+ radial-gradient(120% 120% at 20% 0%, color-mix(in srgb, white 22%, transparent), transparent 55%),
63
+ linear-gradient(135deg, var(--color-primary), color-mix(in srgb, var(--color-primary) 60%, black));
64
+ box-shadow:
65
+ inset 0 1px 0 color-mix(in srgb, white 25%, transparent),
66
+ inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 40%, black),
67
+ 0 1px 2px rgba(0, 0, 0, 0.18);
68
+ flex-shrink: 0;
69
+ }
70
+ }
71
+
72
+ .cny-navbar-nav {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 2px;
76
+ flex: 1;
77
+ min-width: 0;
78
+ overflow-x: auto;
79
+ scrollbar-width: none;
80
+
81
+ &::-webkit-scrollbar { display: none; }
82
+ }
83
+
84
+ .cny-navbar-item {
85
+ position: relative;
86
+ display: inline-flex;
87
+ align-items: center;
88
+ height: 32px;
89
+ padding: 0 $space-3;
90
+ border-radius: var(--radius-sm);
91
+ font-size: 0.875rem;
92
+ font-weight: 500;
93
+ color: var(--text-secondary);
94
+ text-decoration: none;
95
+ white-space: nowrap;
96
+ transition:
97
+ background-color var(--transition-fast),
98
+ color var(--transition-fast);
99
+
100
+ &:hover {
101
+ background: var(--bg-surface-low);
102
+ color: var(--text-primary);
103
+ }
104
+
105
+ &:focus-visible {
106
+ outline: none;
107
+ box-shadow: 0 0 0 var(--ring-width) var(--ring-color);
108
+ }
109
+
110
+ &.active {
111
+ color: var(--text-primary);
112
+ font-weight: 550;
113
+
114
+ &::after {
115
+ content: '';
116
+ position: absolute;
117
+ left: $space-3;
118
+ right: $space-3;
119
+ bottom: -10px;
120
+ height: 2px;
121
+ border-radius: 2px 2px 0 0;
122
+ background: var(--color-primary);
123
+ }
124
+ }
125
+ }
126
+
127
+ .cny-navbar-actions {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: $space-2;
131
+ margin-left: auto;
132
+ flex-shrink: 0;
133
+ }
134
+
135
+ .cny-navbar-divider {
136
+ width: 1px;
137
+ height: 20px;
138
+ background: var(--hairline-color);
139
+ margin: 0 $space-1;
140
+ flex-shrink: 0;
141
+ }
142
+
143
+ // Responsive: tuck nav on narrow screens
144
+ @media (max-width: 640px) {
145
+ .cny-navbar {
146
+ height: 52px;
147
+ padding: 0 $space-4;
148
+ gap: $space-2;
149
+ }
150
+ .cny-navbar-nav { display: none; }
151
+ }
@@ -0,0 +1,70 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Progress — determinate bar or indeterminate shimmer. Set width via inline style or --value.
4
+
5
+ .progress {
6
+ position: relative;
7
+ display: block;
8
+ width: 100%;
9
+ height: 6px;
10
+ background: var(--bg-surface-edge);
11
+ border-radius: var(--radius-full);
12
+ overflow: hidden;
13
+
14
+ &-bar {
15
+ display: block;
16
+ height: 100%;
17
+ width: var(--value, 0%);
18
+ background: var(--color-primary);
19
+ border-radius: inherit;
20
+ transition: width var(--transition-base);
21
+ }
22
+
23
+ &.danger .progress-bar { background: var(--color-danger); }
24
+ &.warning .progress-bar { background: var(--color-warning); }
25
+ &.success .progress-bar { background: var(--color-success); }
26
+ &.info .progress-bar { background: var(--color-info); }
27
+
28
+ &.sm { height: 4px; }
29
+ &.lg { height: 10px; }
30
+
31
+ // Indeterminate — sliding chunk
32
+ &.indeterminate .progress-bar {
33
+ width: 40%;
34
+ animation: cny-progress-slide 1.4s var(--ease-standard) infinite;
35
+ }
36
+ }
37
+
38
+ @keyframes cny-progress-slide {
39
+ 0% { transform: translateX(-100%); }
40
+ 100% { transform: translateX(250%); }
41
+ }
42
+
43
+ // Circular progress — via conic-gradient, driven by --value (0-100)
44
+ .progress-ring {
45
+ --value: 0;
46
+ display: inline-grid;
47
+ place-items: center;
48
+ width: 40px;
49
+ height: 40px;
50
+ border-radius: 50%;
51
+ background:
52
+ conic-gradient(var(--color-primary) calc(var(--value) * 1%), var(--bg-surface-edge) 0);
53
+ font-family: var(--font-body);
54
+ font-size: 0.6875rem;
55
+ font-weight: 600;
56
+ color: var(--text-primary);
57
+
58
+ &::before {
59
+ content: '';
60
+ grid-area: 1 / 1;
61
+ width: calc(100% - 6px);
62
+ height: calc(100% - 6px);
63
+ background: var(--bg-surface-lowest);
64
+ border-radius: 50%;
65
+ }
66
+ & > * { grid-area: 1 / 1; }
67
+
68
+ &.sm { width: 28px; height: 28px; font-size: 0.5625rem; }
69
+ &.lg { width: 56px; height: 56px; font-size: 0.8125rem; }
70
+ }
@@ -0,0 +1,159 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // ── Scrollbar ────────────────────────────────────────────────────
4
+ // Opt-in styled scrollbar. Apply `.cny-scroll` to any overflow container.
5
+ // Modifiers:
6
+ // .thin — narrower track for dense UIs
7
+ // .overlay — hides the thumb until hover/scroll
8
+ // .subtle — track visible only on hover
9
+ // .expand — thin by default, thumb widens on container hover
10
+ // .primary — thumb uses primary color
11
+
12
+ .cny-scroll {
13
+ scrollbar-width: thin;
14
+ scrollbar-color: var(--border-color) transparent;
15
+
16
+ &::-webkit-scrollbar {
17
+ width: 12px;
18
+ height: 12px;
19
+ }
20
+
21
+ &::-webkit-scrollbar-track {
22
+ background: transparent;
23
+ }
24
+
25
+ &::-webkit-scrollbar-thumb {
26
+ background-color: var(--border-color);
27
+ border-radius: var(--radius-full);
28
+ border: 3px solid transparent;
29
+ background-clip: content-box;
30
+ transition: background-color var(--transition-fast);
31
+
32
+ &:hover {
33
+ background-color: var(--text-muted);
34
+ background-clip: content-box;
35
+ }
36
+
37
+ &:active {
38
+ background-color: var(--text-secondary);
39
+ background-clip: content-box;
40
+ }
41
+ }
42
+
43
+ &::-webkit-scrollbar-corner {
44
+ background: transparent;
45
+ }
46
+
47
+ // ── Thin ───────────────────────────────────────────────────────
48
+ &.thin {
49
+ &::-webkit-scrollbar {
50
+ width: 8px;
51
+ height: 8px;
52
+ }
53
+
54
+ &::-webkit-scrollbar-thumb {
55
+ border-width: 2px;
56
+ }
57
+ }
58
+
59
+ // ── Subtle: track appears on hover ─────────────────────────────
60
+ &.subtle {
61
+ &::-webkit-scrollbar-thumb {
62
+ background-color: transparent;
63
+ }
64
+
65
+ &:hover::-webkit-scrollbar-thumb,
66
+ &:focus-within::-webkit-scrollbar-thumb {
67
+ background-color: var(--border-color);
68
+ background-clip: content-box;
69
+ }
70
+ }
71
+
72
+ // ── Overlay: thumb floats over content ─────────────────────────
73
+ &.overlay {
74
+ &::-webkit-scrollbar {
75
+ width: 10px;
76
+ height: 10px;
77
+ }
78
+
79
+ &::-webkit-scrollbar-track {
80
+ background: transparent;
81
+ }
82
+
83
+ &::-webkit-scrollbar-thumb {
84
+ background-color: color-mix(in srgb, var(--text-muted) 40%, transparent);
85
+ border-width: 2px;
86
+
87
+ &:hover {
88
+ background-color: color-mix(in srgb, var(--text-muted) 70%, transparent);
89
+ background-clip: content-box;
90
+ }
91
+ }
92
+ }
93
+
94
+ // ── Expand: slim thumb that fattens when the pointer is on the scrollbar ─
95
+ &.expand {
96
+ // Leave scrollbar-width unset (auto) so Chromium honors the webkit width.
97
+ // Firefox has no pseudo-element hover, so `thin` stays its native look.
98
+ scrollbar-width: auto;
99
+
100
+ &::-webkit-scrollbar {
101
+ width: 16px;
102
+ height: 16px;
103
+ background: transparent;
104
+ }
105
+
106
+ // Rest: 16px track with a 4px transparent border → 8px visible thumb.
107
+ &::-webkit-scrollbar-thumb {
108
+ border-width: 4px;
109
+ background-color: var(--border-color-strong);
110
+ transition:
111
+ border-width 0.15s ease,
112
+ background-color var(--transition-fast);
113
+ }
114
+
115
+ // Pointer over the thumb (anywhere in its 16px column) → fatten to 14px visible.
116
+ // The transparent border is still part of the thumb's hit region.
117
+ &::-webkit-scrollbar-thumb:hover,
118
+ &::-webkit-scrollbar-thumb:active {
119
+ border-width: 1px;
120
+ background-color: var(--text-muted);
121
+ }
122
+ }
123
+
124
+ // ── Primary color ──────────────────────────────────────────────
125
+ &.primary {
126
+ scrollbar-color: var(--color-primary) transparent;
127
+
128
+ &::-webkit-scrollbar-thumb {
129
+ background-color: var(--color-primary);
130
+ background-clip: content-box;
131
+
132
+ &:hover {
133
+ background-color: var(--color-primary);
134
+ background-clip: content-box;
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ // ── Scroll region: a bordered, rounded panel that scrolls ──────
141
+ .cny-scroll-region {
142
+ @extend .cny-scroll;
143
+ border: 1px solid var(--border-color);
144
+ border-radius: var(--radius-md);
145
+ background: var(--bg-surface-lowest);
146
+ overflow: auto;
147
+
148
+ // Fade gradient cues at the top/bottom when content overflows.
149
+ // Uses scroll-driven shadow via background-attachment.
150
+ background:
151
+ linear-gradient(var(--bg-surface-lowest) 30%, rgba(0, 0, 0, 0)) center top,
152
+ linear-gradient(rgba(0, 0, 0, 0), var(--bg-surface-lowest) 70%) center bottom,
153
+ radial-gradient(farthest-side at center top, var(--hairline-color), transparent) center top,
154
+ radial-gradient(farthest-side at center bottom, var(--hairline-color), transparent) center bottom;
155
+ background-repeat: no-repeat;
156
+ background-size: 100% 24px, 100% 24px, 100% 8px, 100% 8px;
157
+ background-attachment: local, local, scroll, scroll;
158
+ background-color: var(--bg-surface-lowest);
159
+ }
@@ -0,0 +1,132 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // ── Sidebar ───────────────────────────────────────────────────
4
+ // A vertical navigation rail. Composes into sections with titles and items.
5
+
6
+ .cny-sidebar {
7
+ display: flex;
8
+ flex-direction: column;
9
+ width: 240px;
10
+ flex-shrink: 0;
11
+ padding: $space-6 $space-5;
12
+ border-right: 1px solid var(--hairline-color);
13
+ background: var(--bg-base);
14
+ overflow-y: auto;
15
+
16
+ &.sticky {
17
+ position: sticky;
18
+ top: 52px;
19
+ align-self: flex-start;
20
+ max-height: calc(100vh - 52px);
21
+ }
22
+
23
+ &.thin {
24
+ width: 200px;
25
+ padding: $space-5 $space-4;
26
+ }
27
+
28
+ &.wide {
29
+ width: 280px;
30
+ }
31
+ }
32
+
33
+ .cny-sidebar-section {
34
+ margin-bottom: $space-6;
35
+
36
+ &:last-child {
37
+ margin-bottom: 0;
38
+ }
39
+ }
40
+
41
+ .cny-sidebar-title {
42
+ font-size: 0.6875rem;
43
+ font-weight: 650;
44
+ letter-spacing: 0.08em;
45
+ text-transform: uppercase;
46
+ color: var(--text-muted);
47
+ padding: 0 $space-2;
48
+ margin: 0 0 $space-3;
49
+ }
50
+
51
+ .cny-sidebar-nav {
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 1px;
55
+ }
56
+
57
+ .cny-sidebar-item {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: $space-2;
61
+ padding: 6px $space-2;
62
+ border-radius: var(--radius-sm);
63
+ font-size: 0.84375rem;
64
+ color: var(--text-secondary);
65
+ text-decoration: none;
66
+ position: relative;
67
+ transition: background-color var(--transition-fast), color var(--transition-fast);
68
+
69
+ &:hover {
70
+ background: var(--bg-surface-low);
71
+ color: var(--text-primary);
72
+ }
73
+
74
+ &.active {
75
+ color: var(--color-primary);
76
+ font-weight: 550;
77
+ background: color-mix(in srgb, var(--color-primary) 8%, transparent);
78
+
79
+ &::before {
80
+ content: '';
81
+ position: absolute;
82
+ left: calc(-1 * #{$space-5});
83
+ top: 50%;
84
+ transform: translateY(-50%);
85
+ width: 2px;
86
+ height: 16px;
87
+ background: var(--color-primary);
88
+ border-radius: 0 2px 2px 0;
89
+ }
90
+ }
91
+
92
+ &.disabled {
93
+ color: var(--text-muted);
94
+ pointer-events: none;
95
+ opacity: 0.55;
96
+ }
97
+
98
+ .cny-sidebar-item-icon {
99
+ width: 14px;
100
+ height: 14px;
101
+ flex-shrink: 0;
102
+ opacity: 0.75;
103
+ }
104
+
105
+ .cny-sidebar-item-badge {
106
+ margin-left: auto;
107
+ }
108
+ }
109
+
110
+ // Collapsed variant: icons only
111
+ .cny-sidebar.collapsed {
112
+ width: 60px;
113
+ padding: $space-5 $space-2;
114
+ align-items: center;
115
+
116
+ .cny-sidebar-title { display: none; }
117
+
118
+ .cny-sidebar-item {
119
+ justify-content: center;
120
+ padding: 8px;
121
+
122
+ span:not(.cny-sidebar-item-icon):not(.cny-sidebar-item-badge) { display: none; }
123
+ .cny-sidebar-item-badge { display: none; }
124
+
125
+ &.active::before { left: -#{$space-2}; }
126
+ }
127
+ }
128
+
129
+ // Responsive: hide below 900px (callers should provide a mobile menu)
130
+ @media (max-width: 900px) {
131
+ .cny-sidebar.responsive { display: none; }
132
+ }
@@ -0,0 +1,72 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Skeleton — shimmering placeholder for async content.
4
+
5
+ .skeleton {
6
+ display: block;
7
+ background: var(--bg-surface-edge);
8
+ border-radius: var(--radius-sm);
9
+ position: relative;
10
+ overflow: hidden;
11
+ color: transparent;
12
+ user-select: none;
13
+
14
+ &::after {
15
+ content: '';
16
+ position: absolute;
17
+ inset: 0;
18
+ transform: translateX(-100%);
19
+ background: linear-gradient(
20
+ 90deg,
21
+ transparent 0%,
22
+ rgba(255, 255, 255, 0.45) 50%,
23
+ transparent 100%
24
+ );
25
+ animation: cny-skeleton-shimmer 1.6s infinite;
26
+ }
27
+
28
+ // Shape variants
29
+ &.text {
30
+ height: 0.875rem;
31
+ border-radius: var(--radius-xs);
32
+ margin: 4px 0;
33
+ }
34
+ &.title {
35
+ height: 1.25rem;
36
+ width: 60%;
37
+ border-radius: var(--radius-xs);
38
+ }
39
+ &.circle { border-radius: 50%; }
40
+ &.button { height: 36px; width: 96px; border-radius: var(--radius-sm); }
41
+ &.avatar { width: 32px; height: 32px; border-radius: 50%; }
42
+ }
43
+
44
+ @keyframes cny-skeleton-shimmer {
45
+ 100% { transform: translateX(100%); }
46
+ }
47
+
48
+ // Respect reduced motion
49
+ @media (prefers-reduced-motion: reduce) {
50
+ .skeleton::after { animation: none; }
51
+ }
52
+
53
+ // Dark theme — use a lighter overlay since background is darker
54
+ .cny-dark .skeleton::after {
55
+ background: linear-gradient(
56
+ 90deg,
57
+ transparent 0%,
58
+ rgba(255, 255, 255, 0.08) 50%,
59
+ transparent 100%
60
+ );
61
+ }
62
+
63
+ @media (prefers-color-scheme: dark) {
64
+ .skeleton::after {
65
+ background: linear-gradient(
66
+ 90deg,
67
+ transparent 0%,
68
+ rgba(255, 255, 255, 0.08) 50%,
69
+ transparent 100%
70
+ );
71
+ }
72
+ }
@@ -0,0 +1,132 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Table — enterprise data tables. Opt-in .striped, .bordered, .compact.
4
+
5
+ .table {
6
+ width: 100%;
7
+ border-collapse: collapse;
8
+ font-family: var(--font-body);
9
+ font-size: 0.875rem;
10
+ color: var(--text-primary);
11
+ background: var(--bg-surface-lowest);
12
+
13
+ th, td {
14
+ padding: $space-3 $space-4;
15
+ text-align: left;
16
+ vertical-align: middle;
17
+ border-bottom: 1px solid var(--hairline-color);
18
+ }
19
+
20
+ thead th {
21
+ font-size: 0.75rem;
22
+ font-weight: 600;
23
+ color: var(--text-muted);
24
+ text-transform: uppercase;
25
+ letter-spacing: 0.04em;
26
+ background: var(--bg-surface-low);
27
+ border-bottom: 1px solid var(--border-color);
28
+ white-space: nowrap;
29
+ }
30
+
31
+ tbody tr {
32
+ transition: background-color var(--transition-fast);
33
+
34
+ &:last-child td { border-bottom: 0; }
35
+ }
36
+
37
+ &.hoverable tbody tr:hover { background: var(--bg-surface-low); }
38
+
39
+ &.striped tbody tr:nth-child(odd) { background: var(--bg-surface-low); }
40
+
41
+ &.compact {
42
+ th, td { padding: $space-2 $space-3; }
43
+ }
44
+
45
+ &.bordered {
46
+ border: 1px solid var(--border-color);
47
+ border-radius: var(--radius-md);
48
+ overflow: hidden;
49
+
50
+ th, td { border-right: 1px solid var(--hairline-color); }
51
+ th:last-child, td:last-child { border-right: 0; }
52
+ }
53
+
54
+ // Numeric/tabular alignment utility on cells
55
+ .num {
56
+ text-align: right;
57
+ font-variant-numeric: tabular-nums;
58
+ }
59
+
60
+ // Resizable columns — activate with .resizable.
61
+ // Needs the canny-ui table-resize.js helper to wire up drag handles.
62
+ &.resizable {
63
+ table-layout: fixed;
64
+
65
+ // Clip shrunk columns at the cell boundary so content never spills into
66
+ // the next column. For nested flex content, wrap text in <span class="truncate">.
67
+ thead th,
68
+ tbody td {
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ white-space: nowrap;
72
+ }
73
+
74
+ thead th { position: relative; }
75
+
76
+ // Allow flex row cells to shrink their text children to the available width.
77
+ tbody td > .cny-row { min-width: 0; }
78
+
79
+ .th-resizer {
80
+ position: absolute;
81
+ top: 0;
82
+ right: 0;
83
+ width: 6px;
84
+ height: 100%;
85
+ cursor: col-resize;
86
+ user-select: none;
87
+ touch-action: none;
88
+ z-index: 1;
89
+ background: transparent;
90
+ transition: background-color var(--transition-fast);
91
+
92
+ &::after {
93
+ content: '';
94
+ position: absolute;
95
+ top: 20%;
96
+ right: 2px;
97
+ width: 1px;
98
+ height: 60%;
99
+ background: var(--border-color);
100
+ transition: background-color var(--transition-fast);
101
+ }
102
+
103
+ &:hover::after,
104
+ &.resizing::after {
105
+ background: var(--color-primary);
106
+ width: 2px;
107
+ }
108
+
109
+ &:hover,
110
+ &.resizing {
111
+ background: color-mix(in srgb, var(--color-primary) 8%, transparent);
112
+ }
113
+ }
114
+
115
+ // Visual feedback while dragging — applied to <body> by the helper.
116
+ &.is-resizing {
117
+ cursor: col-resize;
118
+ user-select: none;
119
+ }
120
+ }
121
+ }
122
+
123
+ // Wrapper for horizontal scroll on narrow viewports
124
+ .table-wrap {
125
+ overflow-x: auto;
126
+ border: 1px solid var(--border-color);
127
+ border-radius: var(--radius-md);
128
+ background: var(--bg-surface-lowest);
129
+
130
+ .table { background: transparent; }
131
+ .table th, .table td { white-space: nowrap; }
132
+ }