Skip to content

Commit

Permalink
feat: add read only status (#264)
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre-Yves Lapersonne <[email protected]>
  • Loading branch information
pylapp committed Feb 5, 2025
1 parent ce1bba0 commit 2ba6dc2
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,22 @@
uuid = "B4A15A02-0B7C-4A17-81BE-9CA389B02C0B"
type = "0"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "B2039618-7129-4C46-B831-E1CBCBA78082"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxLabel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "95"
endingLineNumber = "95"
landmarkName = "labelColor"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class CheckboxConfigurationModel: ComponentConfiguration {

// MARK: - Properties

@Published var isEnabled: Bool {
@Published var status: DesignToolboxCheckboxStatus {
didSet { updateCode() }
}

Expand Down Expand Up @@ -55,7 +55,27 @@ final class CheckboxConfigurationModel: ComponentConfiguration {

// MARK: - Internal types

enum DesignToolboxCheckboxLayout: CaseIterable, CustomStringConvertible { // OUDSCheckbox.Layouy is not accessible
enum DesignToolboxCheckboxStatus: CaseIterable, CustomStringConvertible { // CheckboxInternalState is not accessible
case enabled
case disabled
case readOnly

// No l10n, tehchnical names
var description: String {
switch self {
case .enabled:
"Enabled"
case .disabled:
"Disabled"
case .readOnly:
"Read only"
}
}

var id: String { description }
}

enum DesignToolboxCheckboxLayout: CaseIterable, CustomStringConvertible { // OUDSCheckbox.Layout is not accessible
case selectorOnly
case `default`
case inverse
Expand All @@ -78,7 +98,7 @@ final class CheckboxConfigurationModel: ComponentConfiguration {
// MARK: - Initializer

override init() {
isEnabled = true
status = .enabled
selectorState = .selected
layout = .selectorOnly
helperText = true
Expand Down Expand Up @@ -110,7 +130,7 @@ final class CheckboxConfigurationModel: ComponentConfiguration {
}

private var disableCode: String {
".disable(\(isEnabled ? "false" : "true"))"
".disable(\(status != .enabled ? "false" : "true"))"
}

private var helperTextPatern: String {
Expand All @@ -134,7 +154,7 @@ final class CheckboxConfigurationModel: ComponentConfiguration {
}

private var isErrorPattern: String {
if isError && isEnabled {
if isError && status == .enabled {
return ", isError: true"
} else {
return ""
Expand All @@ -157,9 +177,11 @@ struct CheckboxConfiguration: View {

var body: some View {
VStack(alignment: .leading, spacing: theme.spaces.spaceFixedMedium) {
Toggle("app_common_enabled_label", isOn: $model.isEnabled)
.typeHeadingMedium(theme)
.foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme))
DesignToolboxChoicePicker(title: "app_common_enabled_label", selection: $model.status) {
ForEach(CheckboxConfigurationModel.DesignToolboxCheckboxStatus.allCases, id: \.id) { state in
Text(LocalizedStringKey(state.description)).tag(state)
}
}

DesignToolboxChoicePicker(title: "app_components_checkbox_selection_label", selection: $model.selectorState) {
ForEach(OUDSCheckbox.SelectorState.allCases, id: \.id) { state in
Expand Down Expand Up @@ -191,7 +213,7 @@ struct CheckboxConfiguration: View {
Toggle("app_components_common_onError_label", isOn: $model.isError)
.typeHeadingMedium(theme)
.foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme))
.disabled(!model.isEnabled)
.disabled(model.status != .enabled)

DisclosureGroup("Edit texts") {
DesignToolboxTextField(text: $model.labelContent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ private struct CheckboxDemo: View {
if model.layout == .selectorOnly {
HStack(alignment: .center) {
Spacer()
OUDSCheckbox(state: $model.selectorState)
.disabled(!model.isEnabled)
OUDSCheckbox(state: $model.selectorState,
isError: model.isError && model.status == CheckboxConfigurationModel.DesignToolboxCheckboxStatus.enabled,
isReadOnly: model.status == CheckboxConfigurationModel.DesignToolboxCheckboxStatus.readOnly)
.disabled(isDisabled())
Spacer()
}
} else {
Expand All @@ -87,9 +89,10 @@ private struct CheckboxDemo: View {
helperText: helperTextContent,
icon: icon,
isInversed: model.layout == CheckboxConfigurationModel.DesignToolboxCheckboxLayout.inverse,
isError: model.isError && model.isEnabled,
isError: model.isError && model.status == CheckboxConfigurationModel.DesignToolboxCheckboxStatus.enabled,
isReadOnly: model.status == CheckboxConfigurationModel.DesignToolboxCheckboxStatus.readOnly,
divider: model.divider)
.disabled(!model.isEnabled)
.disabled(isDisabled())
}
}
.padding(.all, theme.spaces.spaceFixedMedium)
Expand All @@ -103,4 +106,8 @@ private struct CheckboxDemo: View {
private var icon: Image? {
model.icon ? Image(decorative: "ic_heart") : nil
}

private func isDisabled() -> Bool {
model.status == CheckboxConfigurationModel.DesignToolboxCheckboxStatus.disabled
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ struct OUDSCheckboxLabel: View {

private var labelColor: Color {
switch internalState {
case .enabled, .pressed, .hover:
case .enabled, .pressed, .hover, .readOnly:
(items.isError ? theme.colors.colorContentStatusNegative : theme.colors.colorContentDefault)
.color(for: colorScheme)
case .disabled:
Expand All @@ -103,7 +103,7 @@ struct OUDSCheckboxLabel: View {

private var iconColor: Color {
switch internalState {
case .enabled, .pressed, .hover:
case .enabled, .pressed, .hover, .readOnly:
theme.colors.colorContentDefault.color(for: colorScheme)
case .disabled:
theme.colors.colorContentDisabled.color(for: colorScheme)
Expand All @@ -112,7 +112,7 @@ struct OUDSCheckboxLabel: View {

private var helperTextColor: Color {
switch internalState {
case .enabled, .pressed, .hover:
case .enabled, .pressed, .hover, .readOnly:
theme.colors.colorContentMuted.color(for: colorScheme)
case .disabled:
theme.colors.colorContentDisabled.color(for: colorScheme)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct OUDSCheckboxLabeledStyle: ButtonStyle {
let selectorState: OUDSCheckbox.SelectorState
let items: OUDSCheckboxLabel.Items
let isInversed: Bool
let isReadOnly: Bool

@State private var isHover: Bool = false
@Environment(\.isEnabled) private var isEnabled
Expand Down Expand Up @@ -72,12 +73,16 @@ struct OUDSCheckboxLabeledStyle: ButtonStyle {
theme.select.selectColorBgHover.color(for: colorScheme)
case .pressed:
theme.select.selectColorBgPressed.color(for: colorScheme)
case .disabled:
case .disabled, .readOnly:
theme.select.selectColorBgDisabled.color(for: colorScheme)
}
}

private func internalState(isPressed: Bool) -> OUDSInternalCheckboxState {
if isReadOnly {
return .readOnly
}

if !isEnabled {
return .disabled
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct OUDSCheckboxNestedStyle: ButtonStyle {

let selectorState: OUDSCheckbox.SelectorState
let isError: Bool
let isReadOnly: Bool

@State private var isHover: Bool = false
@Environment(\.isEnabled) private var isEnabled
Expand All @@ -38,6 +39,10 @@ struct OUDSCheckboxNestedStyle: ButtonStyle {
// MARK: - Helpers

private func internalState(isPressed: Bool) -> OUDSInternalCheckboxState {
if isReadOnly {
return .readOnly
}

if !isEnabled {
return .disabled
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private struct CheckboxSelectorButtonForegroundModifier: ViewModifier {
return hoverColor.color(for: colorScheme)
case .pressed:
return pressedColor.color(for: colorScheme)
case .disabled:
case .disabled, .readOnly:
return disabledColor.color(for: colorScheme)
}
}
Expand Down Expand Up @@ -134,7 +134,7 @@ private struct CheckboxSelectorButtonBackgroundModifier: ViewModifier {
return hoverColor
case .pressed:
return pressedColor
case .disabled:
case .disabled, .readOnly:
return disabledColor
}
}
Expand Down Expand Up @@ -261,7 +261,7 @@ private struct CheckboxSelectorButtonBorderModifier: ViewModifier {
return hoverColor
case .pressed:
return pressedColor
case .disabled:
case .disabled, .readOnly:
return disabledColor
}
}
Expand Down Expand Up @@ -313,7 +313,7 @@ private struct CheckboxSelectorButtonBorderModifier: ViewModifier {
return hoverWidth
case .pressed:
return pressedWidth
case .disabled:
case .disabled, .readOnly:
return disabledWidth
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ enum OUDSInternalCheckboxState {
/// The component is being pressed
case pressed

/// The user cannot interart with the component. Also "read only" cases.
/// The user cannot interart with the component.
case disabled

// .loading not managed yet, for next version
/// The component is not disabled but user cannoit interact with it still. Almost enabled.
case readOnly

// .focus not managed as not that much customizable
// .skeleton not managed as dedicated view in the end
}
63 changes: 45 additions & 18 deletions OUDS/Core/Components/Sources/Checkbox/OUDSCheckbox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ import SwiftUI
/// // The nested layout will be used here.
/// OUDSCheckbox(state: $state)
///
/// // A leading checkbox with a label
/// // A leading checkbox with a label.
/// // The default layout will be used here.
/// OUDSCheckbox(state: $state, label: "Hello world")
///
/// // A leading checkbox with a label, but in read only mode (user cannot interact yet, but not disabled).
/// // The default layout will be used here.
/// OUDSCheckbox(state: $state, label: "Hello world", isReadOnly: true)
///
/// // A leading checkbox with a label, and an helper text.
/// // The default layout will be used here.
/// OUDSCheckbox(state: $state, label: "Bazinga!", helperText: "Doll-Dagga Buzz-Buzz Ziggety-Zag")
Expand Down Expand Up @@ -111,16 +115,18 @@ public struct OUDSCheckbox: View {

/// The three available layouts for this component
private enum Layout {
/// Displays only the checkbox selector, wiht a flag saying if there is an error context
case selectorOnly(Bool)
/// Displays only the checkbox selector, with a first flag saying if there is an error context and a second is in read only mode
case selectorOnly(Bool, Bool)

/// Checkbox selector in leading position, icon in trailing position, like LTR mode.
/// Details are defined in the ``OUDSCheckboxLabel.Items``.
case `default`(OUDSCheckboxLabel.Items)
/// Contains a flag saying if read only mode or not.
case `default`(OUDSCheckboxLabel.Items, Bool)

/// Icon in leading position, checkbox selector in trailing position, like RTL mode
/// Details are defined in the ``OUDSCheckboxLabel.Items``.
case inverse(OUDSCheckboxLabel.Items)
/// Contains a flag saying if read only mode or not.
case inverse(OUDSCheckboxLabel.Items, Bool)
}

// MARK: - Initializers
Expand All @@ -130,9 +136,12 @@ public struct OUDSCheckbox: View {
/// - Parameters:
/// - state: A binding to a property that determines wether the selector is ticked, unticked or preticked.
/// - isError: True if the look and feel of the component must reflect an error state, default set to `false`
public init(state: Binding<SelectorState>, isError: Bool = false) {
/// - isReadOnly: True if component is in read only, i.e. not really disabled but user cannot interact with it yet, default set to `false`
public init(state: Binding<SelectorState>,
isError: Bool = false,
isReadOnly: Bool = false) {
self._state = state
self.layout = .selectorOnly(isError)
self.layout = .selectorOnly(isError, isReadOnly)
}

/// Creates a checkbox with label and optional helper text, icon, divider.
Expand All @@ -144,41 +153,59 @@ public struct OUDSCheckbox: View {
/// - icon: An optional icon
/// - isInversed: `True` of the checkbox selector must be in trailing position,` false` otherwise. Default to `false`
/// - isError: `True` if the look and feel of the component must reflect an error state, default set to `false`
/// - isReadOnly: True if component is in read only, i.e. not really disabled but user cannot interact with it yet, default set to `false`
/// - divider: If `true` a divider is added at the bottom of the view.
public init(state: Binding<SelectorState>,
label: String,
helperText: String? = nil,
icon: Image? = nil,
isInversed: Bool = false,
isError: Bool = false,
isReadOnly: Bool = false,
divider: Bool = false) {
self._state = state
if isInversed {
self.layout = .inverse(.init(label: label, helperText: helperText, icon: icon, isError: isError, divider: divider))
self.layout = .inverse(.init(label: label,
helperText: helperText,
icon: icon,
isError: isError,
divider: divider),
isReadOnly)
} else {
self.layout = .default(.init(label: label, helperText: helperText, icon: icon, isError: isError, divider: divider))
self.layout = .default(.init(label: label,
helperText: helperText,
icon: icon,
isError: isError,
divider: divider),
isReadOnly)
}
}

// MARK: Body

public var body: some View {
switch layout {
case .default(let label):
case let .default(label, isReadOnly):
Button("") {
$state.wrappedValue.toggle()
if !isReadOnly {
$state.wrappedValue.toggle()
}
}
.buttonStyle(OUDSCheckboxLabeledStyle(selectorState: $state.wrappedValue, items: label, isInversed: false))
case .inverse(let label):
.buttonStyle(OUDSCheckboxLabeledStyle(selectorState: $state.wrappedValue, items: label, isInversed: false, isReadOnly: isReadOnly))
case let .inverse(label, isReadOnly):
Button("") {
$state.wrappedValue.toggle()
if !isReadOnly {
$state.wrappedValue.toggle()
}
}
.buttonStyle(OUDSCheckboxLabeledStyle(selectorState: $state.wrappedValue, items: label, isInversed: true))
case .selectorOnly(let isError):
.buttonStyle(OUDSCheckboxLabeledStyle(selectorState: $state.wrappedValue, items: label, isInversed: true, isReadOnly: isReadOnly))
case let .selectorOnly(isError, isReadOnly):
Button("") {
$state.wrappedValue.toggle()
if !isReadOnly {
$state.wrappedValue.toggle()
}
}
.buttonStyle(OUDSCheckboxNestedStyle(selectorState: $state.wrappedValue, isError: isError))
.buttonStyle(OUDSCheckboxNestedStyle(selectorState: $state.wrappedValue, isError: isError, isReadOnly: isReadOnly))
}
}
}

0 comments on commit 2ba6dc2

Please sign in to comment.