css-is-awesome 1.1.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.
Files changed (198) hide show
  1. package/AGENTS.md +343 -0
  2. package/CHANGELOG.md +1036 -0
  3. package/CLAUDE.md +9 -0
  4. package/CONTRACT.md +580 -0
  5. package/GEMINI.md +9 -0
  6. package/LICENSE +21 -0
  7. package/LICENSE-third-party +88 -0
  8. package/MIGRATION.md +312 -0
  9. package/README.md +380 -0
  10. package/ROADMAP.md +714 -0
  11. package/THEMING.md +290 -0
  12. package/THREE-TIERS.md +158 -0
  13. package/VERSIONING.md +200 -0
  14. package/_index.scss +34 -0
  15. package/api.scss +16 -0
  16. package/bin/README.md +57 -0
  17. package/bin/cia.cjs +114 -0
  18. package/bin/migrate-bootstrap.cjs +427 -0
  19. package/bin/migrate-tailwind.cjs +832 -0
  20. package/css-is-awesome.instructions.md +533 -0
  21. package/dist/css-is-awesome.core.css +598 -0
  22. package/dist/css-is-awesome.core.min.css +1 -0
  23. package/dist/css-is-awesome.css +3733 -0
  24. package/dist/css-is-awesome.min.css +1 -0
  25. package/dist/css-is-awesome.utilities.css +2445 -0
  26. package/dist/css-is-awesome.utilities.min.css +1 -0
  27. package/dist/tokens.css +508 -0
  28. package/dist/tokens.d.ts +266 -0
  29. package/dist/tokens.min.css +1 -0
  30. package/figma-tokens/README.md +57 -0
  31. package/figma-tokens/primitives-brand.scss +121 -0
  32. package/figma-tokens/semantic-dark.scss +61 -0
  33. package/figma-tokens/semantic-light.scss +61 -0
  34. package/figma-tokens/tokens.json +144 -0
  35. package/llm.txt +183 -0
  36. package/mcp/server.cjs +1495 -0
  37. package/package.json +199 -0
  38. package/public/icons/README.md +278 -0
  39. package/public/icons/arrow-right.svg +4 -0
  40. package/public/icons/check.svg +4 -0
  41. package/public/icons/chevron-down.svg +4 -0
  42. package/public/icons/close.svg +4 -0
  43. package/public/icons/core/arrow-down.svg +13 -0
  44. package/public/icons/core/arrow-left.svg +13 -0
  45. package/public/icons/core/arrow-right.svg +13 -0
  46. package/public/icons/core/arrow-up.svg +13 -0
  47. package/public/icons/core/bell.svg +13 -0
  48. package/public/icons/core/calendar.svg +15 -0
  49. package/public/icons/core/check.svg +12 -0
  50. package/public/icons/core/chevron-down.svg +12 -0
  51. package/public/icons/core/chevron-left.svg +12 -0
  52. package/public/icons/core/chevron-right.svg +12 -0
  53. package/public/icons/core/chevron-up.svg +12 -0
  54. package/public/icons/core/clock.svg +13 -0
  55. package/public/icons/core/close.svg +13 -0
  56. package/public/icons/core/copy.svg +13 -0
  57. package/public/icons/core/download.svg +14 -0
  58. package/public/icons/core/edit.svg +13 -0
  59. package/public/icons/core/error.svg +14 -0
  60. package/public/icons/core/external-link.svg +14 -0
  61. package/public/icons/core/eye-off.svg +15 -0
  62. package/public/icons/core/eye.svg +13 -0
  63. package/public/icons/core/filter.svg +12 -0
  64. package/public/icons/core/heart.svg +12 -0
  65. package/public/icons/core/help.svg +14 -0
  66. package/public/icons/core/home.svg +13 -0
  67. package/public/icons/core/info.svg +14 -0
  68. package/public/icons/core/loading.svg +12 -0
  69. package/public/icons/core/lock.svg +13 -0
  70. package/public/icons/core/mail.svg +13 -0
  71. package/public/icons/core/menu.svg +14 -0
  72. package/public/icons/core/message.svg +12 -0
  73. package/public/icons/core/minus.svg +12 -0
  74. package/public/icons/core/more-horizontal.svg +14 -0
  75. package/public/icons/core/pause.svg +13 -0
  76. package/public/icons/core/play.svg +12 -0
  77. package/public/icons/core/plus.svg +13 -0
  78. package/public/icons/core/refresh.svg +15 -0
  79. package/public/icons/core/save.svg +14 -0
  80. package/public/icons/core/search.svg +13 -0
  81. package/public/icons/core/settings.svg +13 -0
  82. package/public/icons/core/share.svg +14 -0
  83. package/public/icons/core/sort.svg +15 -0
  84. package/public/icons/core/star.svg +12 -0
  85. package/public/icons/core/success.svg +13 -0
  86. package/public/icons/core/trash.svg +16 -0
  87. package/public/icons/core/unlock.svg +13 -0
  88. package/public/icons/core/upload.svg +14 -0
  89. package/public/icons/core/user.svg +13 -0
  90. package/public/icons/core/users.svg +15 -0
  91. package/public/icons/core/warning.svg +14 -0
  92. package/public/icons/download.svg +4 -0
  93. package/public/icons/edit.svg +4 -0
  94. package/public/icons/menu.svg +4 -0
  95. package/public/icons/search.svg +4 -0
  96. package/public/theme.css +4179 -0
  97. package/public/themes/README.md +102 -0
  98. package/public/themes/boilerplate/theme.css +154 -0
  99. package/public/themes/boilerplate-dark/theme.css +171 -0
  100. package/public/themes/boilerplate-light/theme.css +171 -0
  101. package/public/themes/cupertino/theme.css +165 -0
  102. package/public/themes/cupertino-dark/theme.css +200 -0
  103. package/public/themes/cupertino-light/theme.css +227 -0
  104. package/public/themes/glass/theme.css +190 -0
  105. package/public/themes/glass-dark/theme.css +201 -0
  106. package/public/themes/glass-light/theme.css +229 -0
  107. package/public/themes/graphite/theme.css +155 -0
  108. package/public/themes/graphite-dark/theme.css +223 -0
  109. package/public/themes/graphite-light/theme.css +191 -0
  110. package/public/themes/press/theme.css +189 -0
  111. package/public/themes/press-dark/theme.css +199 -0
  112. package/public/themes/press-light/theme.css +235 -0
  113. package/public/themes/prism/theme.css +159 -0
  114. package/public/themes/prism-dark/theme.css +185 -0
  115. package/public/themes/prism-light/theme.css +188 -0
  116. package/public/themes/sketchbook/theme.css +152 -0
  117. package/public/themes/sketchbook-dark/theme.css +194 -0
  118. package/public/themes/sketchbook-light/theme.css +188 -0
  119. package/public/themes/terminal/theme.css +159 -0
  120. package/public/themes/terminal-dark/theme.css +229 -0
  121. package/public/themes/terminal-light/theme.css +152 -0
  122. package/scripts/README.md +70 -0
  123. package/scripts/icon-contract.json +67 -0
  124. package/scripts/icon-validator.js +205 -0
  125. package/scripts/prepare-dist.mjs +65 -0
  126. package/scripts/theme-a11y.js +415 -0
  127. package/scripts/theme-contract.json +171 -0
  128. package/scripts/theme-validator.js +674 -0
  129. package/scss/README.md +55 -0
  130. package/scss/_animations-utilities.scss +107 -0
  131. package/scss/_animations.scss +248 -0
  132. package/scss/_app-styles.scss +15 -0
  133. package/scss/_generator.scss +174 -0
  134. package/scss/_icons.scss +475 -0
  135. package/scss/_index.scss +17 -0
  136. package/scss/_layout.scss +597 -0
  137. package/scss/_mixins.scss +1036 -0
  138. package/scss/_system.scss +291 -0
  139. package/scss/_utilities.scss +377 -0
  140. package/scss/api.scss +61 -0
  141. package/scss/components/_accordion.scss +72 -0
  142. package/scss/components/_buttons.scss +171 -0
  143. package/scss/components/_copy-button.scss +69 -0
  144. package/scss/components/_data.scss +190 -0
  145. package/scss/components/_feedback.scss +197 -0
  146. package/scss/components/_forms.scss +272 -0
  147. package/scss/components/_index.scss +17 -0
  148. package/scss/components/_navigation.scss +186 -0
  149. package/scss/components/_overlay.scss +259 -0
  150. package/scss/components/_stepper.scss +159 -0
  151. package/scss/components/_tabs.scss +72 -0
  152. package/scss/core.scss +54 -0
  153. package/scss/examples/_theming.scss +107 -0
  154. package/scss/examples/_usage.scss +317 -0
  155. package/scss/main.scss +64 -0
  156. package/scss/recipes/README.md +110 -0
  157. package/scss/recipes/_bare-tags.scss +232 -0
  158. package/scss/recipes/_recipe-template.md +119 -0
  159. package/scss/recipes/combobox.md +509 -0
  160. package/scss/recipes/dialog.md +265 -0
  161. package/scss/recipes/print-to-pdf.md +263 -0
  162. package/scss/theme/README.md +67 -0
  163. package/scss/theme/_brand.scss +18 -0
  164. package/scss/theme/_colors-dark.scss +68 -0
  165. package/scss/theme/_colors-light.scss +70 -0
  166. package/scss/theme/_components.scss +94 -0
  167. package/scss/theme/_icons.scss +104 -0
  168. package/scss/theme/_index.scss +62 -0
  169. package/scss/theme/_shadows.scss +65 -0
  170. package/scss/theme/_shape.scss +13 -0
  171. package/scss/theme/_typography.scss +9 -0
  172. package/scss/theme/registry.json +447 -0
  173. package/scss/themes/boilerplate-dark.scss +212 -0
  174. package/scss/themes/boilerplate-light.scss +209 -0
  175. package/scss/themes/boilerplate.scss +181 -0
  176. package/scss/themes/cupertino-dark.scss +238 -0
  177. package/scss/themes/cupertino-light.scss +266 -0
  178. package/scss/themes/cupertino.scss +194 -0
  179. package/scss/themes/glass-dark.scss +238 -0
  180. package/scss/themes/glass-light.scss +266 -0
  181. package/scss/themes/glass.scss +220 -0
  182. package/scss/themes/graphite-dark.scss +260 -0
  183. package/scss/themes/graphite-light.scss +228 -0
  184. package/scss/themes/graphite.scss +182 -0
  185. package/scss/themes/press-dark.scss +237 -0
  186. package/scss/themes/press-light.scss +273 -0
  187. package/scss/themes/press.scss +241 -0
  188. package/scss/themes/prism-dark.scss +217 -0
  189. package/scss/themes/prism-light.scss +220 -0
  190. package/scss/themes/prism.scss +190 -0
  191. package/scss/themes/sketchbook-dark.scss +231 -0
  192. package/scss/themes/sketchbook-light.scss +231 -0
  193. package/scss/themes/sketchbook.scss +178 -0
  194. package/scss/themes/terminal-dark.scss +267 -0
  195. package/scss/themes/terminal-light.scss +175 -0
  196. package/scss/themes/terminal.scss +185 -0
  197. package/scss/tokens.scss +23 -0
  198. package/scss/utilities-only.scss +4 -0
