diff --git a/Sources/StorybookKit/Hosting/_ViewHost.swift b/Sources/StorybookKit/Hosting/_ViewHost.swift index 3dcb345..dc7226d 100644 --- a/Sources/StorybookKit/Hosting/_ViewHost.swift +++ b/Sources/StorybookKit/Hosting/_ViewHost.swift @@ -5,21 +5,23 @@ struct _ViewHost: { private let instantiate: @MainActor () -> ContentView - private let _update: - @MainActor (_ uiView: ContentView, _ context: Context) -> Void + private let _update: @MainActor (_ uiView: ContentView, _ context: Context) -> Void + private let maxWidth: Double init( + maxWidth: Double, instantiate: @escaping @MainActor () -> ContentView, update: @escaping @MainActor (_ uiViewController: ContentView, _ context: Context) -> - Void = { _, _ in } + Void = { _, _ in } ) { + self.maxWidth = maxWidth self.instantiate = instantiate self._update = update } func makeUIView(context: Context) -> _Label { let instantiated = instantiate() - return .init(contentView: instantiated) + return .init(maxWidth: maxWidth, contentView: instantiated) } func updateUIView(_ uiView: _Label, context: Context) { @@ -31,8 +33,13 @@ struct _ViewHost: final class _Label: UILabel { let contentView: ContentView + let maxWidth: Double - init(contentView: ContentView) { + init( + maxWidth: Double, + contentView: ContentView + ) { + self.maxWidth = maxWidth self.contentView = contentView super.init(frame: .null) addSubview(contentView) @@ -44,7 +51,9 @@ final class _Label: UILabel { fatalError("init(coder:) has not been implemented") } - override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { + override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) + -> CGRect + { var targetSize = bounds.size @@ -56,7 +65,17 @@ final class _Label: UILabel { targetSize.height = UIView.layoutFittingCompressedSize.height } - let size = contentView.systemLayoutSizeFitting(targetSize) + var size = contentView.systemLayoutSizeFitting(targetSize) + + if size.width > maxWidth { + targetSize.width = maxWidth + size = contentView.systemLayoutSizeFitting( + targetSize, + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel + ) + } + return .init(origin: .zero, size: size) } diff --git a/Sources/StorybookKit/Primitives/Book.swift b/Sources/StorybookKit/Primitives/Book.swift index 78ce664..e9df651 100644 --- a/Sources/StorybookKit/Primitives/Book.swift +++ b/Sources/StorybookKit/Primitives/Book.swift @@ -22,25 +22,32 @@ import ResultBuilderKit import SwiftUI +public protocol BookProvider { + static var body: BookNavigationLink { get } +} + public protocol BookType: View { } public final class BookStore: ObservableObject { - @Published var historyLinks: [BookNavigationLink] = [] + @Published var historyPages: [BookNavigationLink] = [] public let title: String - public let links: [BookNavigationLink] + public let groups: [BookPageGroup] + + private let allPages: [BookNavigationLink] private let userDefaults = UserDefaults(suiteName: "jp.eure.storybook2") ?? .standard public init( title: String, - @ArrayBuilder links: () -> [BookNavigationLink] + @ArrayBuilder groups: () -> [BookPageGroup] ) { self.title = title - self.links = links().sorted(by: { $0.title < $1.title }) + self.groups = groups().sorted(by: { $0.title < $1.title }) + self.allPages = self.groups.flatMap { $0.pages } updateHistory() } @@ -50,29 +57,36 @@ public final class BookStore: ObservableObject { let indexes = userDefaults.array(forKey: "history") as? [Int] ?? [] let _links = indexes.compactMap { index -> BookNavigationLink? in - guard let link = links.first(where: { $0.declarationIdentifier.index == index }) else { + guard let page = allPages.first(where: { $0.declarationIdentifier.index == index }) else { return nil } - return link + return page } - historyLinks = _links + historyPages = _links } func onOpen(link: BookNavigationLink) { + guard allPages.contains(where: { $0.id == link.id }) else { + return + } + let index = link.declarationIdentifier.index var current = userDefaults.array(forKey: "history") as? [Int] ?? [] if let index = current.firstIndex(of: index) { current.remove(at: index) } + current.insert(index, at: 0) current = Array(current.prefix(5)) userDefaults.set(current, forKey: "history") + print("Update history", current) + updateHistory() } @@ -93,18 +107,19 @@ public struct BookContainer: BookType { NavigationView { List { Section { - ForEach(store.historyLinks) { link in + ForEach(store.historyPages) { link in link } } header: { Text("History") } - Section { - ForEach(store.links) { link in - link + + ForEach(store.groups) { group in + Section { + group + } header: { + Text(group.title) } - } header: { - Text("Contents") } } .listStyle(.grouped) diff --git a/Sources/StorybookKit/Primitives/BookNavigationLink.swift b/Sources/StorybookKit/Primitives/BookNavigationLink.swift index ed314df..2004d08 100644 --- a/Sources/StorybookKit/Primitives/BookNavigationLink.swift +++ b/Sources/StorybookKit/Primitives/BookNavigationLink.swift @@ -21,6 +21,7 @@ import Foundation import SwiftUI +import ResultBuilderKit public struct DeclarationIdentifier: Hashable, Codable { @@ -42,6 +43,29 @@ private func issueUniqueNumber() -> Int { return _counter } +public struct BookPageGroup: BookView, Identifiable { + + public var id: UUID = .init() + + public let title: String + public let pages: [BookPage] + + public init( + title: String, + @ArrayBuilder pages: () -> [BookPage] + ) { + self.title = title + self.pages = pages().sorted(by: { $0.title < $1.title }) + } + + public var body: some View { + ForEach(pages) { page in + page + } + } + +} + /// A component that displays a disclosure view. public struct BookNavigationLink: BookView, Identifiable { @@ -69,12 +93,10 @@ public struct BookNavigationLink: BookView, Identifiable { NavigationLink( title, destination: { - GeometryReader(content: { geometry in - ScrollView { - destination - .frame(width: geometry.size.width) - } - }) + List { + destination + } + .listStyle(.plain) .onAppear(perform: { context?.onOpen(link: self) }) diff --git a/Sources/StorybookKit/Primitives/BookPage.swift b/Sources/StorybookKit/Primitives/BookPage.swift index 3c6ba3c..500d4d9 100644 --- a/Sources/StorybookKit/Primitives/BookPage.swift +++ b/Sources/StorybookKit/Primitives/BookPage.swift @@ -22,25 +22,4 @@ import Foundation import SwiftUI -public struct BookPage: BookView { - - public let title: String - public let content: Content - - public init( - title: String, - @ViewBuilder content: () -> Content - ) { - self.title = title - self.content = content() - } - - public var body: some View { - BookText(title) - .font(.system(size: 32, weight: .bold)) - .padding(.top, 24) - .padding(.bottom, 18) - content - } - -} +public typealias BookPage = BookNavigationLink diff --git a/Sources/StorybookKit/Primitives/BookPreview.swift b/Sources/StorybookKit/Primitives/BookPreview.swift index 17d313c..0b63c0f 100644 --- a/Sources/StorybookKit/Primitives/BookPreview.swift +++ b/Sources/StorybookKit/Primitives/BookPreview.swift @@ -30,7 +30,7 @@ private struct FrameConstraint { var maxHeight: CGFloat? = nil } -public struct BookPreview: BookView { +public struct BookPreview: BookView { public struct Context { @@ -42,7 +42,7 @@ public struct BookPreview: BookView { } - public let viewBlock: @MainActor (inout Context) -> PlatformView + public let viewBlock: @MainActor (inout Context) -> UIView public let declarationIdentifier: DeclarationIdentifier @@ -55,7 +55,7 @@ public struct BookPreview: BookView { _ file: StaticString = #file, _ line: UInt = #line, title: String? = nil, - viewBlock: @escaping @MainActor (inout Context) -> PlatformView + viewBlock: @escaping @MainActor (inout Context) -> UIView ) { self.title = title @@ -70,35 +70,36 @@ public struct BookPreview: BookView { public var body: some View { - Group { - VStack { - if let title { - Text(title) - .font(.system(size: 17, weight: .semibold)) - } - _ViewHost( - instantiate: { - - var context: Context = .init() - let view = viewBlock(&context) - - // TODO: currently using workaround - Task { - controlView = context.controlView - } - - return _View(element: view, frameConstraint: frameConstraint) - }, - update: { _, _ in } - ) - - controlView + VStack { + if let title { + Text(title) + .font(.system(size: 17, weight: .semibold)) } + _ViewHost( + maxWidth: UIScreen.main.bounds.size.width /* so bad but it's for sizing with autolayout */, + instantiate: { + + var context: Context = .init() + let view = viewBlock(&context) + + // TODO: currently using workaround + Task { + controlView = context.controlView + } + + return _View(element: view, frameConstraint: frameConstraint) + }, + update: { _, _ in } + ) + + controlView + Text("\(file.description):\(line.description)") .font(.body.monospacedDigit()) BookSpacer(height: 16) + } } diff --git a/Sources/StorybookKitTextureSupport/BookNodePreview.swift b/Sources/StorybookKitTextureSupport/BookNodePreview.swift index ad35d65..d1d7746 100644 --- a/Sources/StorybookKitTextureSupport/BookNodePreview.swift +++ b/Sources/StorybookKitTextureSupport/BookNodePreview.swift @@ -25,20 +25,19 @@ import StorybookKit import TextureBridging import TextureSwiftSupport -public struct BookNodePreview: BookView { +public struct BookNodePreview: BookView { - private var backing: BookPreview> + private var backing: BookPreview - @MainActor public init( _ file: StaticString = #file, _ line: UInt = #line, title: String? = nil, - nodeBlock: @escaping @MainActor (BookPreview>.Context) -> Node + nodeBlock: @escaping @MainActor (inout BookPreview.Context) -> ASDisplayNode ) { self.backing = .init(file, line, title: title) { context in - let body = nodeBlock(context) + let body = nodeBlock(&context) let node = AnyDisplayNode { _, size in return LayoutSpec {