benivo-ui-library 1.9.75 → 1.9.77
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 +22 -0
- package/accordion.less +167 -0
- package/activityFeed.less +394 -0
- package/alert.less +278 -0
- package/alertBanner.less +145 -0
- package/avatar.less +280 -0
- package/badge.less +2 -2
- package/benivo-ui-lib.less +34 -0
- package/breadcrumbs.less +92 -0
- package/button.less +327 -311
- package/checkIcon.less +47 -0
- package/checkItem.less +31 -0
- package/checkboxGroup.less +198 -0
- package/closeButton.less +70 -0
- package/copyInput.less +88 -52
- package/datePicker.less +505 -129
- package/emptyState.less +151 -0
- package/featuredIcon.less +13 -1
- package/fileCard.less +345 -0
- package/fileTypeIcon.less +12 -0
- package/fileUpload.less +268 -0
- package/flag.less +44 -0
- package/flagFeatureIcon.less +41 -0
- package/formSection.less +162 -0
- package/forms.less +30 -27
- package/heroInput.less +322 -0
- package/illustration.less +62 -0
- package/index.js +1 -1
- package/index.js.LICENSE.txt +1 -1
- package/input.less +208 -71
- package/menu.less +247 -0
- package/miButton.less +149 -0
- package/modal.less +226 -0
- package/notificationCard.less +202 -0
- package/notifications.less +13 -4
- package/optionList.less +102 -0
- package/package.json +10 -4
- package/pageIndicator.less +125 -0
- package/pagination.less +222 -0
- package/phoneNumber.less +56 -16
- package/progress.less +242 -0
- package/radiobutton.less +29 -0
- package/select.less +284 -55
- package/slider.less +98 -0
- package/starIcon.less +25 -0
- package/tabGroup.less +59 -12
- package/tabs.less +47 -9
- package/tag.less +252 -0
- package/tagsSelect.less +164 -76
- package/textarea.less +40 -32
- package/tileGeometry.less +12 -0
- package/tooltip.less +45 -20
- package/variables.less +122 -13
- package/verificationCode.less +142 -0
package/README.md
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
`benivo-ui-library` is a collection of react hooks and components for faster development.
|
|
6
6
|
|
|
7
|
+
Accessibility is tested automatically with jest-axe — run `npm run a11y:audit` and see
|
|
8
|
+
[docs/accessibility-testing.md](docs/accessibility-testing.md) for the enforcement model.
|
|
9
|
+
|
|
7
10
|
# Installation
|
|
8
11
|
|
|
9
12
|
```sh
|
|
@@ -36,3 +39,22 @@ function MyApp() {
|
|
|
36
39
|
);
|
|
37
40
|
}
|
|
38
41
|
```
|
|
42
|
+
|
|
43
|
+
# Publishing
|
|
44
|
+
|
|
45
|
+
Publishing auth comes from the `NPM_TOKEN` environment variable — never from a token
|
|
46
|
+
committed to this repo. Both `.npmrc` files reference it as `${NPM_TOKEN}`, which npm
|
|
47
|
+
expands at run time.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export NPM_TOKEN=<your granular npm publish token for benivo-ui-library>
|
|
51
|
+
npm run publish:lib
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`publish:lib` runs `check:npm-token` first and stops with a clear message if `NPM_TOKEN`
|
|
55
|
+
is unset, rather than failing part-way through a publish.
|
|
56
|
+
|
|
57
|
+
Use a **granular** npm access token scoped to the `benivo-ui-library` package only, with
|
|
58
|
+
publish rights and an expiry — not a classic automation token on the whole `benivo`
|
|
59
|
+
account. Keep it in your own environment or a CI secret. Do not add it to `.npmrc`,
|
|
60
|
+
`.storybook/.npmrc`, or any other tracked file.
|
package/accordion.less
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Accordion
|
|
3
|
+
//
|
|
4
|
+
// One row: a chevron, a title, and a panel that opens under it.
|
|
5
|
+
//
|
|
6
|
+
// padding border radius
|
|
7
|
+
// none — — —
|
|
8
|
+
// stroke 24px top 1px top —
|
|
9
|
+
// card 16px all 1px all 12px
|
|
10
|
+
//
|
|
11
|
+
// Only those three lines differ between the looks. Everything else — the 24px
|
|
12
|
+
// gap, the 18/28 title, the 16/24 body, the 8px above the panel — is shared.
|
|
13
|
+
//
|
|
14
|
+
// Figma also carries a Breakpoint property, which is not a variant here: the
|
|
15
|
+
// mobile frames are identical to the desktop ones apart from being narrower, so
|
|
16
|
+
// this reflows rather than switching on a prop.
|
|
17
|
+
//
|
|
18
|
+
//
|
|
19
|
+
// The group holds a set of rows and does nothing else — no border, no padding,
|
|
20
|
+
// no background. It only decides how far apart they sit, and that depends on the
|
|
21
|
+
// divider: a stroke needs clear air either side of its rule, while cards carry
|
|
22
|
+
// their own edges and sit closer.
|
|
23
|
+
//
|
|
24
|
+
// none 24px
|
|
25
|
+
// stroke 24px
|
|
26
|
+
// card 10px
|
|
27
|
+
//
|
|
28
|
+
// `align-items` is left alone so the rows stretch to the group's width. Figma
|
|
29
|
+
// says items-start with w-full on each row, which comes to the same thing without
|
|
30
|
+
// reaching into the children.
|
|
31
|
+
//
|
|
32
|
+
.bnv-accordion-group {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: 1.5rem;
|
|
36
|
+
width: 100%;
|
|
37
|
+
|
|
38
|
+
&--card {
|
|
39
|
+
gap: .625rem;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.bnv-accordion {
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
min-width: 320px;
|
|
46
|
+
|
|
47
|
+
// Structural only. The row needs to appear in a screen reader's heading list,
|
|
48
|
+
// but its size comes from __title, not from whichever level is used.
|
|
49
|
+
&__heading {
|
|
50
|
+
margin: 0;
|
|
51
|
+
font: inherit;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&__trigger {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: flex-start;
|
|
57
|
+
gap: 1.5rem;
|
|
58
|
+
width: 100%;
|
|
59
|
+
margin: 0;
|
|
60
|
+
padding: 0;
|
|
61
|
+
border: 0;
|
|
62
|
+
background: none;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
text-align: left;
|
|
65
|
+
font-family: @font-family;
|
|
66
|
+
|
|
67
|
+
// Keyboard only, so clicking a row does not leave a ring behind. Written
|
|
68
|
+
// out rather than sharing the button restyle's variables, which are not on
|
|
69
|
+
// main yet — worth folding together once that lands.
|
|
70
|
+
&:focus-visible {
|
|
71
|
+
outline: 2px solid var(--brand-400);
|
|
72
|
+
outline-offset: 2px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&__icon {
|
|
77
|
+
display: inline-flex;
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
// Figma nudges the 24px chevron 2px down so it reads as centred against
|
|
80
|
+
// the 28px title line instead of sitting flush with its ascender.
|
|
81
|
+
padding-top: 2px;
|
|
82
|
+
color: var(--text-400);
|
|
83
|
+
|
|
84
|
+
svg {
|
|
85
|
+
display: block;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- hover -----------------------------------------------------------
|
|
90
|
+
//
|
|
91
|
+
// Guarded on a device that genuinely hovers: a touch device latches :hover
|
|
92
|
+
// after a tap, which would leave the last-tapped row looking permanently
|
|
93
|
+
// active.
|
|
94
|
+
//
|
|
95
|
+
// The hover belongs to the trigger, not the whole row — the panel is not
|
|
96
|
+
// clickable, so hovering the answer must not suggest that it is. For the card
|
|
97
|
+
// that means matching on the trigger but colouring the card, hence `:has()`
|
|
98
|
+
// rather than a plain `:hover` on the root.
|
|
99
|
+
@media (hover: hover) {
|
|
100
|
+
.bnv-accordion__trigger:hover .bnv-accordion__icon {
|
|
101
|
+
color: var(--text-600);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&--card:has(.bnv-accordion__trigger:hover) {
|
|
105
|
+
background: var(--bg-50);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
&__title {
|
|
110
|
+
flex: 1 0 0;
|
|
111
|
+
min-width: 0;
|
|
112
|
+
color: var(--black);
|
|
113
|
+
font-size: 1.125rem;
|
|
114
|
+
line-height: 1.75rem;
|
|
115
|
+
font-weight: @font-weight-medium;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// No `display` here on purpose: the panel is hidden with the `hidden`
|
|
119
|
+
// attribute, and a display rule would override the browser's own
|
|
120
|
+
// `[hidden] { display: none }` and leave the answer on screen when closed.
|
|
121
|
+
&__panel {
|
|
122
|
+
margin-top: .5rem;
|
|
123
|
+
// 24px chevron + 24px gap, so the body lines up under the title rather
|
|
124
|
+
// than under the icon.
|
|
125
|
+
padding-left: 3rem;
|
|
126
|
+
color: var(--text-600);
|
|
127
|
+
font-size: 1rem;
|
|
128
|
+
line-height: 1.5rem;
|
|
129
|
+
font-weight: @font-weight-normal;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- icon position ---------------------------------------------------
|
|
133
|
+
// `order` rather than row-reverse: the chevron moves but the DOM does not,
|
|
134
|
+
// so the title stays first for anything reading the markup.
|
|
135
|
+
&--icon-right {
|
|
136
|
+
.bnv-accordion__icon { order: 1; }
|
|
137
|
+
|
|
138
|
+
.bnv-accordion__panel {
|
|
139
|
+
padding-left: 0;
|
|
140
|
+
padding-right: 3rem;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- dividers --------------------------------------------------------
|
|
145
|
+
//
|
|
146
|
+
// Both rules are drawn with an inset shadow rather than a border, because
|
|
147
|
+
// Figma puts its strokes *inside* the frame while a CSS border adds to it.
|
|
148
|
+
// With a border, stroke came out 1px taller than the design and card 2px
|
|
149
|
+
// taller AND 2px narrower — which moved where the body copy wraps, so it was
|
|
150
|
+
// not only a total-height rounding difference. An inset shadow occupies no
|
|
151
|
+
// space, which is exactly what Figma is doing.
|
|
152
|
+
//
|
|
153
|
+
// The rule sits on top of each row, so stacking rows draws the lines between
|
|
154
|
+
// them without needing a wrapper. The first row gets one too, which is
|
|
155
|
+
// Figma's.
|
|
156
|
+
&--stroke {
|
|
157
|
+
box-shadow: inset 0 1px 0 0 var(--border-200);
|
|
158
|
+
padding-top: 1.5rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
&--card {
|
|
162
|
+
background: var(--white);
|
|
163
|
+
box-shadow: inset 0 0 0 1px var(--border-200);
|
|
164
|
+
border-radius: 12px;
|
|
165
|
+
padding: 1rem;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Activity feed
|
|
3
|
+
//
|
|
4
|
+
// A timeline / message log built from FeedItems, matching the DS "Feed item base"
|
|
5
|
+
// and "Activity feed" (Figma page 253-14231). The container owns spacing and
|
|
6
|
+
// separators; the item owns its anatomy and the connector rail.
|
|
7
|
+
//
|
|
8
|
+
// Colours are tokens only (see scripts/check-tokens.sh): text on --black /
|
|
9
|
+
// --text-400 / --text-600, links and the "new" dot on --brand-700, rail and
|
|
10
|
+
// separators on --border-200, the date band on --bg-100.
|
|
11
|
+
//
|
|
12
|
+
|
|
13
|
+
@feed-bubble-shadow: 0 1px 1px rgba(16, 24, 40, 0.05);
|
|
14
|
+
@feed-gap: 32px; // spacing between items (Figma: 32px in every divider mode)
|
|
15
|
+
|
|
16
|
+
.bnv-feed {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
font-family: @font-family;
|
|
20
|
+
|
|
21
|
+
// ---- spacing per divider style ----
|
|
22
|
+
// none: 32px between items.
|
|
23
|
+
&--divider-none {
|
|
24
|
+
gap: @feed-gap;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// line: a full-width 1px separator sits in a 16px gap either side (~32px total).
|
|
28
|
+
&--divider-line {
|
|
29
|
+
gap: 16px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// connector: the rail is a flex segment inside the item, so the item carries
|
|
33
|
+
// its own bottom spacing (Figma: content pb 32). No container gap.
|
|
34
|
+
&--divider-connector {
|
|
35
|
+
gap: 0;
|
|
36
|
+
|
|
37
|
+
.bnv-feed-item:last-child .bnv-feed-item__body {
|
|
38
|
+
padding-bottom: 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// With a 32px container gap, the date's own 12px block margin would make the
|
|
43
|
+
// date↔item spacing 44px, which reads as disconnected. Drop it here so it is a
|
|
44
|
+
// clean 32px, uniform with the item spacing. (Line mode keeps the 12px → 28px.)
|
|
45
|
+
&--divider-none &__date,
|
|
46
|
+
&--divider-connector &__date {
|
|
47
|
+
margin: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---- separators ----
|
|
51
|
+
&__separator {
|
|
52
|
+
height: 1px;
|
|
53
|
+
background-color: var(--border-200);
|
|
54
|
+
border: 0;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---- day / date grouping header ----
|
|
59
|
+
// A pill (bg-100) with a calendar glyph and a bold date. The 12px block
|
|
60
|
+
// padding sits on top of the container gap, matching Figma's date frame.
|
|
61
|
+
&__date {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 10px;
|
|
65
|
+
box-sizing: border-box;
|
|
66
|
+
width: 100%;
|
|
67
|
+
margin: 12px 0;
|
|
68
|
+
padding: 8px 12px;
|
|
69
|
+
background-color: var(--bg-100);
|
|
70
|
+
border-radius: 0.5rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&__date-icon {
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
color: var(--text-600);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&__date-label {
|
|
79
|
+
font-size: @font-size; // 16px
|
|
80
|
+
line-height: 24px;
|
|
81
|
+
font-weight: @font-weight-semibold;
|
|
82
|
+
color: var(--black);
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ------------------------------------------------------------------ FeedItem
|
|
88
|
+
.bnv-feed-item {
|
|
89
|
+
--fi-avatar: 48px;
|
|
90
|
+
position: relative;
|
|
91
|
+
display: flex;
|
|
92
|
+
gap: 12px;
|
|
93
|
+
align-items: flex-start;
|
|
94
|
+
box-sizing: border-box;
|
|
95
|
+
font-size: @font-size; // 16px
|
|
96
|
+
line-height: 24px;
|
|
97
|
+
color: var(--text-600);
|
|
98
|
+
|
|
99
|
+
&--sm {
|
|
100
|
+
--fi-avatar: 32px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------- leading column + rail ----------
|
|
104
|
+
&__leading {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
align-items: center;
|
|
108
|
+
flex-shrink: 0;
|
|
109
|
+
width: var(--fi-avatar);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&--connector &__leading {
|
|
113
|
+
align-self: stretch;
|
|
114
|
+
gap: 4px;
|
|
115
|
+
padding-bottom: 4px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&__avatar {
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fallback marker when there is no avatar/icon.
|
|
124
|
+
&__marker {
|
|
125
|
+
width: 10px;
|
|
126
|
+
height: 10px;
|
|
127
|
+
margin-top: 7px;
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
background-color: var(--text-300);
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Decorative timeline rail — grows to fill the column below the avatar.
|
|
134
|
+
&__rail {
|
|
135
|
+
flex: 1 0 0;
|
|
136
|
+
width: 2px;
|
|
137
|
+
min-height: 8px;
|
|
138
|
+
border-radius: 2px;
|
|
139
|
+
background-color: var(--border-200);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------- body ----------
|
|
143
|
+
&__body {
|
|
144
|
+
flex: 1 1 0;
|
|
145
|
+
min-width: 0;
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
gap: 12px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&--connector &__body {
|
|
152
|
+
padding-bottom: @feed-gap;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Header + action line sit tight together; 12px separates them from supporting.
|
|
156
|
+
&__text-block {
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-direction: column;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
&__heading {
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
min-width: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
&__meta {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: baseline;
|
|
170
|
+
flex-wrap: wrap;
|
|
171
|
+
gap: 8px;
|
|
172
|
+
min-width: 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&__actor {
|
|
176
|
+
font-weight: @font-weight-semibold;
|
|
177
|
+
color: var(--black);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&__timestamp {
|
|
181
|
+
font-weight: @font-weight-normal;
|
|
182
|
+
color: var(--text-400);
|
|
183
|
+
white-space: nowrap;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// @handle sub-line (messages).
|
|
187
|
+
&__handle {
|
|
188
|
+
font-size: @font-size-sm;
|
|
189
|
+
line-height: 20px;
|
|
190
|
+
color: var(--text-400);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Status pill sits below the action line, left-aligned.
|
|
194
|
+
&__status {
|
|
195
|
+
display: flex;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Edit/delete sit top-right, right of the name. Absolute + centred on the name
|
|
199
|
+
// row (24px) so taller buttons never push the description down.
|
|
200
|
+
&__trailing {
|
|
201
|
+
position: absolute;
|
|
202
|
+
top: 0;
|
|
203
|
+
right: 0;
|
|
204
|
+
height: 24px;
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: 8px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
&__actions {
|
|
211
|
+
display: flex;
|
|
212
|
+
align-items: center;
|
|
213
|
+
gap: 2px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Row actions use the DS Button (icon-only). It carries its own styling — this
|
|
217
|
+
// class is only a hook for context tweaks if ever needed.
|
|
218
|
+
&__action-btn {
|
|
219
|
+
flex-shrink: 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Visually-hidden label (e.g. "Unread") — exposed to assistive tech only.
|
|
223
|
+
&__sr-only {
|
|
224
|
+
position: absolute;
|
|
225
|
+
width: 1px;
|
|
226
|
+
height: 1px;
|
|
227
|
+
margin: -1px;
|
|
228
|
+
padding: 0;
|
|
229
|
+
border: 0;
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
clip: rect(0 0 0 0);
|
|
232
|
+
clip-path: inset(50%);
|
|
233
|
+
white-space: nowrap;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Unread indicator.
|
|
237
|
+
&__new-dot {
|
|
238
|
+
width: 10px;
|
|
239
|
+
height: 10px;
|
|
240
|
+
border-radius: 50%;
|
|
241
|
+
background-color: var(--brand-700);
|
|
242
|
+
flex-shrink: 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------- action line ----------
|
|
246
|
+
// Kept uniformly 16px/24px across every variant so the name→description gap is
|
|
247
|
+
// identical whether the item has a file, labels, text or nothing.
|
|
248
|
+
&__action {
|
|
249
|
+
color: var(--text-600);
|
|
250
|
+
|
|
251
|
+
a,
|
|
252
|
+
.bnv-feed-item__link {
|
|
253
|
+
color: var(--brand-700);
|
|
254
|
+
font-weight: @font-weight-medium;
|
|
255
|
+
text-decoration: none;
|
|
256
|
+
|
|
257
|
+
&:hover { text-decoration: underline; }
|
|
258
|
+
|
|
259
|
+
&:focus-visible {
|
|
260
|
+
outline: 2px solid var(--primary);
|
|
261
|
+
outline-offset: 2px;
|
|
262
|
+
border-radius: 0.125rem;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---------- supporting: file ----------
|
|
268
|
+
// The file is its own hoverable target — download/delete belong to the document,
|
|
269
|
+
// so they sit on its right and reveal on hover/focus. A "card" outline is drawn
|
|
270
|
+
// just outside the content so nothing shifts when it appears.
|
|
271
|
+
&__file {
|
|
272
|
+
position: relative;
|
|
273
|
+
display: flex;
|
|
274
|
+
gap: 12px;
|
|
275
|
+
align-items: flex-start;
|
|
276
|
+
width: 100%;
|
|
277
|
+
min-width: 0;
|
|
278
|
+
|
|
279
|
+
&::before {
|
|
280
|
+
content: '';
|
|
281
|
+
position: absolute;
|
|
282
|
+
inset: -8px -12px;
|
|
283
|
+
border: 1px solid transparent;
|
|
284
|
+
border-radius: 0.5rem;
|
|
285
|
+
pointer-events: none;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
&:hover::before,
|
|
289
|
+
&:focus-within::before {
|
|
290
|
+
border-color: var(--border-200);
|
|
291
|
+
background-color: var(--white);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
> * { position: relative; } // keep content above the ::before card
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
&__file-icon {
|
|
298
|
+
flex-shrink: 0;
|
|
299
|
+
width: 32px;
|
|
300
|
+
height: 40px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
&__file-text {
|
|
304
|
+
display: flex;
|
|
305
|
+
flex-direction: column;
|
|
306
|
+
flex: 1 1 auto;
|
|
307
|
+
min-width: 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
&__file-name {
|
|
311
|
+
color: var(--black);
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
text-overflow: ellipsis;
|
|
314
|
+
white-space: nowrap;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
&__file-size {
|
|
318
|
+
font-size: @font-size-sm;
|
|
319
|
+
line-height: 20px;
|
|
320
|
+
color: var(--text-600);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Actions on the right of the file row — hidden until hover/focus.
|
|
324
|
+
&__file-actions {
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 2px;
|
|
328
|
+
flex-shrink: 0;
|
|
329
|
+
align-self: center;
|
|
330
|
+
opacity: 0;
|
|
331
|
+
transition: opacity 0.12s ease;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
&__file:hover &__file-actions,
|
|
335
|
+
&__file:focus-within &__file-actions {
|
|
336
|
+
opacity: 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Touch devices have no hover — keep the actions visible there.
|
|
340
|
+
@media (hover: none) {
|
|
341
|
+
&__file-actions { opacity: 1; }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---------- supporting: labels ----------
|
|
345
|
+
&__labels {
|
|
346
|
+
display: flex;
|
|
347
|
+
flex-wrap: wrap;
|
|
348
|
+
gap: 4px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------- supporting: text (message bubble) ----------
|
|
352
|
+
&__bubble {
|
|
353
|
+
box-sizing: border-box;
|
|
354
|
+
width: 100%;
|
|
355
|
+
padding: 12px;
|
|
356
|
+
background-color: var(--white);
|
|
357
|
+
border: 1px solid var(--border-200);
|
|
358
|
+
// Speech-bubble: square top-left, rounded elsewhere.
|
|
359
|
+
border-radius: 0 8px 8px 8px;
|
|
360
|
+
box-shadow: @feed-bubble-shadow;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
&__text {
|
|
364
|
+
color: var(--black);
|
|
365
|
+
white-space: pre-wrap;
|
|
366
|
+
overflow-wrap: anywhere;
|
|
367
|
+
|
|
368
|
+
&.is-clamped {
|
|
369
|
+
display: -webkit-box;
|
|
370
|
+
-webkit-box-orient: vertical;
|
|
371
|
+
-webkit-line-clamp: 4; // overridden inline via clampLines
|
|
372
|
+
overflow: hidden;
|
|
373
|
+
position: relative;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Fade the last visible line while clamped and actually overflowing.
|
|
377
|
+
&.is-clamped.is-overflowing::after {
|
|
378
|
+
content: '';
|
|
379
|
+
position: absolute;
|
|
380
|
+
left: 0;
|
|
381
|
+
right: 0;
|
|
382
|
+
bottom: 0;
|
|
383
|
+
height: 1.4em;
|
|
384
|
+
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--white));
|
|
385
|
+
pointer-events: none;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// The toggle is the DS Button (text/link). Only position it below the text.
|
|
390
|
+
&__toggle {
|
|
391
|
+
align-self: flex-start;
|
|
392
|
+
margin-top: 8px;
|
|
393
|
+
}
|
|
394
|
+
}
|