feat: add sidebar

WIP
This commit is contained in:
2 * r + 2 * t 2025-09-18 15:19:15 +10:00
parent 2f42377479
commit 85d575f6c1
16 changed files with 296 additions and 55 deletions

View file

@ -516,6 +516,7 @@ default, you must create it manually.
}, },
"session": { "session": {
"dragThreshold": 30, "dragThreshold": 30,
"enabled": true,
"vimKeybinds": false, "vimKeybinds": false,
"commands": { "commands": {
"logout": ["loginctl", "terminate-user", ""], "logout": ["loginctl", "terminate-user", ""],
@ -524,6 +525,10 @@ default, you must create it manually.
"reboot": ["systemctl", "reboot"] "reboot": ["systemctl", "reboot"]
} }
}, },
"sidebar": {
"dragThreshold": 80,
"enabled": true
}.
"utilities": { "utilities": {
"enabled": true "enabled": true
} }

View file

@ -21,6 +21,7 @@ Singleton {
property alias winfo: adapter.winfo property alias winfo: adapter.winfo
property alias lock: adapter.lock property alias lock: adapter.lock
property alias utilities: adapter.utilities property alias utilities: adapter.utilities
property alias sidebar: adapter.sidebar
property alias services: adapter.services property alias services: adapter.services
property alias paths: adapter.paths property alias paths: adapter.paths
@ -46,6 +47,7 @@ Singleton {
property WInfoConfig winfo: WInfoConfig {} property WInfoConfig winfo: WInfoConfig {}
property LockConfig lock: LockConfig {} property LockConfig lock: LockConfig {}
property UtilitiesConfig utilities: UtilitiesConfig {} property UtilitiesConfig utilities: UtilitiesConfig {}
property SidebarConfig sidebar: SidebarConfig {}
property ServiceConfig services: ServiceConfig {} property ServiceConfig services: ServiceConfig {}
property UserPaths paths: UserPaths {} property UserPaths paths: UserPaths {}
} }

11
config/SidebarConfig.qml Normal file
View file

@ -0,0 +1,11 @@
import Quickshell.Io
JsonObject {
property bool enabled: true
property int dragThreshold: 80
property Sizes sizes: Sizes {}
component Sizes: JsonObject {
property int width: 430
}
}

View file

@ -7,6 +7,7 @@ import qs.modules.launcher as Launcher
import qs.modules.dashboard as Dashboard import qs.modules.dashboard as Dashboard
import qs.modules.bar.popouts as BarPopouts import qs.modules.bar.popouts as BarPopouts
import qs.modules.utilities as Utilities import qs.modules.utilities as Utilities
import qs.modules.sidebar as Sidebar
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
@ -24,12 +25,13 @@ Shape {
Osd.Background { Osd.Background {
wrapper: root.panels.osd wrapper: root.panels.osd
startX: root.width - root.panels.session.width startX: root.width - root.panels.session.width - root.panels.sidebar.width
startY: (root.height - wrapper.height) / 2 - rounding startY: (root.height - wrapper.height) / 2 - rounding
} }
Notifications.Background { Notifications.Background {
wrapper: root.panels.notifications wrapper: root.panels.notifications
sidebar: sidebar
startX: root.width startX: root.width
startY: 0 startY: 0
@ -38,7 +40,7 @@ Shape {
Session.Background { Session.Background {
wrapper: root.panels.session wrapper: root.panels.session
startX: root.width startX: root.width - root.panels.sidebar.width
startY: (root.height - wrapper.height) / 2 - rounding startY: (root.height - wrapper.height) / 2 - rounding
} }
@ -66,8 +68,19 @@ Shape {
Utilities.Background { Utilities.Background {
wrapper: root.panels.utilities wrapper: root.panels.utilities
sidebar: sidebar
startX: root.width startX: root.width
startY: root.height startY: root.height
} }
Sidebar.Background {
id: sidebar
wrapper: root.panels.sidebar
panels: root.panels
startX: root.width
startY: root.panels.notifications.height
}
} }

View file

@ -72,11 +72,12 @@ Variants {
} }
HyprlandFocusGrab { HyprlandFocusGrab {
active: (visibilities.launcher && Config.launcher.enabled) || (visibilities.session && Config.session.enabled) active: (visibilities.launcher && Config.launcher.enabled) || (visibilities.session && Config.session.enabled) || (visibilities.sidebar && Config.sidebar.enabled)
windows: [win] windows: [win]
onCleared: { onCleared: {
visibilities.launcher = false; visibilities.launcher = false;
visibilities.session = false; visibilities.session = false;
visibilities.sidebar = false;
} }
} }
@ -119,6 +120,7 @@ Variants {
property bool launcher property bool launcher
property bool dashboard property bool dashboard
property bool utilities property bool utilities
property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this) Component.onCompleted: Visibilities.load(scope.modelData, this)
} }