package/scss/api.scss ADDED
@@ -0,0 +1,61 @@
1
+ // ============================================================================
2
+ // API - Authoring Barrel (zero-emit)
3
+ // ============================================================================
4
+ // Import this in a COMPONENT stylesheet to reach the whole mixin/function API
5
+ // WITHOUT emitting any CSS. Nothing is printed until you actually call a mixin.
6
+ //
7
+ // This is the entry point for per-component styles (e.g. Card.module.scss):
8
+ //
9
+ // @use 'css-is-awesome/api' as cia;
10
+ //
11
+ // .card {
12
+ // background: cia.color(surface-default);
13
+ // padding: cia.space(4);
14
+ // border-radius: cia.radius(md);
15
+ // @include cia.card-base($shadow: 2);
16
+ // }
17
+ //
18
+ // It emits ZERO bytes and NO `:root` block, so it is safe inside a
19
+ // `.module.scss` under Next.js CSS Modules "pure" mode.
20
+ //
21
+ // The GLOBAL tokens (`:root { --… }`) and animation keyframes are emitted ONCE
22
+ // at your app root by a separate import — the CSS bundle (`css-is-awesome`) or
23
+ // a theme stylesheet. Tokens + keyframes live at the root; each component only
24
+ // pulls in the mixins here. `cia.animate(fade-in)` emits an `animation-name`
25
+ // that references the globally-defined keyframe — the keyframe itself is NOT
26
+ // re-emitted per component (that is why `animations-utilities` is deliberately
27
+ // NOT forwarded here; see `_index.scss` for the kitchen-sink barrel).
28
+ //
29
+ // See also:
30
+ // - `css-is-awesome` → the CSS bundle (emits tokens + base, use once)
31
+ // - `css-is-awesome/tokens` → tokens-only CSS
32
+ // - `css-is-awesome/api` → THIS file: mixins/functions, zero emit
33
+ // ============================================================================
34
+
35
+ @use './system' as s;
36
+ @use './theme' as t;
37
+
38
+ // Forward the full authoring API. Every entry below is zero-emit: mixins and
39
+ // functions print nothing until called; token maps are compile-time only.
40
+ @forward './system' as sys-*;
41
+ @forward './theme' as theme-*;
42
+ @forward './generator';
43
+ @forward './mixins'; // No prefix - main API
44
+ @forward './layout'; // Layout mixins (grid, wrap, page)
45
+ @forward './icons' as icon-*; // Icon mixins with prefix
46
+ @forward './animations'; // animate() + animate-on() mixins (CSS-Modules-safe)
47
+ @forward './components'; // Component mixins (btn, card, nav, etc.)
48
+
49
+ // NOTE: './animations-utilities' (keyframes + .cia-anim-* classes + global
50
+ // reduced-motion safety net) is intentionally NOT forwarded here — it emits
51
+ // CSS and belongs in the global bundle, loaded once.
52
+
53
+ // Re-export key maps for advanced usage (variable declarations — no CSS).
54
+ $spacing: s.$spacing;
55
+ $breakpoints: s.$breakpoints;
56
+ $z-layers: s.$z-layers;
57
+ $font-sizes: s.$font-sizes;
58
+ $theme-brand: t.$theme-brand;
59
+ $theme-light: t.$theme-light;
60
+ $theme-dark: t.$theme-dark;
61
+ $theme-radius: t.$theme-radius;
@@ -0,0 +1,72 @@
1
+ // ============================================================================
2
+ // ACCORDION
3
+ // ============================================================================
4
+ // Native <details name="..."> mutual exclusion. Zero JavaScript.
5
+ //
6
+ // Usage (mixin — the primary API):
7
+ // .faq-item { @include accordion; }
8
+ //
9
+ // Usage (utility class, opt-in convenience):
10
+ // <details name="faq" class="cia-accordion">
11
+ // <summary>Question</summary>
12
+ // <div>Answer</div>
13
+ // </details>
14
+ //
15
+ // Multiple <details name="faq"> at the same DOM level auto-close siblings
16
+ // when one opens. Native browser behavior; no JS, no script tag, no hydration.
17
+ // Baseline since 2024 (Chrome 120, Firefox 130, Safari 17.6).
18
+ //
19
+ // A11y: <details>/<summary> is keyboard-accessible by default (Tab + Space).
20
+ // focus-ring routed through m.focus-ring for consistency.
21
+ // ============================================================================
22
+ @use '../mixins' as m;
23
+
24
+ @mixin accordion {
25
+ border: 1px solid m.color(border-default);
26
+ border-radius: m.radius(md);
27
+ background: m.color(surface-default);
28
+ overflow: hidden;
29
+
30
+ & + & { margin-block-start: m.space(2); }
31
+
32
+ > summary {
33
+ cursor: pointer;
34
+ padding: m.space(3) m.space(4);
35
+ font-weight: m.font-weight(medium);
36
+ color: m.color(text-primary);
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: space-between;
40
+ gap: m.space(2);
41
+ list-style: none;
42
+ min-height: var(--touch-target-min, 24px); /* WCAG 2.2 SC 2.5.8 */
43
+ @include m.transition(background-color);
44
+
45
+ &::-webkit-details-marker { display: none; } /* Safari */
46
+
47
+ &::after {
48
+ content: '';
49
+ width: 0.55em;
50
+ height: 0.55em;
51
+ border-right: 2px solid currentColor;
52
+ border-bottom: 2px solid currentColor;
53
+ transform: rotate(45deg);
54
+ transition: transform var(--duration-fast, 180ms) var(--ease, ease);
55
+ flex-shrink: 0;
56
+ }
57
+
58
+ &:hover { background: m.color(interactive-hover); }
59
+
60
+ @include m.focus-ring;
61
+ }
62
+
63
+ &[open] > summary::after {
64
+ transform: rotate(-135deg);
65
+ }
66
+
67
+ > :not(summary) {
68
+ padding: m.space(3) m.space(4);
69
+ padding-block-start: 0;
70
+ color: m.color(text-secondary);
71
+ }
72
+ }
@@ -0,0 +1,171 @@
1
+ // ============================================================================
2
+ // BUTTONS
3
+ // ============================================================================
4
+ // Mixin-first button system. One router (`btn`) covers every variant.
5
+ // Each variant is a small private mixin; the router stays small no matter
6
+ // how many variants you add.
7
+ //
8
+ // Numeric scale keys are the source of truth (1..9). T-shirt keys still
9
+ // work as aliases — both of the following produce identical output:
10
+ // .btn-small-numeric { @include btn-base($py: 1, $px: 2, $font-size: 2); }
11
+ // .btn-small-tshirt { @include btn-base($py: xs, $px: sm, $font-size: sm); }
12
+ //
13
+ // Usage:
14
+ // .btn { @include btn-base; }
15
+ // .btn-cta { @include btn(primary, $px: 6, $r: full); }
16
+ // .btn-warn { @include btn(warning); }
17
+ // .btn-edit { @include btn(outline) { letter-spacing: 0.05em; } }
18
+ //
19
+ // To add a new variant: write a `_btn-<name>` private mixin below and add
20
+ // one branch to the router. Status variants (info/success/warning/error)
21
+ // are FREE — they share `_btn-status` via token interpolation.
22
+ // ============================================================================
23
+ @use 'sass:map';
24
+ @use '../theme' as t;
25
+ @use '../system' as s;
26
+ @use '../mixins' as m;
27
+
28
+ // ----------------------------------------------------------------------------
29
+ // BASE — shared structural rules (display, padding, radius, focus-ring, …)
30
+ // ----------------------------------------------------------------------------
31
+ @mixin btn-base(
32
+ $py: 1,
33
+ $px: 4,
34
+ $r: md,
35
+ $font-weight: medium,
36
+ $font-size: null
37
+ ) {
38
+ @include m.button-reset;
39
+ display: inline-flex;
40
+ align-items: center;
41
+ gap: m.space(1);
42
+ min-height: var(--touch-target-min, 24px); /* WCAG 2.2 SC 2.5.8 */
43
+ font-weight: var(--btn-font-weight, #{m.font-weight($font-weight)});
44
+ padding: var(--btn-padding-y, #{m.space($py)}) var(--btn-padding-x, #{m.space($px)});
45
+ border-radius: var(--btn-radius, #{m.radius($r)});
46
+ @include m.transition(background-color, border-color, color, box-shadow);
47
+ @include m.focus-ring;
48
+
49
+ @if $font-size {
50
+ font-size: var(--btn-font-size, #{m.font-size($font-size)});
51
+ }
52
+ }
53
+
54
+ // ----------------------------------------------------------------------------
55
+ // ROUTER — btn($variant)
56
+ // ----------------------------------------------------------------------------
57
+ // One mixin handles every variant. Defaults come from the theme contract;
58
+ // every token is overridable via named args. `$args...` forwards to
59
+ // btn-base for sizing (`$py`, `$px`, `$r`, `$font-weight`, `$font-size`).
60
+ @mixin btn(
61
+ $variant,
62
+ $bg: null, $bg-hover: null, $bg-active: null,
63
+ $color: null, $border: null,
64
+ $args...
65
+ ) {
66
+ @include btn-base($args...);
67
+
68
+ @if $variant == primary { @include _btn-solid(action-primary-default, action-primary-hover, action-primary-active, $bg, $bg-hover, $bg-active, $color); } @else if $variant == secondary { @include _btn-solid(action-secondary-default, action-secondary-hover, action-secondary-active, $bg, $bg-hover, $bg-active, $color); } @else if $variant == outline { @include _btn-outline($color, $border); } @else if $variant == ghost { @include _btn-ghost($color); } @else if $variant == info or
69
+ $variant == success or
70
+ $variant == warning or
71
+ $variant == error { @include _btn-status($variant, $bg, $border, $color); } @else if $variant == disabled { @include _btn-disabled($bg, $border); } @else if $variant == action { @include _btn-status(info, $bg, $border, $color); }
72
+ // back-compat alias for old buttonold
73
+ @else {
74
+ @error "btn(): unknown variant '#{$variant}'. Expected one of: primary, secondary, outline, ghost, info, success, warning, error, disabled, action.";
75
+ }
76
+
77
+ @content;
78
+ }
79
+
80
+ // ----------------------------------------------------------------------------
81
+ // PER-VARIANT PRIVATE MIXINS — small, local, easy to grow.
82
+ // ----------------------------------------------------------------------------
83
+
84
+ // Solid variants — same shape, different default tokens. Used by primary,
85
+ // secondary, and any future "filled" variant (e.g. tertiary).
86
+ //
87
+ // State cascade: when consumer passes only $bg (custom palette), hover +
88
+ // active derive from $bg via the same color-mix(in oklch) formula m.states()
89
+ // uses (12% hover, 20% active, light-dark-aware). Explicit $bg-hover /
90
+ // $bg-active still take precedence when supplied. No $bg = original behavior
91
+ // (use $hover / $active token defaults).
92
+ @mixin _btn-solid(
93
+ $default, $hover, $active,
94
+ $bg: null, $bg-hover: null, $bg-active: null, $color: null
95
+ ) {
96
+ background: m.color($bg or $default);
97
+ color: m.color($color or text-inverse);
98
+
99
+ &:hover {
100
+ @if $bg-hover {
101
+ background: m.color($bg-hover);
102
+ } @else if $bg {
103
+ background: color-mix(in oklch, m.color($bg), light-dark(black, white) 12%);
104
+ } @else {
105
+ background: m.color($hover);
106
+ }
107
+ }
108
+
109
+ &:active {
110
+ @if $bg-active {
111
+ background: m.color($bg-active);
112
+ } @else if $bg {
113
+ background: color-mix(in oklch, m.color($bg), light-dark(black, white) 20%);
114
+ } @else {
115
+ background: m.color($active);
116
+ }
117
+ }
118
+
119
+ &:disabled, &[disabled] { @include m.disabled; }
120
+ }
121
+
122
+ // Outline variant — transparent bg, visible border, hover bg + border swap.
123
+ @mixin _btn-outline($color: null, $border: null) {
124
+ background: transparent;
125
+ border: 1px solid m.color($border or border-default);
126
+ color: m.color($color or text-primary);
127
+ &:hover {
128
+ background: m.color(interactive-hover);
129
+ border-color: m.color(border-focus);
130
+ }
131
+ &:active { background: m.color(interactive-active); }
132
+ &:disabled, &[disabled] { @include m.disabled; }
133
+ }
134
+
135
+ // Ghost variant — transparent bg, no border, hover bg only.
136
+ @mixin _btn-ghost($color: null) {
137
+ background: transparent;
138
+ color: m.color($color or text-primary);
139
+ &:hover { background: m.color(interactive-hover); }
140
+ &:active { background: m.color(interactive-active); }
141
+ &:disabled, &[disabled] { @include m.disabled; }
142
+ }
143
+
144
+ // Status variants — token interpolation. Adding a new status is FREE if the
145
+ // tokens exist in the contract: <status>-subtle, <status>-default, <status>-text.
146
+ @mixin _btn-status($status, $bg: null, $border: null, $color: null) {
147
+ background: m.color($bg or #{$status}-subtle);
148
+ border: 1px solid m.color($border or #{$status}-default);
149
+ color: m.color($color or #{$status}-text);
150
+ }
151
+
152
+ // Disabled state — looks disabled AND disables interaction.
153
+ @mixin _btn-disabled($bg: null, $border: null) {
154
+ background: m.color($bg or surface-muted);
155
+ border: 1px solid m.color($border or border-default);
156
+ @include m.disabled;
157
+ }
158
+
159
+ // ----------------------------------------------------------------------------
160
+ // ICON BUTTON — different shape (square, no padding system), kept separate.
161
+ // ----------------------------------------------------------------------------
162
+ @mixin btn-icon($size: 2.5rem, $r: md) {
163
+ @include m.button-reset;
164
+ @include m.flex($justify: center);
165
+ width: $size;
166
+ height: $size;
167
+ border-radius: m.radius($r);
168
+ @include m.transition(background-color, color);
169
+ @include m.focus-ring;
170
+ @include m.interactive;
171
+ }
@@ -0,0 +1,69 @@
1
+ // ============================================================================
2
+ // COPY BUTTON
3
+ // ============================================================================
4
+ // Reusable copy-to-clipboard button. Decoupled from any parent context;
5
+ // consumer wires it to any element via `data-copy-target="#id"`.
6
+ //
7
+ // Usage (mixin — primary API):
8
+ // .doc-copy { @include copy-button; }
9
+ //
10
+ // Usage (utility class):
11
+ // <pre id="snippet">…</pre>
12
+ // <button data-copy-target="#snippet" class="cia-copy-button">Copy</button>
13
+ //
14
+ // cia ships ZERO JavaScript in the npm package. Wire your own click handler
15
+ // to read `data-copy-target` and call navigator.clipboard.writeText().
16
+ //
17
+ // Copy-paste recipe + the framework-free shim are documented at
18
+ // /docs/recipes/copy-button on the cia site. A separate add-on package
19
+ // (@cia/copy-button) is on the v1.x roadmap.
20
+ //
21
+ // Toast variant: pair the button with <div popover="hint" class="cia-copy-toast">
22
+ // and add `popovertarget="..."` to the button. Native popover handles show;
23
+ // your handler calls hidePopover() after a timeout for auto-hide.
24
+ // ============================================================================
25
+ @use '../mixins' as m;
26
+ @use './overlay' as o;
27
+
28
+ @mixin copy-button {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: m.space(1);
32
+ padding: m.space(1) m.space(2);
33
+ min-height: var(--touch-target-min, 24px); /* WCAG 2.2 SC 2.5.8 */
34
+ font-family: m.font-family(mono);
35
+ font-size: m.font-size(2);
36
+ line-height: 1.2;
37
+ letter-spacing: 0.04em;
38
+ color: m.color(text-secondary);
39
+ background: m.color(surface-raised);
40
+ border: 1px solid m.color(border-default);
41
+ border-radius: m.radius(sm);
42
+ cursor: pointer;
43
+ @include m.transition(background-color, color, border-color);
44
+ @include m.focus-ring;
45
+
46
+ &:hover {
47
+ background: m.color(surface-emphasis);
48
+ color: m.color(text-primary);
49
+ border-color: m.color(border-emphasis);
50
+ }
51
+
52
+ /* Copied state — JS shim toggles data-copied="true" for 1.5s */
53
+ &[data-copied='true'] {
54
+ background: m.color(success-subtle);
55
+ color: m.color(success-text);
56
+ border-color: m.color(success-default);
57
+ }
58
+ }
59
+
60
+ // Toast variant — composes with the tooltip mixin from overlay.
61
+ // Used as a separate element triggered by the copy button via popovertarget.
62
+ @mixin copy-toast {
63
+ @include o.tooltip;
64
+ background: m.color(success-default);
65
+ color: m.color(text-inverse);
66
+ font-family: m.font-family(primary);
67
+ font-size: m.font-size(2);
68
+ white-space: normal;
69
+ }
@@ -0,0 +1,190 @@
1
+ // ============================================================================
2
+ // DATA DISPLAY — Table, Card, List, Badge, Avatar
3
+ // ============================================================================
4
+ @use 'sass:map';
5
+ @use '../theme' as t;
6
+ @use '../system' as s;
7
+ @use '../mixins' as m;
8
+
9
+ // ----------------------------------------------------------------------------
10
+ // BADGE — moved here from _feedback.scss in v0.8.1 (alongside card / table).
11
+ // ----------------------------------------------------------------------------
12
+ @mixin badge-base(
13
+ $py: 1,
14
+ $px: 1,
15
+ $r: full,
16
+ $font-size: 1
17
+ ) {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ padding: var(--badge-padding-y, #{m.space($py)}) var(--badge-padding-x, #{m.space($px)});
21
+ border-radius: var(--badge-radius, #{m.radius($r)});
22
+ @include m.font(medium, $font-size);
23
+ }
24
+
25
+ @mixin badge($status: info) {
26
+ @include badge-base;
27
+ background: m.color(#{$status}-subtle);
28
+ color: m.color(#{$status}-text);
29
+ }
30
+
31
+ // ----------------------------------------------------------------------------
32
+ // TABLE
33
+ // ----------------------------------------------------------------------------
34
+ // Usage:
35
+ // table { @include table-base; }
36
+ // table { @include table-base($striped: true, $hover: true); }
37
+
38
+ @mixin table-base($striped: false, $hover: false, $bordered: false, $compact: false) {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ @include m.font(reg, 2);
42
+
43
+ th, td {
44
+ text-align: left;
45
+ padding: m.space(if(sass($compact): 1; else: 2)) m.space(if(sass($compact): 2; else: 4));
46
+ border-bottom: 1px solid m.color(border-default);
47
+ }
48
+
49
+ th {
50
+ @include m.font(semibold, 1);
51
+ color: m.color(text-secondary);
52
+ text-transform: uppercase;
53
+ letter-spacing: map.get(s.$letter-spacings, wide);
54
+ }
55
+
56
+ @if $bordered {
57
+ th, td { border: 1px solid m.color(border-default); }
58
+ }
59
+
60
+ @if $striped {
61
+ tbody tr:nth-child(even) {
62
+ background: m.color(surface-subtle);
63
+ }
64
+ }
65
+
66
+ @if $hover {
67
+ tbody tr {
68
+ @include m.transition(background-color);
69
+ &:hover { background: m.color(interactive-hover); }
70
+ }
71
+ }
72
+ }
73
+
74
+ // Responsive table wrapper
75
+ @mixin table-responsive {
76
+ width: 100%;
77
+ overflow-x: auto;
78
+ -webkit-overflow-scrolling: touch;
79
+ }
80
+
81
+ // ----------------------------------------------------------------------------
82
+ // CARD
83
+ // ----------------------------------------------------------------------------
84
+ @mixin card-base(
85
+ $p: 4,
86
+ $r: lg,
87
+ $shadow: 1,
88
+ $bg: surface-default,
89
+ $border: false
90
+ ) {
91
+ padding: var(--card-padding, #{m.space($p)});
92
+ border-radius: var(--card-radius, #{m.radius($r)});
93
+ background: m.color($bg);
94
+
95
+ @if $shadow and $shadow != none {
96
+ box-shadow: var(--card-shadow, #{m.shadow($shadow)});
97
+ }
98
+
99
+ @if $border {
100
+ border: var(--card-border, 1px solid #{m.color(border-default)});
101
+ }
102
+ }
103
+
104
+ @mixin card-header($pb: 2) {
105
+ padding-block-end: m.space($pb);
106
+ border-block-end: 1px solid m.color(border-default);
107
+ margin-block-end: m.space(4);
108
+ }
109
+
110
+ @mixin card-footer($pt: 2) {
111
+ padding-block-start: m.space($pt);
112
+ border-block-start: 1px solid m.color(border-default);
113
+ margin-block-start: m.space(4);
114
+ }
115
+
116
+ @mixin card-interactive {
117
+ cursor: pointer;
118
+ @include m.transition(box-shadow, transform);
119
+
120
+ &:hover {
121
+ box-shadow: m.shadow(2);
122
+ transform: translateY(-1px);
123
+ }
124
+
125
+ &:active {
126
+ transform: translateY(0);
127
+ }
128
+ }
129
+
130
+ // ----------------------------------------------------------------------------
131
+ // LIST
132
+ // ----------------------------------------------------------------------------
133
+ @mixin list-base($gap: 0, $dividers: false) {
134
+ @include m.list-reset;
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: m.space($gap);
138
+
139
+ @if $dividers and $gap == 0 {
140
+ > li + li { border-top: 1px solid m.color(border-default); }
141
+ }
142
+ }
143
+
144
+ // Usage:
145
+ // li { @include list-item; }
146
+ // li { @include list-item($interactive: true); }
147
+ @mixin list-item($py: 2, $px: 4, $interactive: false) {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: m.space(2);
151
+ padding: m.space($py) m.space($px);
152
+
153
+ @if $interactive {
154
+ cursor: pointer;
155
+ @include m.transition(background-color);
156
+ &:hover { background: m.color(interactive-hover); }
157
+ &:active { background: m.color(interactive-active); }
158
+ }
159
+ }
160
+
161
+ // ----------------------------------------------------------------------------
162
+ // AVATAR
163
+ // ----------------------------------------------------------------------------
164
+ @mixin avatar($size: 2.5rem, $r: full) {
165
+ width: $size;
166
+ height: $size;
167
+ border-radius: m.radius($r);
168
+ object-fit: cover;
169
+ flex-shrink: 0;
170
+ }
171
+
172
+ @mixin avatar-placeholder($size: 2.5rem, $r: full, $bg: surface-muted, $color: text-muted) {
173
+ @include m.flex($justify: center);
174
+ width: $size;
175
+ height: $size;
176
+ border-radius: m.radius($r);
177
+ background: m.color($bg);
178
+ color: m.color($color);
179
+ @include m.font(semibold, 2);
180
+ flex-shrink: 0;
181
+ }
182
+
183
+ @mixin avatar-group($overlap: -0.5rem) {
184
+ display: flex;
185
+
186
+ > * + * {
187
+ margin-left: $overlap;
188
+ border: 2px solid m.color(surface-default);
189
+ }
190
+ }