diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index f808e999..27693abe 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18AE87502C06FDA60075F181 /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AE874F2C06FDA60075F181 /* Carousel.swift */; }; 18AE87542C06FE610075F181 /* CarouselChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */; }; + 18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; }; @@ -241,6 +242,7 @@ 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18AE874F2C06FDA60075F181 /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselChangeLog.txt; sourceTree = ""; }; + 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotAlignmentModel.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; @@ -501,6 +503,7 @@ isa = PBXGroup; children = ( 18AE874F2C06FDA60075F181 /* Carousel.swift */, + 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */, 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */, ); path = Carousel; @@ -1300,6 +1303,7 @@ EA8E40932A82889500934ED3 /* TooltipDialog.swift in Sources */, 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */, EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */, + 18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */, 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */, EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */, EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */, diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 84bfd340..a90e4e4b 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -30,4 +30,282 @@ open class Carousel: View { super.init(coder: coder) } + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// Aspect-ratio options for tilelet in the carousel. If 'none' is passed, the tilelet will take the height of the tallest item in the carousel. + open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } + + /// Data used to render tilelets in the carousel. + open var data: Array? = [] { didSet { setNeedsUpdate() } } + + /// Space between each tile. The default value will be 24px in tablet and 12px in mobile. + open var gutter: Gutter? { + get { return _gutter } + set { + if let newValue { + _gutter = newValue + } else { + _gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + } + setNeedsUpdate() + } + } + + /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + open var layout: Layout? { + get { return _layout } + set { + if let newValue { + _layout = newValue + } else { + _layout = UIDevice.isIPad ? .threeUP : .oneUP + } + setNeedsUpdate() + } + } + + /// A callback when moving the carousel. Returns event object and selectedGroupIndex. + open var onChange: ((Int) -> Void)? { // TO DO: return object and index + get { nil } + set { + onChangeCancellable?.cancel() + if let newValue { + onChangeCancellable = onChangePublisher + .sink { c in + newValue(c) + } + } + } + } + + // TO DO: pagination + /// Config object for pagination. + /// Custom data type for pagination prop in this component. + + /// If provided, will determine the conditions to render the pagination arrows. + open var paginationDisplay: PaginationDisplay? { + get { return _paginationDisplay } + set { + if let newValue { + _paginationDisplay = newValue + } else { + _paginationDisplay = .none + } + setNeedsUpdate() + } + } + + /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. + /// The default value will be 12px in tablet and 8px in mobile. + open var paginationInset: CGFloat? { + get { return _paginationInset } + set { + if let newValue { + _paginationInset = newValue + } else { + _paginationInset = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X + } + setNeedsUpdate() + } + } + + /// Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. + open var peek: Peek? { + get { return _peek } + set { + if let newValue { + _peek = newValue + } else { + _peek = .none + } + setNeedsUpdate() + } + } + + // TO DO: renderItem + /// Render item function. This will pass the data array and expects a react component in return. +// open var renderItem: ([] -> Any)? { // TO DO: return object and index +// get { nil } +// set { +// onScrollCancellable?.cancel() +// if let newValue { +// onScrollCancellable = onScrollPublisher +// .sink { c in +// newValue(c) +// } +// } +// } +// } +// open var renderItem: ([] -> Void)? {} + + /// The initial visible slide's index in the carousel. + open var selectedIndex: Int? { didSet { setNeedsUpdate() } } + + /// If provided, will set the alignment for slot content when the slots has different heights. + open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } + + /// + open var hidePaginationBorder: Bool = false { didSet { setNeedsUpdate() } } + + /// viewport + /// viewportOverride + /// viewportOverrideObjectType + /// Custom data type for viewportOverride prop in this component. + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + // Sizes are from InVision design specs. + internal var containerSize: CGSize { CGSize(width: 320, height: 44) } + internal var containerView = View().with { + $0.clipsToBounds = true + } + + /// Previous button to show previous slide. + private let previousButton: PaginationButton = .init(type: .previous) + + /// Next button to show next slide. + private let nextButton: PaginationButton = .init(type: .next) + + /// A publisher for when the scrubber position changes. Passes parameters (position). + open var onChangePublisher = PassthroughSubject() + private var onChangeCancellable: AnyCancellable? + + /// A publisher for when the carousel moves. Passes parameters (data). +// open var onScrollPublisher = PassthroughSubject, Never>() +// private var onScrollCancellable: AnyCancellable? + + internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP + internal var _paginationDisplay: PaginationDisplay = .none + internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X + internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + internal var _peek: Peek = .none + + private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } +// private var minPaginationInset: CGFloat { +// return UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X +// } + + //-------------------------------------------------- + // MARK: - Enums + //-------------------------------------------------- + /// Enum used to describe the aspect ratios used for this component. + public enum AspectRatio: String, CaseIterable { + case ratio1x1 = "1:1" + case ratio3x4 = "3:4" + case ratio4x3 = "4:3" + case ratio2x3 = "2:3" + case ratio3x2 = "3:2" + case ratio9x16 = "9:16" + case ratio16x9 = "16:9" + case ratio1x2 = "1:2" + case ratio2x1 = "2:1" + case none + } + + /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + public enum Layout: String, CaseIterable { + case oneUP = "1UP" + case twoUP = "2UP" + case threeUP = "3UP" + case fourUP = "4UP" + case fiveUP = "5UP" + case sixUP = "6UP" + + var value: Int { + switch self { + case .oneUP: + 1 + case .twoUP: + 2 + case .threeUP: + 3 + case .fourUP: + 4 + case .fiveUP: + 5 + case .sixUP: + 6 + } + } + } + + /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + public enum Gutter: String, CaseIterable { + case twelvePX = "12px" + case twentyFourPX = "24px" + + var value: CGFloat { + switch self { + case .twelvePX: + VDSLayout.space12X + case .twentyFourPX: + VDSLayout.space24X + } + } + } + + /// Enum used to describe the pagination display for this component. + public enum PaginationDisplay: String, CaseIterable { + case onHover, persistent, none + } + + /// Enum used to describe the peek for this component. Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. + public enum Peek: String, CaseIterable { + case standard, minimum, none + } + + /// Enum used to describe the pagination kind for pagination button icons. + public enum PaginationKind: String, CaseIterable { + case ghost, lowContrast, highContrast + } // TO DO: it should be used as pagination properties, validate desc stmt. and API - passing data is different. + + // TO DO: move to model class + /// Enum used to describe the vertical of slotAlignment. + public enum Vertical: String, CaseIterable { + case top, middle, bottom + } + + /// Enum used to describe the horizontal of slotAlignment. + public enum Horizontal: String, CaseIterable { + case left, center, right + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func initialSetup() { + super.initialSetup() + } + + open override func setup() { + super.setup() + isAccessibilityElement = false + + addSubview(containerView) + containerView + .pinTop() + .pinBottom() + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .height(containerSize.height) +// .width(containerSize.width) + + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + } + + open override func updateView() { + super.updateView() + } + + open override func reset() { +// for subview in subviews { +// for recognizer in subview.gestureRecognizers ?? [] { +// subview.removeGestureRecognizer(recognizer) +// } +// } + super.reset() + } } diff --git a/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift b/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift new file mode 100644 index 00000000..5a8ec661 --- /dev/null +++ b/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift @@ -0,0 +1,25 @@ +// +// CarouselSlotAlignmentModel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 31/05/24. +// + +import Foundation + +/// Custom data type for slotAlignment prop for 'Carousel' component. +extension Carousel { + public struct CarouselSlotAlignmentModel { + + /// Text that shown to an indicator for legend + public var vertical: String + + /// Date to an indicator + public var horizontal: String + + public init(vertical: String, horizontal: String) { + self.vertical = vertical + self.horizontal = horizontal + } + } +} diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index d145ea0a..6582ce4a 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -26,6 +26,7 @@ Using the system allows designers and developers to collaborate more easily and - ``ButtonIcon`` - ``ButtonGroup`` - ``CalendarBase`` +- ``Carousel`` - ``CarouselScrollbar`` - ``Checkbox`` - ``CheckboxItem``