@uncinq/css-components 1.27.4 → 1.29.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <img width="1280" height="640" alt="share-css-components" src="https://github.com/user-attachments/assets/3d74df8a-e7bf-4ba5-8529-99cfef0bc176" />
6
6
 
7
- 30 components and 1 utility, written as plain CSS, reading their values from [@uncinq/component-tokens](https://github.com/uncinq/component-tokens) and adding none of their own.
7
+ 31 components and 1 utility, written as plain CSS, reading their values from [@uncinq/component-tokens](https://github.com/uncinq/component-tokens) and adding none of their own.
8
8
 
9
9
  ## Where this sits
10
10
 
@@ -55,7 +55,7 @@ The failure is silent. No error is raised, the page simply stays at its mobile v
55
55
  | --- | --- |
56
56
  | Buttons | `.btn` with colour, size and style variants, plus `btn-close`, `btn-menu`, `btn-search`, `btn-filter`, `btn-share`, `btn-toc`, `btn-toggle-video` |
57
57
  | Overlays | `modal`, `drawer`, `dropdown`, and the shared panel skeleton |
58
- | Content | `alert`, `badge`, `banner`, `card`, `items`, `list`, `media`, `push`, `surtitle` |
58
+ | Content | `alert`, `badge`, `banner`, `card`, `items`, `list`, `media`, `push`, `rating`, `surtitle` |
59
59
  | Navigation | `nav`, `nav-title`, `nav-accessibility`, `breadcrumb`, `pagination` |
60
60
  | Embeds | `embed`, `video`, `map` |
61
61
  | Forms | `form`, `form-check` and the layout helpers |
@@ -85,7 +85,7 @@ It is also versioned with the code in [`docs/`](docs/), one page per component,
85
85
  - [Cascade layers](docs/cascade-layers.md) — the two layers, and the `:where()` convention
86
86
  - [Buttons](docs/buttons/) — [`.btn`](docs/buttons/btn.md) and the seven specialised buttons
87
87
  - [Overlays](docs/overlays/) — [the panel skeleton](docs/overlays/panel.md), [`.modal`](docs/overlays/modal.md), [`.drawer`](docs/overlays/drawer.md), [`.dropdown`](docs/overlays/dropdown.md)
88
- - [Content](docs/content/) — [`.alert`](docs/content/alert.md), [`.badge`](docs/content/badge.md), [`.banner`](docs/content/banner.md), [`.card`](docs/content/card.md), [`.items`](docs/content/items.md), [`.list`](docs/content/list.md), [`.media`](docs/content/media.md), [`.surtitle`](docs/content/surtitle.md)
88
+ - [Content](docs/content/) — [`.alert`](docs/content/alert.md), [`.badge`](docs/content/badge.md), [`.banner`](docs/content/banner.md), [`.card`](docs/content/card.md), [`.items`](docs/content/items.md), [`.list`](docs/content/list.md), [`.media`](docs/content/media.md), [`.rating`](docs/content/rating.md), [`.surtitle`](docs/content/surtitle.md)
89
89
  - [Navigation](docs/navigation/) — [`.nav`](docs/navigation/nav.md), [`.nav-title`](docs/navigation/nav-title.md), [skip links](docs/navigation/nav-accessibility.md), [breadcrumb](docs/navigation/breadcrumb.md), [pagination](docs/navigation/pagination.md)
90
90
  - [Embeds](docs/embeds/) — [`.embed`](docs/embeds/embed.md), [`.video`](docs/embeds/video.md), [`.map`](docs/embeds/map.md)
91
91
  - [Forms](docs/forms/) — [`.form`](docs/forms/form.md), [`.form-check`](docs/forms/form-check.md)
@@ -0,0 +1,41 @@
1
+ /* components/rating.css */
2
+ /*
3
+ * .rating — star rating out of 5, fractional values included (4.8).
4
+ *
5
+ * Structure:
6
+ * div.rating
7
+ * span.rating-star (×5, style="--rating-fill: 0–100%")
8
+ * .icon (the theme's star glyph, painted as a mask)
9
+ * span.visually-hidden (e.g. "Rating: 4.8 out of 5")
10
+ *
11
+ * The icon is a mask, so its background is what shows through the glyph.
12
+ * A hard-stop gradient paints each star in the active colour up to its
13
+ * --rating-fill and in the empty colour after it.
14
+ */
15
+ @layer components {
16
+ .rating {
17
+ --icon-size: var(--rating-size);
18
+
19
+ align-items: center;
20
+ display: flex;
21
+ gap: var(--rating-gap);
22
+ justify-content: var(--rating-justify-content);
23
+ }
24
+
25
+ .rating-star {
26
+ display: flex;
27
+
28
+ .icon {
29
+ background: linear-gradient(
30
+ to right,
31
+ var(--rating-color-active) var(--rating-fill, 0%),
32
+ var(--rating-color-empty) var(--rating-fill, 0%)
33
+ );
34
+
35
+ /* RTL: stars fill from the right */
36
+ &:dir(rtl) {
37
+ scale: -1 1;
38
+ }
39
+ }
40
+ }
41
+ }
package/css/index.css CHANGED
@@ -27,6 +27,7 @@
27
27
  @import 'components/pagination.css';
28
28
  @import 'components/panel.css';
29
29
  @import 'components/push.css';
30
+ @import 'components/rating.css';
30
31
  @import 'components/surtitle.css';
31
32
  @import 'components/video.css';
32
33
 
@@ -68,6 +68,12 @@
68
68
  overflow-x: auto;
69
69
  overflow-y: hidden;
70
70
  padding-inline: var(--container-bleed, var(--gutter));
71
+ /* The row is the containing block of anything absolute inside it.
72
+ overflow only clips what it contains: without this, an absolute
73
+ descendant of an unpositioned item (the .visually-hidden label of a
74
+ .rating, say) anchors to an ancestor outside the row, escapes the clip
75
+ and widens the whole page by the length of the row. */
76
+ position: relative;
71
77
  scroll-snap-type: x mandatory;
72
78
 
73
79
  > * {
@@ -89,6 +95,7 @@
89
95
  overflow-x: revert-layer;
90
96
  overflow-y: revert-layer;
91
97
  padding-inline: revert-layer;
98
+ position: revert-layer;
92
99
  scroll-snap-type: revert-layer;
93
100
 
94
101
  > * {
@@ -107,6 +114,7 @@
107
114
  overflow-x: revert-layer;
108
115
  overflow-y: revert-layer;
109
116
  padding-inline: revert-layer;
117
+ position: revert-layer;
110
118
  scroll-snap-type: revert-layer;
111
119
 
112
120
  > * {
@@ -125,6 +133,7 @@
125
133
  overflow-x: revert-layer;
126
134
  overflow-y: revert-layer;
127
135
  padding-inline: revert-layer;
136
+ position: revert-layer;
128
137
  scroll-snap-type: revert-layer;
129
138
 
130
139
  > * {
@@ -143,6 +152,7 @@
143
152
  overflow-x: revert-layer;
144
153
  overflow-y: revert-layer;
145
154
  padding-inline: revert-layer;
155
+ position: revert-layer;
146
156
  scroll-snap-type: revert-layer;
147
157
 
148
158
  > * {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  isIndex: false
3
3
  title: Content
4
- description: Alerts, badges, banners, cards, the items grid, labelled lists, media objects, pushes and surtitles.
4
+ description: Alerts, badges, banners, cards, the items grid, labelled lists, media objects, pushes, star ratings and surtitles.
5
5
  weight: 5
6
6
  icon: card-text
7
7
  ---
@@ -0,0 +1,68 @@
1
+ ---
2
+ isIndex: false
3
+ title: Rating
4
+ description: A star rating out of 5 with fractional values, how each star is filled through its icon mask, and what the markup has to provide.
5
+ weight: 10
6
+ icon: star-half
7
+ ---
8
+
9
+ A star rating out of 5, fractional values included: 4.8 shows four full stars and one filled to 80%.
10
+
11
+ ```html
12
+ <div class="rating">
13
+ <span class="rating-star" style="--rating-fill: 100%"><i class="icon icon-star" aria-hidden="true"></i></span>
14
+ <span class="rating-star" style="--rating-fill: 100%"><i class="icon icon-star" aria-hidden="true"></i></span>
15
+ <span class="rating-star" style="--rating-fill: 100%"><i class="icon icon-star" aria-hidden="true"></i></span>
16
+ <span class="rating-star" style="--rating-fill: 100%"><i class="icon icon-star" aria-hidden="true"></i></span>
17
+ <span class="rating-star" style="--rating-fill: 80%"><i class="icon icon-star" aria-hidden="true"></i></span>
18
+ <span class="visually-hidden">Rating: 4.8 out of 5</span>
19
+ </div>
20
+ ```
21
+
22
+ | Child | Role |
23
+ | --- | --- |
24
+ | `.rating-star` | One star, carrying its own fill in `--rating-fill`, from `0%` to `100%` |
25
+ | `.icon` | The star glyph, rendered by the theme |
26
+ | `.visually-hidden` | The value, read out by screen readers |
27
+
28
+ The fill is computed where the markup is written, not here: star *n* takes `(rating − n + 1) × 100%`, clamped between `0%` and `100%`. A star with no `--rating-fill` is empty.
29
+
30
+ ## The fill goes through the mask
31
+
32
+ This package ships no star. The `.icon` is the theme's, and the rating expects it to be a **mask**: an element whose `background` shows through the glyph, as the Hugolify icons render.
33
+
34
+ ```css
35
+ .rating-star .icon {
36
+ background: linear-gradient(
37
+ to right,
38
+ var(--rating-color-active) var(--rating-fill, 0%),
39
+ var(--rating-color-empty) var(--rating-fill, 0%)
40
+ );
41
+ }
42
+ ```
43
+
44
+ Both colour stops sit at the same position, so the gradient is a hard edge, not a blend: the glyph is in the active colour up to its fill and in the empty colour after it. With an outline icon set, such as Lucide, that is the outline which changes colour, not the inside of the star.
45
+
46
+ An icon inlined as an `<svg>` ignores the background and is not filled.
47
+
48
+ Under `:dir(rtl)` the glyph is mirrored, so stars fill from the right.
49
+
50
+ ## Size and alignment
51
+
52
+ The rating sets `--icon-size` from `--rating-size`, so the stars follow the rating rather than the font size around them.
53
+
54
+ It is a flex row aligned to the start. `--rating-justify-content` moves it, which is how a centred context, a quote for instance, centres its stars without a modifier:
55
+
56
+ ```css
57
+ .quote .rating {
58
+ --rating-justify-content: center;
59
+ }
60
+ ```
61
+
62
+ `--rating-gap` is `0` by default: the star glyph carries its own inner margin, and a gap would only widen the row.
63
+
64
+ ## Accessibility
65
+
66
+ The stars carry no meaning for assistive technology: each `.icon` is `aria-hidden`, and the value is given as text in a `.visually-hidden` span, in words rather than as a fraction.
67
+
68
+ The empty colour defaults to `--color-text-muted` rather than a border colour, so an empty star stays visible on a muted background such as a card.
@@ -2,7 +2,7 @@
2
2
  isIndex: false
3
3
  title: Surtitle
4
4
  description: An eyebrow label above a heading, tightened automatically when it sits inside a card.
5
- weight: 10
5
+ weight: 11
6
6
  icon: type-h2
7
7
  ---
8
8
 
package/docs/overview.md CHANGED
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  isIndex: false
3
3
  title: Overview
4
- description: The 30 components and 1 utility, how to install them, the import order inside the package, and what it deliberately leaves out.
4
+ description: The 31 components and 1 utility, how to install them, the import order inside the package, and what it deliberately leaves out.
5
5
  weight: 1
6
6
  icon: book
7
7
  ---
8
8
 
9
- `@uncinq/css-components` is the top of the stack: 30 components and 1 utility, written as plain CSS, reading their values from [@uncinq/component-tokens](../../component-tokens/) and adding no values of their own.
9
+ `@uncinq/css-components` is the top of the stack: 31 components and 1 utility, written as plain CSS, reading their values from [@uncinq/component-tokens](../../component-tokens/) and adding no values of their own.
10
10
 
11
11
  ```
12
12
  @uncinq/design-tokens primitive + semantic values
@@ -25,7 +25,7 @@ One page per component, grouped into six sections.
25
25
  | --- | --- |
26
26
  | [Buttons](../buttons/) | [`.btn`](../buttons/btn/), [`.btn-close`](../buttons/btn-close/), [`.btn-menu`](../buttons/btn-menu/), [`.btn-search`](../buttons/btn-search/), [`.btn-filter`](../buttons/btn-filter/), [`.btn-share`](../buttons/btn-share/), [`.btn-toc`](../buttons/btn-toc/), [`.btn-toggle-video`](../buttons/btn-toggle-video/) |
27
27
  | [Overlays](../overlays/) | [panel](../overlays/panel/), [`.modal`](../overlays/modal/), [`.drawer`](../overlays/drawer/), [`.dropdown`](../overlays/dropdown/) |
28
- | [Content](../content/) | [`.alert`](../content/alert/), [`.badge`](../content/badge/), [`.banner`](../content/banner/), [`.card`](../content/card/), [`.items`](../content/items/), [`.list`](../content/list/), [`.media`](../content/media/), [`.push`](../content/push/), [`.surtitle`](../content/surtitle/) |
28
+ | [Content](../content/) | [`.alert`](../content/alert/), [`.badge`](../content/badge/), [`.banner`](../content/banner/), [`.card`](../content/card/), [`.items`](../content/items/), [`.list`](../content/list/), [`.media`](../content/media/), [`.push`](../content/push/), [`.rating`](../content/rating/), [`.surtitle`](../content/surtitle/) |
29
29
  | [Navigation](../navigation/) | [`.nav`](../navigation/nav/), [`.nav-title`](../navigation/nav-title/), [`.nav-accessibility`](../navigation/nav-accessibility/), [`.breadcrumb-wrapper`](../navigation/breadcrumb/), [`.pagination`](../navigation/pagination/) |
30
30
  | [Embeds](../embeds/) | [`.embed`](../embeds/embed/), [`.video`](../embeds/video/), [`.map`](../embeds/map/) |
31
31
  | [Forms](../forms/) | [`.form`](../forms/form/), [`.form-check`](../forms/form-check/) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uncinq/css-components",
3
- "version": "1.27.4",
3
+ "version": "1.29.0",
4
4
  "description": "Framework-agnostic CSS component implementations.",
5
5
  "license": "MIT",
6
6
  "repository": {