@inglorious/ui 6.0.0 → 7.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.
- package/package.json +32 -12
- package/src/controls/controls.css +3 -1
- package/src/controls/file-upload/file-upload.stories.js +93 -0
- package/src/controls/file-upload/index.js +3 -0
- package/src/controls/file-upload/style.css +99 -0
- package/src/controls/file-upload/template.js +100 -0
- package/src/controls/input/input.stories.js +110 -3
- package/src/controls/textarea/index.js +3 -0
- package/src/controls/textarea/style.css +98 -0
- package/src/controls/textarea/template.js +94 -0
- package/src/controls/textarea/textarea.stories.js +158 -0
- package/src/data-display/before-after/before-after.stories.js +74 -0
- package/src/data-display/before-after/handlers.js +44 -0
- package/src/data-display/before-after/helpers.js +4 -0
- package/src/data-display/before-after/index.js +17 -0
- package/src/data-display/before-after/style.css +128 -0
- package/src/data-display/before-after/template.js +225 -0
- package/src/data-display/carousel/carousel.stories.js +166 -0
- package/src/data-display/carousel/handlers.js +78 -0
- package/src/data-display/carousel/helpers.js +54 -0
- package/src/data-display/carousel/index.js +17 -0
- package/src/data-display/carousel/style.css +233 -0
- package/src/data-display/carousel/template.js +655 -0
- package/src/data-display/data-display.css +2 -0
- package/src/examples/dashboard/app-drawer.js +12 -0
- package/src/examples/dashboard/before-after-data.js +29 -0
- package/src/examples/dashboard/primitive-content.js +220 -33
- package/src/index.js +4 -0
- package/src/layout/flex/flex.stories.js +49 -0
- package/src/layout/flex/style.css +59 -0
- package/src/layout/flex/template.js +2 -0
- package/types/controls/file-upload.d.ts +27 -0
- package/types/controls/input.d.ts +6 -0
- package/types/controls/textarea.d.ts +29 -0
- package/types/controls/upload-field.d.ts +27 -0
- package/types/data-display/before-after.d.ts +39 -0
- package/types/data-display/carousel.d.ts +72 -0
- package/types/index.d.ts +2 -0
- package/types/layout/flex.d.ts +1 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../../../types/controls/textarea').TextareaProps} TextareaProps
|
|
3
|
+
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { html } from "@inglorious/web"
|
|
7
|
+
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
|
+
import { ref } from "@inglorious/web/directives/ref"
|
|
9
|
+
|
|
10
|
+
import { applyElementProps } from "../../shared/applyElementProps.js"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Renders a labeled textarea field with optional hint and error messaging.
|
|
14
|
+
* Supports disabled/readonly/required states and emits `onChange` on input.
|
|
15
|
+
* @param {TextareaProps} props
|
|
16
|
+
* @returns {TemplateResult}
|
|
17
|
+
*/
|
|
18
|
+
export function render(props) {
|
|
19
|
+
const {
|
|
20
|
+
id,
|
|
21
|
+
name = "",
|
|
22
|
+
value = "",
|
|
23
|
+
placeholder = "",
|
|
24
|
+
label,
|
|
25
|
+
hint,
|
|
26
|
+
error,
|
|
27
|
+
size = "md",
|
|
28
|
+
rows = 4,
|
|
29
|
+
isDisabled = false,
|
|
30
|
+
isReadOnly = false,
|
|
31
|
+
isRequired = false,
|
|
32
|
+
isFullWidth = false,
|
|
33
|
+
isAutoSize = false,
|
|
34
|
+
isResizable = false,
|
|
35
|
+
onChange,
|
|
36
|
+
onBlur,
|
|
37
|
+
onFocus,
|
|
38
|
+
...rest
|
|
39
|
+
} = props
|
|
40
|
+
|
|
41
|
+
const textareaId = id || name
|
|
42
|
+
|
|
43
|
+
const wrapperClasses = {
|
|
44
|
+
"iw-textarea-field": true,
|
|
45
|
+
"iw-textarea-full-width": isFullWidth,
|
|
46
|
+
"iw-textarea-disabled": isDisabled,
|
|
47
|
+
"iw-textarea-has-error": !!error,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const textareaClasses = {
|
|
51
|
+
"iw-textarea": true,
|
|
52
|
+
[`iw-textarea-${size}`]: size !== "md",
|
|
53
|
+
"iw-textarea-error": !!error,
|
|
54
|
+
"iw-textarea-auto-size": isAutoSize,
|
|
55
|
+
"iw-textarea-resizable": isResizable,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return html`
|
|
59
|
+
<div class=${classMap(wrapperClasses)}>
|
|
60
|
+
${label
|
|
61
|
+
? html`
|
|
62
|
+
<label for=${textareaId} class="iw-textarea-label">
|
|
63
|
+
${label}
|
|
64
|
+
${isRequired
|
|
65
|
+
? html`<span class="iw-textarea-required">*</span>`
|
|
66
|
+
: null}
|
|
67
|
+
</label>
|
|
68
|
+
`
|
|
69
|
+
: null}
|
|
70
|
+
|
|
71
|
+
<textarea
|
|
72
|
+
id=${textareaId}
|
|
73
|
+
name=${name}
|
|
74
|
+
rows=${rows}
|
|
75
|
+
.value=${value}
|
|
76
|
+
placeholder=${placeholder}
|
|
77
|
+
?disabled=${isDisabled}
|
|
78
|
+
?readonly=${isReadOnly}
|
|
79
|
+
?required=${isRequired}
|
|
80
|
+
class=${classMap(textareaClasses)}
|
|
81
|
+
@input=${(event) => onChange?.(event.target.value)}
|
|
82
|
+
@blur=${onBlur}
|
|
83
|
+
@focus=${onFocus}
|
|
84
|
+
${ref((element) => applyElementProps(element, rest))}
|
|
85
|
+
></textarea>
|
|
86
|
+
|
|
87
|
+
${error
|
|
88
|
+
? html`<span class="iw-textarea-error-message">${error}</span>`
|
|
89
|
+
: hint
|
|
90
|
+
? html`<span class="iw-textarea-hint">${hint}</span>`
|
|
91
|
+
: null}
|
|
92
|
+
</div>
|
|
93
|
+
`
|
|
94
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createRender } from "../../stories/notifyStory.js"
|
|
2
|
+
import { Textarea } from "."
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: "Controls/Textarea",
|
|
6
|
+
tags: ["autodocs"],
|
|
7
|
+
render: createRender(Textarea),
|
|
8
|
+
argTypes: {
|
|
9
|
+
label: {
|
|
10
|
+
control: "text",
|
|
11
|
+
description: "Field label rendered above the textarea.",
|
|
12
|
+
},
|
|
13
|
+
placeholder: {
|
|
14
|
+
control: "text",
|
|
15
|
+
description: "Placeholder text shown when value is empty.",
|
|
16
|
+
},
|
|
17
|
+
name: {
|
|
18
|
+
control: "text",
|
|
19
|
+
description: "Native HTML textarea name attribute.",
|
|
20
|
+
},
|
|
21
|
+
value: {
|
|
22
|
+
control: "text",
|
|
23
|
+
description: "Current textarea value.",
|
|
24
|
+
},
|
|
25
|
+
hint: {
|
|
26
|
+
control: "text",
|
|
27
|
+
description: "Helper text shown below the field when there is no error.",
|
|
28
|
+
},
|
|
29
|
+
error: {
|
|
30
|
+
control: "text",
|
|
31
|
+
description: "Validation message shown below the field.",
|
|
32
|
+
},
|
|
33
|
+
size: {
|
|
34
|
+
control: "select",
|
|
35
|
+
options: ["sm", "md", "lg"],
|
|
36
|
+
description: "Size scale for internal spacing and typography.",
|
|
37
|
+
},
|
|
38
|
+
rows: {
|
|
39
|
+
control: "number",
|
|
40
|
+
description: "Initial number of visible text rows.",
|
|
41
|
+
},
|
|
42
|
+
isDisabled: {
|
|
43
|
+
control: "boolean",
|
|
44
|
+
description: "Disables user input and interaction.",
|
|
45
|
+
},
|
|
46
|
+
isReadOnly: {
|
|
47
|
+
control: "boolean",
|
|
48
|
+
description: "Prevents editing while keeping the control focusable.",
|
|
49
|
+
},
|
|
50
|
+
isRequired: {
|
|
51
|
+
control: "boolean",
|
|
52
|
+
description: "Marks the field as required.",
|
|
53
|
+
},
|
|
54
|
+
isFullWidth: {
|
|
55
|
+
control: "boolean",
|
|
56
|
+
description: "Expands the textarea width to 100% of its container.",
|
|
57
|
+
},
|
|
58
|
+
isAutoSize: {
|
|
59
|
+
control: "boolean",
|
|
60
|
+
description:
|
|
61
|
+
"Applies field-sizing: content so the textarea grows with its content.",
|
|
62
|
+
},
|
|
63
|
+
isResizable: {
|
|
64
|
+
control: "boolean",
|
|
65
|
+
description: "Enables the browser resize handle for vertical resizing.",
|
|
66
|
+
},
|
|
67
|
+
onChange: { action: "onChange" },
|
|
68
|
+
onBlur: { action: "onBlur" },
|
|
69
|
+
onFocus: { action: "onFocus" },
|
|
70
|
+
},
|
|
71
|
+
parameters: {
|
|
72
|
+
docs: {
|
|
73
|
+
description: {
|
|
74
|
+
component:
|
|
75
|
+
"Multi-line text input with labels, hints, errors, and automatic content sizing support.",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const Default = {}
|
|
82
|
+
Default.args = {
|
|
83
|
+
label: "Bio",
|
|
84
|
+
placeholder: "Tell us a little about yourself",
|
|
85
|
+
name: "bio",
|
|
86
|
+
value: "",
|
|
87
|
+
hint: "",
|
|
88
|
+
error: "",
|
|
89
|
+
size: "md",
|
|
90
|
+
rows: 4,
|
|
91
|
+
isDisabled: false,
|
|
92
|
+
isReadOnly: false,
|
|
93
|
+
isRequired: false,
|
|
94
|
+
isFullWidth: false,
|
|
95
|
+
isAutoSize: false,
|
|
96
|
+
isResizable: false,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const WithHint = {}
|
|
100
|
+
WithHint.args = {
|
|
101
|
+
...Default.args,
|
|
102
|
+
label: "Project notes",
|
|
103
|
+
hint: "Include any implementation details or blockers.",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const WithError = {}
|
|
107
|
+
WithError.args = {
|
|
108
|
+
...Default.args,
|
|
109
|
+
label: "Description",
|
|
110
|
+
value: "This is too long",
|
|
111
|
+
error: "Please keep the description under 280 characters.",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const Required = {}
|
|
115
|
+
Required.args = {
|
|
116
|
+
...Default.args,
|
|
117
|
+
label: "Message",
|
|
118
|
+
placeholder: "Write a quick message",
|
|
119
|
+
isRequired: true,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const Disabled = {}
|
|
123
|
+
Disabled.args = {
|
|
124
|
+
...Default.args,
|
|
125
|
+
label: "Disabled textarea",
|
|
126
|
+
isDisabled: true,
|
|
127
|
+
value: "Cannot edit this value",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const Readonly = {}
|
|
131
|
+
Readonly.args = {
|
|
132
|
+
...Default.args,
|
|
133
|
+
label: "Read-only textarea",
|
|
134
|
+
isReadOnly: true,
|
|
135
|
+
value: "This field is read-only and not editable.",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const Small = {}
|
|
139
|
+
Small.args = {
|
|
140
|
+
...Default.args,
|
|
141
|
+
label: "Small textarea",
|
|
142
|
+
size: "sm",
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const Large = {}
|
|
146
|
+
Large.args = {
|
|
147
|
+
...Default.args,
|
|
148
|
+
label: "Large textarea",
|
|
149
|
+
size: "lg",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const FullWidth = {}
|
|
153
|
+
FullWidth.args = {
|
|
154
|
+
...Default.args,
|
|
155
|
+
label: "Full width textarea",
|
|
156
|
+
placeholder: "This textarea takes the full width of its container",
|
|
157
|
+
isFullWidth: true,
|
|
158
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEntityRender,
|
|
3
|
+
notifyActionArgType,
|
|
4
|
+
} from "../../stories/notifyStory.js"
|
|
5
|
+
import { BeforeAfter } from "./index.js"
|
|
6
|
+
|
|
7
|
+
const before = {
|
|
8
|
+
src: "https://picsum.photos/id/1015/800/500?grayscale",
|
|
9
|
+
alt: "Before",
|
|
10
|
+
}
|
|
11
|
+
const after = {
|
|
12
|
+
src: "https://picsum.photos/id/1015/800/500",
|
|
13
|
+
alt: "After",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
title: "Data Display/BeforeAfter",
|
|
18
|
+
tags: ["autodocs"],
|
|
19
|
+
render: createEntityRender({ BeforeAfter }),
|
|
20
|
+
argTypes: {
|
|
21
|
+
...notifyActionArgType,
|
|
22
|
+
label: { control: "text", description: "Accessible label for the slider." },
|
|
23
|
+
position: {
|
|
24
|
+
control: { type: "range", min: 0, max: 100, step: 1 },
|
|
25
|
+
description: "Divider position as a percentage (0-100).",
|
|
26
|
+
},
|
|
27
|
+
step: {
|
|
28
|
+
control: "number",
|
|
29
|
+
description: "Keyboard increment (percent) for arrow keys.",
|
|
30
|
+
},
|
|
31
|
+
isDisabled: { control: "boolean", description: "Disable interaction." },
|
|
32
|
+
isFullWidth: {
|
|
33
|
+
control: "boolean",
|
|
34
|
+
description: "Expand to container width.",
|
|
35
|
+
},
|
|
36
|
+
before: { control: "object", description: "Before image { src, alt }." },
|
|
37
|
+
after: { control: "object", description: "After image { src, alt }." },
|
|
38
|
+
},
|
|
39
|
+
parameters: {
|
|
40
|
+
docs: {
|
|
41
|
+
description: {
|
|
42
|
+
component:
|
|
43
|
+
"Before/after image comparison slider. The reveal is pure CSS (clip-path); the divider position is the only piece of entity state. Drag anywhere, or focus and use arrow keys / Home / End.",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Default = {}
|
|
50
|
+
Default.args = {
|
|
51
|
+
id: "before-after",
|
|
52
|
+
type: "BeforeAfter",
|
|
53
|
+
label: "Before and after comparison",
|
|
54
|
+
position: 50,
|
|
55
|
+
step: 1,
|
|
56
|
+
isDisabled: false,
|
|
57
|
+
isFullWidth: true,
|
|
58
|
+
before,
|
|
59
|
+
after,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const StartLeft = {}
|
|
63
|
+
StartLeft.args = {
|
|
64
|
+
...Default.args,
|
|
65
|
+
id: "before-after-left",
|
|
66
|
+
position: 15,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const Disabled = {}
|
|
70
|
+
Disabled.args = {
|
|
71
|
+
...Default.args,
|
|
72
|
+
id: "before-after-disabled",
|
|
73
|
+
isDisabled: true,
|
|
74
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../../../types/data-display/before-after.js').BeforeAfterProps} BeforeAfterEntity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { clamp } from "@inglorious/utils/math/numbers.js"
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_POSITION,
|
|
9
|
+
DEFAULT_STEP,
|
|
10
|
+
MAX_POSITION,
|
|
11
|
+
MIN_POSITION,
|
|
12
|
+
} from "./helpers.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize missing before/after state defaults.
|
|
16
|
+
* The only persistent piece of state is `position`: the divider location as a
|
|
17
|
+
* percentage (0-100). Everything else is configuration.
|
|
18
|
+
* @param {BeforeAfterEntity} entity
|
|
19
|
+
*/
|
|
20
|
+
export function create(entity) {
|
|
21
|
+
entity.position ??= DEFAULT_POSITION
|
|
22
|
+
entity.step ??= DEFAULT_STEP
|
|
23
|
+
|
|
24
|
+
entity.before ??= null
|
|
25
|
+
entity.after ??= null
|
|
26
|
+
|
|
27
|
+
entity.label ??= ""
|
|
28
|
+
entity.isDisabled ??= false
|
|
29
|
+
entity.isFullWidth ??= false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Move the divider to a new position, clamped to the 0-100 range.
|
|
34
|
+
* @param {BeforeAfterEntity} entity
|
|
35
|
+
* @param {number} position
|
|
36
|
+
*/
|
|
37
|
+
export function positionChange(entity, position) {
|
|
38
|
+
if (entity.isDisabled) return
|
|
39
|
+
|
|
40
|
+
const value = Number(position)
|
|
41
|
+
if (Number.isNaN(value)) return
|
|
42
|
+
|
|
43
|
+
entity.position = clamp(value, MIN_POSITION, MAX_POSITION)
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as handlers from "./handlers.js"
|
|
2
|
+
import { BeforeAfter as renderers } from "./template.js"
|
|
3
|
+
|
|
4
|
+
export const BeforeAfter = {
|
|
5
|
+
...handlers,
|
|
6
|
+
...renderers,
|
|
7
|
+
render(entity, api) {
|
|
8
|
+
const id = entity.id
|
|
9
|
+
const props = {
|
|
10
|
+
...entity,
|
|
11
|
+
onPositionChange: (position) =>
|
|
12
|
+
api.notify(`#${id}:positionChange`, position),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return this.renderBeforeAfter(props)
|
|
16
|
+
},
|
|
17
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
.iw-before-after {
|
|
2
|
+
--iw-before-after-position: 50%;
|
|
3
|
+
|
|
4
|
+
/* Neutral in every theme on purpose: the divider should separate the two
|
|
5
|
+
pictures without competing with them for attention. */
|
|
6
|
+
--iw-before-after-divider-width: 2px;
|
|
7
|
+
--iw-before-after-divider-color: rgb(255 255 255 / 90%);
|
|
8
|
+
--iw-before-after-handle-size: 40px;
|
|
9
|
+
--iw-before-after-handle-bg: rgb(255 255 255 / 90%);
|
|
10
|
+
--iw-before-after-handle-color: rgb(17 24 39 / 65%);
|
|
11
|
+
--iw-before-after-handle-arrow: 5px;
|
|
12
|
+
|
|
13
|
+
position: relative;
|
|
14
|
+
display: inline-block;
|
|
15
|
+
max-width: 100%;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
line-height: 0;
|
|
18
|
+
cursor: ew-resize;
|
|
19
|
+
touch-action: none;
|
|
20
|
+
user-select: none;
|
|
21
|
+
-webkit-user-select: none;
|
|
22
|
+
outline: none;
|
|
23
|
+
|
|
24
|
+
&:focus-visible {
|
|
25
|
+
outline: 2px solid var(--iw-color-primary);
|
|
26
|
+
outline-offset: 2px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.iw-before-after-full-width {
|
|
30
|
+
display: block;
|
|
31
|
+
width: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Greyed out and dimmed, so it reads as "you cannot move this". */
|
|
35
|
+
&.iw-before-after-disabled {
|
|
36
|
+
--iw-before-after-divider-color: rgb(156 163 175 / 60%);
|
|
37
|
+
--iw-before-after-handle-bg: rgb(156 163 175 / 60%);
|
|
38
|
+
--iw-before-after-handle-color: rgb(255 255 255 / 60%);
|
|
39
|
+
|
|
40
|
+
cursor: default;
|
|
41
|
+
|
|
42
|
+
.iw-before-after-image {
|
|
43
|
+
filter: grayscale(70%);
|
|
44
|
+
opacity: 0.75;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.iw-before-after-handle {
|
|
48
|
+
box-shadow: none;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.iw-before-after-before {
|
|
53
|
+
display: block;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.iw-before-after-before .iw-before-after-image {
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.iw-before-after-after {
|
|
62
|
+
position: absolute;
|
|
63
|
+
inset: 0;
|
|
64
|
+
clip-path: inset(0 0 0 var(--iw-before-after-position));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.iw-before-after-after .iw-before-after-image {
|
|
68
|
+
width: 100%;
|
|
69
|
+
height: 100%;
|
|
70
|
+
object-fit: cover;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.iw-before-after-image {
|
|
74
|
+
display: block;
|
|
75
|
+
pointer-events: none;
|
|
76
|
+
user-select: none;
|
|
77
|
+
-webkit-user-drag: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.iw-before-after-divider {
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset-block: 0;
|
|
83
|
+
left: var(--iw-before-after-position);
|
|
84
|
+
width: var(--iw-before-after-divider-width);
|
|
85
|
+
transform: translateX(-50%);
|
|
86
|
+
background: var(--iw-before-after-divider-color);
|
|
87
|
+
box-shadow: 0 0 0 1px rgb(0 0 0 / 12%);
|
|
88
|
+
pointer-events: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.iw-before-after-handle {
|
|
92
|
+
position: absolute;
|
|
93
|
+
top: 50%;
|
|
94
|
+
left: 50%;
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
box-sizing: border-box;
|
|
99
|
+
width: var(--iw-before-after-handle-size);
|
|
100
|
+
height: var(--iw-before-after-handle-size);
|
|
101
|
+
border-radius: var(--iw-radius-full);
|
|
102
|
+
background: var(--iw-before-after-handle-bg);
|
|
103
|
+
transform: translate(-50%, -50%);
|
|
104
|
+
box-shadow: var(--iw-shadow-md);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Two chevron arrows drawn with borders, no icon font needed. */
|
|
108
|
+
.iw-before-after-handle::before,
|
|
109
|
+
.iw-before-after-handle::after {
|
|
110
|
+
content: "";
|
|
111
|
+
width: 0;
|
|
112
|
+
height: 0;
|
|
113
|
+
border-top: var(--iw-before-after-handle-arrow) solid transparent;
|
|
114
|
+
border-bottom: var(--iw-before-after-handle-arrow) solid transparent;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.iw-before-after-handle::before {
|
|
118
|
+
margin-right: var(--iw-before-after-handle-arrow);
|
|
119
|
+
border-right: var(--iw-before-after-handle-arrow) solid
|
|
120
|
+
var(--iw-before-after-handle-color);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.iw-before-after-handle::after {
|
|
124
|
+
margin-left: var(--iw-before-after-handle-arrow);
|
|
125
|
+
border-left: var(--iw-before-after-handle-arrow) solid
|
|
126
|
+
var(--iw-before-after-handle-color);
|
|
127
|
+
}
|
|
128
|
+
}
|