@adrienlcp/styles 0.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.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/package.json +58 -0
- package/src/_breakpoints.sass +13 -0
- package/src/_focus.sass +32 -0
- package/src/_fonts.sass +16 -0
- package/src/reduced-motion.css +8 -0
- package/src/reset.css +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adrien Lacourpaille
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @adrienlcp/styles
|
|
2
|
+
|
|
3
|
+
The styling floor every app of mine starts from: a reset, a reduced-motion
|
|
4
|
+
switch, and the Sass mixins that CSS cannot write yet. Plain `.css` for what
|
|
5
|
+
CSS can say, `.sass` only for what it cannot — a custom property is not allowed
|
|
6
|
+
in a media query or in `unicode-range`, and CSS has no mixins.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add @adrienlcp/styles
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Plain CSS
|
|
13
|
+
|
|
14
|
+
| File | Does |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `reset.css` | Box sizing, zeroed margins and paddings, inherited fonts on controls, bare buttons and lists, balanced headings, pretty paragraphs. Inside `@layer reset` |
|
|
17
|
+
| `reduced-motion.css` | Collapses `--transition-fast`, `--transition-base` and `--transition-slow` to `0ms` under `prefers-reduced-motion: reduce`. Unlayered, so it beats the tokens wherever they are defined |
|
|
18
|
+
|
|
19
|
+
Import them once — from JavaScript, or from the global stylesheet in Sass:
|
|
20
|
+
|
|
21
|
+
```sass
|
|
22
|
+
@use '@adrienlcp/styles/reset.css'
|
|
23
|
+
@use '@adrienlcp/styles/reduced-motion.css'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`reset.css` declares a layer: set the order in the document head before any
|
|
27
|
+
stylesheet loads, so the reset stays under every component rule —
|
|
28
|
+
`<style>@layer reset, tokens, base, components;</style>`.
|
|
29
|
+
|
|
30
|
+
## Sass
|
|
31
|
+
|
|
32
|
+
Resolved through the `sass` export condition, which Vite reads; with the Sass
|
|
33
|
+
CLI, use `pkg:@adrienlcp/styles/focus` and `--pkg-importer=node`.
|
|
34
|
+
|
|
35
|
+
### `breakpoints`
|
|
36
|
+
|
|
37
|
+
```sass
|
|
38
|
+
@use '@adrienlcp/styles/breakpoints'
|
|
39
|
+
|
|
40
|
+
.page
|
|
41
|
+
@include breakpoints.wide
|
|
42
|
+
grid-template-columns: 1fr 1fr
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`$wide-screen` is `900px`; `wide` is `width >= $wide-screen` and `narrow` its
|
|
46
|
+
exact complement. Another value: `@use '@adrienlcp/styles/breakpoints' with
|
|
47
|
+
($wide-screen: 1024px)`.
|
|
48
|
+
|
|
49
|
+
### `focus`
|
|
50
|
+
|
|
51
|
+
`ring` and `ring-inset` draw the outline on `[data-focus-visible]` (react-aria)
|
|
52
|
+
and `:focus-visible` (a native element). `hovered` wraps a hover style in
|
|
53
|
+
`(hover: hover) and (pointer: fine)`, since a touch screen replays hover after a
|
|
54
|
+
tap and leaves it stuck; `$except` skips a state.
|
|
55
|
+
|
|
56
|
+
```sass
|
|
57
|
+
@use '@adrienlcp/styles/focus'
|
|
58
|
+
|
|
59
|
+
.button
|
|
60
|
+
@include focus.ring
|
|
61
|
+
|
|
62
|
+
@include focus.hovered($except: '[data-disabled]')
|
|
63
|
+
background: var(--hover)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Configure the ring once, in the app's own `_focus.sass`, and have components
|
|
67
|
+
`@use` that file:
|
|
68
|
+
|
|
69
|
+
```sass
|
|
70
|
+
@forward '@adrienlcp/styles/focus' with ($ring-color: var(--focus), $ring-offset: 3px, $ring-width: 3px)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `fonts`
|
|
74
|
+
|
|
75
|
+
`$latin` and `$latin-ext` are the unicode ranges Google Fonts cuts a Latin face
|
|
76
|
+
into; `font-face` declares one self-hosted `woff2` file.
|
|
77
|
+
|
|
78
|
+
```sass
|
|
79
|
+
@use '@adrienlcp/styles/fonts'
|
|
80
|
+
|
|
81
|
+
@include fonts.font-face('Archivo', '/fonts/archivo-latin.woff2', fonts.$latin, $weight: 100 900, $stretch: 62% 125%)
|
|
82
|
+
@include fonts.font-face('Archivo', '/fonts/archivo-latin-ext.woff2', fonts.$latin-ext, $weight: 100 900, $stretch: 62% 125%)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`$weight`, `$style` (`normal`), `$stretch` (left out) and `$display` (`swap`)
|
|
86
|
+
are optional.
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "Adrien Lacourpaille",
|
|
3
|
+
"bugs": "https://github.com/AdrienLcp/packages/issues",
|
|
4
|
+
"description": "A reset, a reduced-motion switch, and Sass mixins for self-hosted fonts, a breakpoint, focus rings and pointer-only hover",
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"sass": "^1.105.0"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
"./breakpoints": {
|
|
10
|
+
"sass": "./src/_breakpoints.sass"
|
|
11
|
+
},
|
|
12
|
+
"./focus": {
|
|
13
|
+
"sass": "./src/_focus.sass"
|
|
14
|
+
},
|
|
15
|
+
"./fonts": {
|
|
16
|
+
"sass": "./src/_fonts.sass"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
"./reduced-motion.css": "./src/reduced-motion.css",
|
|
20
|
+
"./reset.css": "./src/reset.css"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"src/*.css",
|
|
24
|
+
"src/*.sass"
|
|
25
|
+
],
|
|
26
|
+
"homepage": "https://github.com/AdrienLcp/packages/tree/main/packages/styles#readme",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"css",
|
|
29
|
+
"focus-visible",
|
|
30
|
+
"font-face",
|
|
31
|
+
"reset",
|
|
32
|
+
"sass"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"name": "@adrienlcp/styles",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"sass": ">=1.80.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"sass": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"directory": "packages/styles",
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/AdrienLcp/packages.git"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest --watch"
|
|
52
|
+
},
|
|
53
|
+
"sideEffects": [
|
|
54
|
+
"*.css"
|
|
55
|
+
],
|
|
56
|
+
"type": "module",
|
|
57
|
+
"version": "0.1.0"
|
|
58
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// Where a screen stops being a phone's. A media query cannot read a custom
|
|
2
|
+
/// property, so the breakpoint is a Sass value; override it with
|
|
3
|
+
/// `@use '@adrienlcp/styles/breakpoints' with ($wide-screen: 1024px)`.
|
|
4
|
+
$wide-screen: 900px !default
|
|
5
|
+
|
|
6
|
+
/// `wide` and `narrow` are exact complements: every width is one or the other.
|
|
7
|
+
@mixin wide
|
|
8
|
+
@media (width >= $wide-screen)
|
|
9
|
+
@content
|
|
10
|
+
|
|
11
|
+
@mixin narrow
|
|
12
|
+
@media (width < $wide-screen)
|
|
13
|
+
@content
|
package/src/_focus.sass
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// Configure once, in the app's own `_focus.sass`:
|
|
2
|
+
/// `@forward '@adrienlcp/styles/focus' with ($ring-color: var(--focus) !default)`.
|
|
3
|
+
$ring-width: 2px !default
|
|
4
|
+
$ring-color: currentColor !default
|
|
5
|
+
$ring-offset: 3px !default
|
|
6
|
+
|
|
7
|
+
/// react-aria stamps `data-focus-visible` on keyboard focus only; the
|
|
8
|
+
/// `:focus-visible` twin covers a native element.
|
|
9
|
+
@mixin ring
|
|
10
|
+
&[data-focus-visible],
|
|
11
|
+
&:focus-visible
|
|
12
|
+
outline: $ring-width solid $ring-color
|
|
13
|
+
outline-offset: $ring-offset
|
|
14
|
+
|
|
15
|
+
/// The ring drawn inside the box, for an element a clipping ancestor would cut.
|
|
16
|
+
@mixin ring-inset
|
|
17
|
+
&[data-focus-visible],
|
|
18
|
+
&:focus-visible
|
|
19
|
+
outline: $ring-width solid $ring-color
|
|
20
|
+
outline-offset: calc(-1 * $ring-width)
|
|
21
|
+
|
|
22
|
+
/// Only a real pointer hovers: a touch screen replays hover after a tap and
|
|
23
|
+
/// leaves it stuck until the next one. `$except` skips a state, as in
|
|
24
|
+
/// `hovered($except: '[data-disabled]')`.
|
|
25
|
+
@mixin hovered($except: null)
|
|
26
|
+
@media (hover: hover) and (pointer: fine)
|
|
27
|
+
@if $except
|
|
28
|
+
&[data-hovered]:not(#{$except})
|
|
29
|
+
@content
|
|
30
|
+
@else
|
|
31
|
+
&[data-hovered]
|
|
32
|
+
@content
|
package/src/_fonts.sass
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// The two subsets Google Fonts cuts a Latin face into: a page in English or
|
|
2
|
+
/// French downloads the first file only.
|
|
3
|
+
$latin: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD
|
|
4
|
+
$latin-ext: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF
|
|
5
|
+
|
|
6
|
+
/// One self-hosted `woff2` file. `$weight` and `$stretch` take a range for a
|
|
7
|
+
/// variable face.
|
|
8
|
+
@mixin font-face($family, $url, $unicode-range, $weight: 400, $style: normal, $stretch: null, $display: swap)
|
|
9
|
+
@font-face
|
|
10
|
+
font-display: $display
|
|
11
|
+
font-family: $family
|
|
12
|
+
font-stretch: $stretch
|
|
13
|
+
font-style: $style
|
|
14
|
+
font-weight: $weight
|
|
15
|
+
src: url($url) format('woff2')
|
|
16
|
+
unicode-range: $unicode-range
|
package/src/reset.css
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
@layer reset {
|
|
2
|
+
*,
|
|
3
|
+
*::before,
|
|
4
|
+
*::after {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
* {
|
|
9
|
+
padding: 0;
|
|
10
|
+
margin: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html {
|
|
14
|
+
-webkit-text-size-adjust: 100%;
|
|
15
|
+
text-size-adjust: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
-webkit-font-smoothing: antialiased;
|
|
20
|
+
line-height: 1.5;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
img,
|
|
24
|
+
picture,
|
|
25
|
+
svg,
|
|
26
|
+
canvas,
|
|
27
|
+
video {
|
|
28
|
+
display: block;
|
|
29
|
+
max-width: 100%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
button,
|
|
33
|
+
input,
|
|
34
|
+
select,
|
|
35
|
+
textarea {
|
|
36
|
+
font: inherit;
|
|
37
|
+
color: inherit;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
button {
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
background: none;
|
|
43
|
+
border: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
h1,
|
|
47
|
+
h2,
|
|
48
|
+
h3,
|
|
49
|
+
h4 {
|
|
50
|
+
text-wrap: balance;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
p {
|
|
54
|
+
text-wrap: pretty;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ul,
|
|
58
|
+
ol {
|
|
59
|
+
list-style: none;
|
|
60
|
+
}
|
|
61
|
+
}
|