View file

@ -82,6 +82,8 @@ CustomMouseArea {
const x = event.x; const x = event.x;
const y = event.y; const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
// Show bar in non-exclusive mode on hover // Show bar in non-exclusive mode on hover
if (!visibilities.bar && Config.bar.showOnHover && x < bar.implicitWidth) if (!visibilities.bar && Config.bar.showOnHover && x < bar.implicitWidth)
@ -89,13 +91,13 @@ CustomMouseArea {
// Show/hide bar on drag // Show/hide bar on drag
if (pressed && dragStart.x < bar.implicitWidth) { if (pressed && dragStart.x < bar.implicitWidth) {
const dragX = x - dragStart.x;
if (dragX > Config.bar.dragThreshold) if (dragX > Config.bar.dragThreshold)
visibilities.bar = true; visibilities.bar = true;
else if (dragX < -Config.bar.dragThreshold) else if (dragX < -Config.bar.dragThreshold)
visibilities.bar = false; visibilities.bar = false;
} }
if (panels.sidebar.width === 0) {
// Show osd on hover // Show osd on hover
const showOsd = inRightPanel(panels.osd, x, y); const showOsd = inRightPanel(panels.osd, x, y);
@ -109,13 +111,48 @@ CustomMouseArea {
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x;
// Show/hide session on drag // Show/hide session on drag
if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) { if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
const dragX = x - dragStart.x;
if (dragX < -Config.session.dragThreshold) if (dragX < -Config.session.dragThreshold)
visibilities.session = true; visibilities.session = true;
else if (dragX > Config.session.dragThreshold) else if (dragX > Config.session.dragThreshold)
visibilities.session = false; visibilities.session = false;
// Show sidebar on drag if in session area and session is nearly fully visible
if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold)
visibilities.sidebar = true;
} else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
// Show sidebar on drag if not in session area
visibilities.sidebar = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width;
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
// Show/hide session on drag
if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
if (dragX < -Config.session.dragThreshold)
visibilities.session = true;
else if (dragX > Config.session.dragThreshold)
visibilities.session = false;
}
// Hide sidebar on drag
if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold)
visibilities.sidebar = false;
} }
// Show launcher on hover, or show/hide on drag if hover is disabled // Show launcher on hover, or show/hide on drag if hover is disabled
@ -123,7 +160,6 @@ CustomMouseArea {
if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y)) if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y))
visibilities.launcher = true; visibilities.launcher = true;
} else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) { } else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) {
const dragY = y - dragStart.y;
if (dragY < -Config.launcher.dragThreshold) if (dragY < -Config.launcher.dragThreshold)
visibilities.launcher = true; visibilities.launcher = true;
else if (dragY > Config.launcher.dragThreshold) else if (dragY > Config.launcher.dragThreshold)
@ -143,7 +179,6 @@ CustomMouseArea {
// Show/hide dashboard on drag (for touchscreen devices) // Show/hide dashboard on drag (for touchscreen devices)
if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) { if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
const dragY = y - dragStart.y;
if (dragY > Config.dashboard.dragThreshold) if (dragY > Config.dashboard.dragThreshold)
visibilities.dashboard = true; visibilities.dashboard = true;
else if (dragY < -Config.dashboard.dragThreshold) else if (dragY < -Config.dashboard.dragThreshold)

View file

@ -6,6 +6,7 @@ import qs.modules.launcher as Launcher
import qs.modules.dashboard as Dashboard import qs.modules.dashboard as Dashboard
import qs.modules.bar.popouts as BarPopouts import qs.modules.bar.popouts as BarPopouts
import qs.modules.utilities as Utilities import qs.modules.utilities as Utilities
import qs.modules.sidebar as Sidebar
import Quickshell import Quickshell
import QtQuick import QtQuick
@ -23,6 +24,7 @@ Item {
readonly property Dashboard.Wrapper dashboard: dashboard readonly property Dashboard.Wrapper dashboard: dashboard
readonly property BarPopouts.Wrapper popouts: popouts readonly property BarPopouts.Wrapper popouts: popouts
readonly property Utilities.Wrapper utilities: utilities readonly property Utilities.Wrapper utilities: utilities
readonly property Sidebar.Wrapper sidebar: sidebar
anchors.fill: parent anchors.fill: parent
anchors.margins: Config.border.thickness anchors.margins: Config.border.thickness
@ -31,20 +33,20 @@ Item {
Osd.Wrapper { Osd.Wrapper {
id: osd id: osd
clip: root.visibilities.session clip: session.width > 0 || sidebar.width > 0
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: session.width anchors.rightMargin: session.width + sidebar.width
} }
Notifications.Wrapper { Notifications.Wrapper {
id: notifications id: notifications
visibilities: root.visibilities visibilities: root.visibilities
panel: root panels: root
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
@ -53,10 +55,13 @@ Item {
Session.Wrapper { Session.Wrapper {
id: session id: session
clip: sidebar.width > 0
visibilities: root.visibilities visibilities: root.visibilities
panels: root
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: sidebar.width
} }
Launcher.Wrapper { Launcher.Wrapper {
@ -101,8 +106,20 @@ Item {
id: utilities id: utilities
visibilities: root.visibilities visibilities: root.visibilities
sidebar: sidebar
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
} }
Sidebar.Wrapper {
id: sidebar
visibilities: root.visibilities
panels: root
anchors.top: notifications.bottom
anchors.bottom: utilities.top
anchors.right: parent.right
}
} }

View file

@ -1,7 +1,6 @@
import qs.components import qs.components
import qs.services import qs.services
import qs.config import qs.config
import Quickshell
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
@ -9,10 +8,10 @@ ShapePath {
id: root id: root
required property Wrapper wrapper required property Wrapper wrapper
required property var sidebar
readonly property real rounding: Config.border.rounding readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2 readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
property real fullHeightRounding: wrapper.height >= QsWindow.window?.height - Config.border.thickness * 2 ? -rounding : rounding
strokeWidth: -1 strokeWidth: -1
fillColor: Colours.palette.m3surface fillColor: Colours.palette.m3surface
@ -32,14 +31,14 @@ ShapePath {
relativeY: root.wrapper.height - root.roundingY * 2 relativeY: root.wrapper.height - root.roundingY * 2
} }
PathArc { PathArc {
relativeX: root.fullHeightRounding relativeX: root.sidebar.notifsRoundingX
relativeY: root.roundingY relativeY: root.roundingY
radiusX: Math.abs(root.fullHeightRounding) radiusX: root.sidebar.notifsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height) radiusY: Math.min(root.rounding, root.wrapper.height)
direction: root.fullHeightRounding < 0 ? PathArc.Clockwise : PathArc.Counterclockwise direction: PathArc.Counterclockwise
} }
PathLine { PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.fullHeightRounding : root.wrapper.width relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width
relativeY: 0 relativeY: 0
} }
PathArc { PathArc {
@ -52,8 +51,4 @@ ShapePath {
Behavior on fillColor { Behavior on fillColor {
CAnim {} CAnim {}
} }
Behavior on fullHeightRounding {
Anim {}
}
} }

View file

@ -10,7 +10,7 @@ Item {
id: root id: root
required property PersistentProperties visibilities required property PersistentProperties visibilities
required property Item panel required property Item panels
readonly property int padding: Appearance.padding.large readonly property int padding: Appearance.padding.large
anchors.top: parent.top anchors.top: parent.top
@ -27,15 +27,15 @@ Item {
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
height += list.itemAtIndex(i)?.nonAnimHeight ?? 0; height += list.itemAtIndex(i)?.nonAnimHeight ?? 0;
if (visibilities && panel) { if (visibilities && panels) {
if (visibilities.osd) { if (visibilities.osd) {
const h = panel.osd.y - Config.border.rounding * 2 - padding * 2; const h = panels.osd.y - Config.border.rounding * 2 - padding * 2;
if (height > h) if (height > h)
height = h; height = h;
} }
if (visibilities.session) { if (visibilities.session) {
const h = panel.session.y - Config.border.rounding * 2 - padding * 2; const h = panels.session.y - Config.border.rounding * 2 - padding * 2;
if (height > h) if (height > h)
height = h; height = h;
} }

View file

@ -1,21 +1,39 @@
import qs.components
import qs.config import qs.config
import Quickshell
import QtQuick import QtQuick
Item { Item {
id: root id: root
required property PersistentProperties visibilities required property var visibilities
required property Item panel required property Item panels
visible: height > 0 visible: height > 0
implicitWidth: Math.max(panels.sidebar.width, content.implicitWidth)
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth
states: State {
name: "hidden"
when: root.visibilities.sidebar && Config.sidebar.enabled
PropertyChanges {
root.implicitHeight: 0
}
}
transitions: Transition {
Anim {
target: root
property: "implicitHeight"
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Content { Content {
id: content id: content
visibilities: root.visibilities visibilities: root.visibilities
panel: root.panel panels: root.panels
} }
} }

View file

@ -9,6 +9,8 @@ Item {
id: root id: root
required property PersistentProperties visibilities required property PersistentProperties visibilities
required property var panels
readonly property real nonAnimWidth: content.implicitWidth
visible: width > 0 visible: width > 0
implicitWidth: 0 implicitWidth: 0
@ -19,7 +21,7 @@ Item {
when: root.visibilities.session && Config.session.enabled when: root.visibilities.session && Config.session.enabled
PropertyChanges { PropertyChanges {
root.implicitWidth: content.implicitWidth root.implicitWidth: root.nonAnimWidth
} }
} }
@ -41,7 +43,7 @@ Item {
Anim { Anim {
target: root target: root
property: "implicitWidth" property: "implicitWidth"
easing.bezierCurve: root.visibilities.osd ? Appearance.anim.curves.expressiveDefaultSpatial : Appearance.anim.curves.emphasized easing.bezierCurve: root.panels.osd.width > 0 ? Appearance.anim.curves.expressiveDefaultSpatial : Appearance.anim.curves.emphasized
} }
} }
] ]

View file

@ -0,0 +1,52 @@
import qs.components
import qs.services
import qs.config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
required property Wrapper wrapper
required property var panels
readonly property real rounding: Config.border.rounding
readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width
readonly property real utilsRoundingX: utilsWidthDiff < rounding * 2 ? utilsWidthDiff / 2 : rounding
strokeWidth: -1
fillColor: Colours.palette.m3surface
PathLine {
relativeX: -root.wrapper.width - root.notifsRoundingX
relativeY: 0
}
PathArc {
relativeX: root.notifsRoundingX
relativeY: root.rounding
radiusX: root.notifsRoundingX
radiusY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
relativeX: -root.utilsRoundingX
relativeY: root.rounding
radiusX: root.utilsRoundingX
radiusY: root.rounding
}
PathLine {
relativeX: root.wrapper.width + root.utilsRoundingX
relativeY: 0
}
Behavior on fillColor {
CAnim {}
}
}

View file

@ -0,0 +1,19 @@
import qs.config
import QtQuick
import QtQuick.Layouts
Item {
id: root
required property var visibilities
implicitWidth: layout.implicitWidth
implicitHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.fill: parent
spacing: Appearance.spacing.normal
}
}

View file

@ -0,0 +1,66 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.config
import QtQuick
Item {
id: root
required property var visibilities
required property var panels
visible: width > 0
implicitWidth: 0
implicitHeight: 0
states: State {
name: "visible"
when: root.visibilities.sidebar && Config.sidebar.enabled
PropertyChanges {
root.implicitWidth: Config.sidebar.sizes.width
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
target: root
property: "implicitWidth"
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
},
Transition {
from: "visible"
to: ""
Anim {
target: root
property: "implicitWidth"
easing.bezierCurve: root.panels.osd.width > 0 || root.panels.session.width > 0 ? Appearance.anim.curves.expressiveDefaultSpatial : Appearance.anim.curves.emphasized
}
}
]
Loader {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.padding.large
visible: false
active: true
Component.onCompleted: active = Qt.binding(() => (root.visibilities.sidebar && Config.sidebar.enabled) || root.visible)
sourceComponent: Content {
visibilities: root.visibilities
}
}
}

View file

@ -8,6 +8,7 @@ ShapePath {
id: root id: root
required property Wrapper wrapper required property Wrapper wrapper
required property var sidebar
readonly property real rounding: Config.border.rounding readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2 readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
@ -31,13 +32,13 @@ ShapePath {
relativeY: -(root.wrapper.height - root.roundingY * 2) relativeY: -(root.wrapper.height - root.roundingY * 2)
} }
PathArc { PathArc {
relativeX: root.rounding relativeX: root.sidebar.utilsRoundingX
relativeY: -root.roundingY relativeY: -root.roundingY
radiusX: root.rounding radiusX: root.sidebar.utilsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height) radiusY: Math.min(root.rounding, root.wrapper.height)
} }
PathLine { PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding * 2 : root.wrapper.width relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.utilsRoundingX : root.wrapper.width
relativeY: 0 relativeY: 0
} }
PathArc { PathArc {

View file

@ -9,6 +9,8 @@ Item {
id: root id: root
required property var visibilities required property var visibilities
required property Item sidebar
readonly property PersistentProperties props: PersistentProperties { readonly property PersistentProperties props: PersistentProperties {
property bool recordingListExpanded: false property bool recordingListExpanded: false
property string recordingConfirmDelete property string recordingConfirmDelete
@ -16,10 +18,11 @@ Item {
reloadableId: "utilities" reloadableId: "utilities"
} }
readonly property bool shouldBeActive: visibilities.sidebar || (visibilities.utilities && Config.utilities.enabled)
visible: height > 0 visible: height > 0
implicitHeight: 0 implicitHeight: 0
implicitWidth: Config.utilities.sizes.width implicitWidth: Math.max(sidebar.width, Config.utilities.sizes.width)
onStateChanged: { onStateChanged: {
if (state === "visible" && timer.running) { if (state === "visible" && timer.running) {
@ -30,7 +33,7 @@ Item {
states: State { states: State {
name: "visible" name: "visible"
when: root.visibilities.utilities when: root.shouldBeActive
PropertyChanges { PropertyChanges {
root.implicitHeight: content.implicitHeight + Appearance.padding.large * 2 root.implicitHeight: content.implicitHeight + Appearance.padding.large * 2
@ -67,7 +70,7 @@ Item {
running: true running: true
interval: Appearance.anim.durations.extraLarge interval: Appearance.anim.durations.extraLarge
onTriggered: { onTriggered: {
content.active = Qt.binding(() => (root.visibilities.utilities && Config.utilities.enabled) || root.visible); content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true; content.visible = true;
} }
} }
@ -77,13 +80,13 @@ Item {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.padding.large anchors.margins: Appearance.padding.large
visible: false visible: false
active: true active: true
sourceComponent: Content { sourceComponent: Content {
implicitWidth: Config.utilities.sizes.width - Appearance.padding.large * 2
props: root.props props: root.props
visibilities: root.visibilities visibilities: root.visibilities
} }