├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Development
├── DynamicFrameworkComponents
│ └── Component.swift
├── StaticLibraryComponents
│ └── Component.swift
├── Storybook.xcodeproj
│ ├── StorybookKit_Info.plist
│ ├── StorybookUI_Info.plist
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── xcshareddata
│ │ ├── IDETemplateMacros.plist
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ ├── Storybook-Package.xcscheme
│ │ └── SwiftUIDemoApp.xcscheme
└── SwiftUIDemoApp
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Component.swift
│ ├── ContentView.swift
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── SwiftUIDemoAppApp.swift
├── LICENSE
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
└── Sources
├── StorybookKit
├── BookGenerator.swift
├── BookLorem.swift
├── Compositions
│ ├── BookPadding.swift
│ ├── BookPattern.swift
│ └── BookSection.swift
├── Internals
│ ├── Hosting
│ │ ├── _ViewControllerHost.swift
│ │ └── _ViewHost.swift
│ ├── Preview
│ │ └── PreviewRegistryWrapper.swift
│ ├── TargetViewControllerKey.swift
│ ├── fuzzyMatch.swift
│ └── machOLoader.swift
├── Primitives
│ ├── Book.swift
│ ├── BookAction.swift
│ ├── BookPage.swift
│ ├── BookPresent.swift
│ ├── BookPreview.swift
│ ├── BookProvider.swift
│ ├── BookPush.swift
│ ├── BookStore.swift
│ ├── BookText.swift
│ └── BookView.swift
├── SearchBar.swift
├── Storybook.swift
├── StorybookDisplayRootView.swift
└── StorybookKit.swift
├── StorybookKitTextureSupport
├── BookNodePreview.swift
├── Info.plist
└── StorybookKitTextureSupport.h
├── StorybookMacrosPlugin
├── StorybookMacrosPlugin.swift
├── StorybookPageMacro.swift
└── StorybookPreviewMacro.swift
└── StorybookMacrosTests
├── StorybookPageTests.swift
└── StorybookPreviewTests.swift
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build-storybook-kit:
7 | runs-on: macos-15
8 | steps:
9 | - uses: maxim-lobanov/setup-xcode@v1.1
10 | with:
11 | xcode-version: "16.2"
12 | - uses: actions/checkout@v2
13 | - name: xcodebuild
14 | run: set -o pipefail && xcodebuild -scheme StorybookKit -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' | xcbeautify
15 | build-storybook-kit-texture-support:
16 | runs-on: macos-15
17 | steps:
18 | - uses: maxim-lobanov/setup-xcode@v1.1
19 | with:
20 | xcode-version: "16.2"
21 | - uses: actions/checkout@v2
22 | - name: xcodebuild
23 | run: set -o pipefail && xcodebuild -scheme StorybookKitTextureSupport -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' | xcbeautify
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | .DS_Store
6 |
7 | ## Build generated
8 | build/
9 | DerivedData/
10 |
11 | ## Various settings
12 | *.pbxuser
13 | !default.pbxuser
14 | *.mode1v3
15 | !default.mode1v3
16 | *.mode2v3
17 | !default.mode2v3
18 | *.perspectivev3
19 | !default.perspectivev3
20 | xcuserdata/
21 |
22 | ## Other
23 | *.moved-aside
24 | *.xccheckout
25 | *.xcscmblueprint
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | .build/
44 |
45 | # CocoaPods
46 | #
47 | # We recommend against adding the Pods directory to your .gitignore. However
48 | # you should judge for yourself, the pros and cons are mentioned at:
49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
50 | #
51 | Pods/
52 | #
53 | # Add this line if you want to avoid checking in source code from the Xcode workspace
54 | # *.xcworkspace
55 |
56 | # Carthage
57 | #
58 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
59 | # Carthage/Checkouts
60 |
61 | Carthage/Build
62 |
63 | # fastlane
64 | #
65 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
66 | # screenshots whenever they are needed.
67 | # For more information about the recommended setup visit:
68 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
69 |
70 | fastlane/report.xml
71 | fastlane/Preview.html
72 | fastlane/screenshots/**/*.png
73 | fastlane/test_output
74 |
75 | # Code Injection
76 | #
77 | # After new code Injection tools there's a generated folder /iOSInjectionProject
78 | # https://github.com/johnno1962/injectionforxcode
79 |
80 | iOSInjectionProject/
81 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Development/DynamicFrameworkComponents/Component.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | #Preview("Circle") {
25 | Circle()
26 | .fill(.purple)
27 | .frame(width: 100, height: 100)
28 | }
29 |
--------------------------------------------------------------------------------
/Development/StaticLibraryComponents/Component.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | #Preview("Circle") {
25 | Circle()
26 | .fill(.purple)
27 | .frame(width: 100, height: 100)
28 | }
29 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/StorybookKit_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/StorybookUI_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 70;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4B8438DA2D4CE6C500BE1DA9 /* libStaticLibraryComponents.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B8438CC2D4CE44C00BE1DA9 /* libStaticLibraryComponents.a */; };
11 | 4B8438DE2D4CE6E400BE1DA9 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B8438DD2D4CE6E400BE1DA9 /* SwiftUI.framework */; };
12 | 4B8438DF2D4CE6EC00BE1DA9 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B8438DD2D4CE6E400BE1DA9 /* SwiftUI.framework */; };
13 | 4BA377E92D4CE2F000D4E565 /* DynamicFrameworkComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BA377D62D4CE2F000D4E565 /* DynamicFrameworkComponents.framework */; };
14 | 4BA377EA2D4CE2F000D4E565 /* DynamicFrameworkComponents.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4BA377D62D4CE2F000D4E565 /* DynamicFrameworkComponents.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15 | 4BA377F42D4CE31D00D4E565 /* StorybookKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4BA377F32D4CE31D00D4E565 /* StorybookKit */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXContainerItemProxy section */
19 | 4B8438DB2D4CE6C500BE1DA9 /* PBXContainerItemProxy */ = {
20 | isa = PBXContainerItemProxy;
21 | containerPortal = 957A89282226610200CDD25D /* Project object */;
22 | proxyType = 1;
23 | remoteGlobalIDString = 4B8438CB2D4CE44C00BE1DA9;
24 | remoteInfo = StaticLibraryComponents;
25 | };
26 | 4BA377E72D4CE2F000D4E565 /* PBXContainerItemProxy */ = {
27 | isa = PBXContainerItemProxy;
28 | containerPortal = 957A89282226610200CDD25D /* Project object */;
29 | proxyType = 1;
30 | remoteGlobalIDString = 4BA377D52D4CE2F000D4E565;
31 | remoteInfo = DynamicFrameworkComponents;
32 | };
33 | /* End PBXContainerItemProxy section */
34 |
35 | /* Begin PBXCopyFilesBuildPhase section */
36 | 4B8438CA2D4CE44C00BE1DA9 /* CopyFiles */ = {
37 | isa = PBXCopyFilesBuildPhase;
38 | buildActionMask = 2147483647;
39 | dstPath = "include/$(PRODUCT_NAME)";
40 | dstSubfolderSpec = 16;
41 | files = (
42 | );
43 | runOnlyForDeploymentPostprocessing = 0;
44 | };
45 | 4BA377EF2D4CE2F000D4E565 /* Embed Frameworks */ = {
46 | isa = PBXCopyFilesBuildPhase;
47 | buildActionMask = 2147483647;
48 | dstPath = "";
49 | dstSubfolderSpec = 10;
50 | files = (
51 | 4BA377EA2D4CE2F000D4E565 /* DynamicFrameworkComponents.framework in Embed Frameworks */,
52 | );
53 | name = "Embed Frameworks";
54 | runOnlyForDeploymentPostprocessing = 0;
55 | };
56 | /* End PBXCopyFilesBuildPhase section */
57 |
58 | /* Begin PBXFileReference section */
59 | 4B0BDD7C2A87A4A200CF2633 /* Storybook-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "Storybook-ios"; path = ..; sourceTree = ""; };
60 | 4B8438CC2D4CE44C00BE1DA9 /* libStaticLibraryComponents.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStaticLibraryComponents.a; sourceTree = BUILT_PRODUCTS_DIR; };
61 | 4B8438DD2D4CE6E400BE1DA9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.2.sdk/System/iOSSupport/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
62 | 4BA377A72D4CE2C500D4E565 /* SwiftUIDemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIDemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
63 | 4BA377D62D4CE2F000D4E565 /* DynamicFrameworkComponents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DynamicFrameworkComponents.framework; sourceTree = BUILT_PRODUCTS_DIR; };
64 | /* End PBXFileReference section */
65 |
66 | /* Begin PBXFileSystemSynchronizedRootGroup section */
67 | 4B8438CD2D4CE44C00BE1DA9 /* StaticLibraryComponents */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = StaticLibraryComponents; sourceTree = ""; };
68 | 4BA377A82D4CE2C500D4E565 /* SwiftUIDemoApp */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = SwiftUIDemoApp; sourceTree = ""; };
69 | 4BA377D72D4CE2F000D4E565 /* DynamicFrameworkComponents */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = DynamicFrameworkComponents; sourceTree = ""; };
70 | /* End PBXFileSystemSynchronizedRootGroup section */
71 |
72 | /* Begin PBXFrameworksBuildPhase section */
73 | 4B8438C92D4CE44C00BE1DA9 /* Frameworks */ = {
74 | isa = PBXFrameworksBuildPhase;
75 | buildActionMask = 2147483647;
76 | files = (
77 | 4B8438DF2D4CE6EC00BE1DA9 /* SwiftUI.framework in Frameworks */,
78 | );
79 | runOnlyForDeploymentPostprocessing = 0;
80 | };
81 | 4BA377A42D4CE2C500D4E565 /* Frameworks */ = {
82 | isa = PBXFrameworksBuildPhase;
83 | buildActionMask = 2147483647;
84 | files = (
85 | 4BA377F42D4CE31D00D4E565 /* StorybookKit in Frameworks */,
86 | 4BA377E92D4CE2F000D4E565 /* DynamicFrameworkComponents.framework in Frameworks */,
87 | 4B8438DA2D4CE6C500BE1DA9 /* libStaticLibraryComponents.a in Frameworks */,
88 | );
89 | runOnlyForDeploymentPostprocessing = 0;
90 | };
91 | 4BA377D32D4CE2F000D4E565 /* Frameworks */ = {
92 | isa = PBXFrameworksBuildPhase;
93 | buildActionMask = 2147483647;
94 | files = (
95 | 4B8438DE2D4CE6E400BE1DA9 /* SwiftUI.framework in Frameworks */,
96 | );
97 | runOnlyForDeploymentPostprocessing = 0;
98 | };
99 | /* End PBXFrameworksBuildPhase section */
100 |
101 | /* Begin PBXGroup section */
102 | 4B0BDD7D2A87A4C800CF2633 /* Frameworks */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 4B8438DD2D4CE6E400BE1DA9 /* SwiftUI.framework */,
106 | );
107 | name = Frameworks;
108 | sourceTree = "";
109 | };
110 | 957A89272226610200CDD25D = {
111 | isa = PBXGroup;
112 | children = (
113 | 4B0BDD7C2A87A4A200CF2633 /* Storybook-ios */,
114 | 4BA377A82D4CE2C500D4E565 /* SwiftUIDemoApp */,
115 | 4BA377D72D4CE2F000D4E565 /* DynamicFrameworkComponents */,
116 | 4B8438CD2D4CE44C00BE1DA9 /* StaticLibraryComponents */,
117 | 957A89322226610200CDD25D /* Products */,
118 | 4B0BDD7D2A87A4C800CF2633 /* Frameworks */,
119 | );
120 | indentWidth = 2;
121 | sourceTree = "";
122 | tabWidth = 2;
123 | };
124 | 957A89322226610200CDD25D /* Products */ = {
125 | isa = PBXGroup;
126 | children = (
127 | 4BA377A72D4CE2C500D4E565 /* SwiftUIDemoApp.app */,
128 | 4BA377D62D4CE2F000D4E565 /* DynamicFrameworkComponents.framework */,
129 | 4B8438CC2D4CE44C00BE1DA9 /* libStaticLibraryComponents.a */,
130 | );
131 | name = Products;
132 | sourceTree = "";
133 | };
134 | /* End PBXGroup section */
135 |
136 | /* Begin PBXHeadersBuildPhase section */
137 | 4BA377D12D4CE2F000D4E565 /* Headers */ = {
138 | isa = PBXHeadersBuildPhase;
139 | buildActionMask = 2147483647;
140 | files = (
141 | );
142 | runOnlyForDeploymentPostprocessing = 0;
143 | };
144 | /* End PBXHeadersBuildPhase section */
145 |
146 | /* Begin PBXNativeTarget section */
147 | 4B8438CB2D4CE44C00BE1DA9 /* StaticLibraryComponents */ = {
148 | isa = PBXNativeTarget;
149 | buildConfigurationList = 4B8438D02D4CE44C00BE1DA9 /* Build configuration list for PBXNativeTarget "StaticLibraryComponents" */;
150 | buildPhases = (
151 | 4B8438C82D4CE44C00BE1DA9 /* Sources */,
152 | 4B8438C92D4CE44C00BE1DA9 /* Frameworks */,
153 | 4B8438CA2D4CE44C00BE1DA9 /* CopyFiles */,
154 | );
155 | buildRules = (
156 | );
157 | dependencies = (
158 | );
159 | fileSystemSynchronizedGroups = (
160 | 4B8438CD2D4CE44C00BE1DA9 /* StaticLibraryComponents */,
161 | );
162 | name = StaticLibraryComponents;
163 | packageProductDependencies = (
164 | );
165 | productName = StaticLibraryComponents;
166 | productReference = 4B8438CC2D4CE44C00BE1DA9 /* libStaticLibraryComponents.a */;
167 | productType = "com.apple.product-type.library.static";
168 | };
169 | 4BA377A62D4CE2C500D4E565 /* SwiftUIDemoApp */ = {
170 | isa = PBXNativeTarget;
171 | buildConfigurationList = 4BA377CE2D4CE2C700D4E565 /* Build configuration list for PBXNativeTarget "SwiftUIDemoApp" */;
172 | buildPhases = (
173 | 4BA377A32D4CE2C500D4E565 /* Sources */,
174 | 4BA377A42D4CE2C500D4E565 /* Frameworks */,
175 | 4BA377A52D4CE2C500D4E565 /* Resources */,
176 | 4BA377EF2D4CE2F000D4E565 /* Embed Frameworks */,
177 | );
178 | buildRules = (
179 | );
180 | dependencies = (
181 | 4BA377E82D4CE2F000D4E565 /* PBXTargetDependency */,
182 | 4B8438DC2D4CE6C500BE1DA9 /* PBXTargetDependency */,
183 | );
184 | fileSystemSynchronizedGroups = (
185 | 4BA377A82D4CE2C500D4E565 /* SwiftUIDemoApp */,
186 | );
187 | name = SwiftUIDemoApp;
188 | packageProductDependencies = (
189 | 4BA377F32D4CE31D00D4E565 /* StorybookKit */,
190 | );
191 | productName = SwiftUIDemoApp;
192 | productReference = 4BA377A72D4CE2C500D4E565 /* SwiftUIDemoApp.app */;
193 | productType = "com.apple.product-type.application";
194 | };
195 | 4BA377D52D4CE2F000D4E565 /* DynamicFrameworkComponents */ = {
196 | isa = PBXNativeTarget;
197 | buildConfigurationList = 4BA377EC2D4CE2F000D4E565 /* Build configuration list for PBXNativeTarget "DynamicFrameworkComponents" */;
198 | buildPhases = (
199 | 4BA377D12D4CE2F000D4E565 /* Headers */,
200 | 4BA377D22D4CE2F000D4E565 /* Sources */,
201 | 4BA377D32D4CE2F000D4E565 /* Frameworks */,
202 | 4BA377D42D4CE2F000D4E565 /* Resources */,
203 | );
204 | buildRules = (
205 | );
206 | dependencies = (
207 | );
208 | fileSystemSynchronizedGroups = (
209 | 4BA377D72D4CE2F000D4E565 /* DynamicFrameworkComponents */,
210 | );
211 | name = DynamicFrameworkComponents;
212 | packageProductDependencies = (
213 | );
214 | productName = DynamicFrameworkComponents;
215 | productReference = 4BA377D62D4CE2F000D4E565 /* DynamicFrameworkComponents.framework */;
216 | productType = "com.apple.product-type.framework";
217 | };
218 | /* End PBXNativeTarget section */
219 |
220 | /* Begin PBXProject section */
221 | 957A89282226610200CDD25D /* Project object */ = {
222 | isa = PBXProject;
223 | attributes = {
224 | LastSwiftUpdateCheck = 1620;
225 | LastUpgradeCheck = 1010;
226 | ORGANIZATIONNAME = "eureka, Inc.";
227 | TargetAttributes = {
228 | 4B8438CB2D4CE44C00BE1DA9 = {
229 | CreatedOnToolsVersion = 16.2;
230 | };
231 | 4BA377A62D4CE2C500D4E565 = {
232 | CreatedOnToolsVersion = 16.2;
233 | };
234 | 4BA377D52D4CE2F000D4E565 = {
235 | CreatedOnToolsVersion = 16.2;
236 | LastSwiftMigration = 1620;
237 | };
238 | };
239 | };
240 | buildConfigurationList = 957A892B2226610200CDD25D /* Build configuration list for PBXProject "Storybook" */;
241 | compatibilityVersion = "Xcode 9.3";
242 | developmentRegion = en;
243 | hasScannedForEncodings = 0;
244 | knownRegions = (
245 | en,
246 | Base,
247 | );
248 | mainGroup = 957A89272226610200CDD25D;
249 | packageReferences = (
250 | 4BB655B82A87B0C200F2E2D7 /* XCRemoteSwiftPackageReference "swiftui-support" */,
251 | );
252 | productRefGroup = 957A89322226610200CDD25D /* Products */;
253 | projectDirPath = "";
254 | projectRoot = "";
255 | targets = (
256 | 4BA377A62D4CE2C500D4E565 /* SwiftUIDemoApp */,
257 | 4BA377D52D4CE2F000D4E565 /* DynamicFrameworkComponents */,
258 | 4B8438CB2D4CE44C00BE1DA9 /* StaticLibraryComponents */,
259 | );
260 | };
261 | /* End PBXProject section */
262 |
263 | /* Begin PBXResourcesBuildPhase section */
264 | 4BA377A52D4CE2C500D4E565 /* Resources */ = {
265 | isa = PBXResourcesBuildPhase;
266 | buildActionMask = 2147483647;
267 | files = (
268 | );
269 | runOnlyForDeploymentPostprocessing = 0;
270 | };
271 | 4BA377D42D4CE2F000D4E565 /* Resources */ = {
272 | isa = PBXResourcesBuildPhase;
273 | buildActionMask = 2147483647;
274 | files = (
275 | );
276 | runOnlyForDeploymentPostprocessing = 0;
277 | };
278 | /* End PBXResourcesBuildPhase section */
279 |
280 | /* Begin PBXSourcesBuildPhase section */
281 | 4B8438C82D4CE44C00BE1DA9 /* Sources */ = {
282 | isa = PBXSourcesBuildPhase;
283 | buildActionMask = 2147483647;
284 | files = (
285 | );
286 | runOnlyForDeploymentPostprocessing = 0;
287 | };
288 | 4BA377A32D4CE2C500D4E565 /* Sources */ = {
289 | isa = PBXSourcesBuildPhase;
290 | buildActionMask = 2147483647;
291 | files = (
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | };
295 | 4BA377D22D4CE2F000D4E565 /* Sources */ = {
296 | isa = PBXSourcesBuildPhase;
297 | buildActionMask = 2147483647;
298 | files = (
299 | );
300 | runOnlyForDeploymentPostprocessing = 0;
301 | };
302 | /* End PBXSourcesBuildPhase section */
303 |
304 | /* Begin PBXTargetDependency section */
305 | 4B8438DC2D4CE6C500BE1DA9 /* PBXTargetDependency */ = {
306 | isa = PBXTargetDependency;
307 | target = 4B8438CB2D4CE44C00BE1DA9 /* StaticLibraryComponents */;
308 | targetProxy = 4B8438DB2D4CE6C500BE1DA9 /* PBXContainerItemProxy */;
309 | };
310 | 4BA377E82D4CE2F000D4E565 /* PBXTargetDependency */ = {
311 | isa = PBXTargetDependency;
312 | target = 4BA377D52D4CE2F000D4E565 /* DynamicFrameworkComponents */;
313 | targetProxy = 4BA377E72D4CE2F000D4E565 /* PBXContainerItemProxy */;
314 | };
315 | /* End PBXTargetDependency section */
316 |
317 | /* Begin XCBuildConfiguration section */
318 | 4B8438D12D4CE44C00BE1DA9 /* Debug */ = {
319 | isa = XCBuildConfiguration;
320 | buildSettings = {
321 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
323 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
324 | CODE_SIGN_STYLE = Automatic;
325 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
326 | GCC_C_LANGUAGE_STANDARD = gnu17;
327 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
328 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
329 | OTHER_LDFLAGS = "-ObjC";
330 | PRODUCT_NAME = "$(TARGET_NAME)";
331 | SKIP_INSTALL = YES;
332 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
333 | SWIFT_VERSION = 5.0;
334 | TARGETED_DEVICE_FAMILY = "1,2";
335 | };
336 | name = Debug;
337 | };
338 | 4B8438D22D4CE44C00BE1DA9 /* Release */ = {
339 | isa = XCBuildConfiguration;
340 | buildSettings = {
341 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
343 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
344 | CODE_SIGN_STYLE = Automatic;
345 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
346 | GCC_C_LANGUAGE_STANDARD = gnu17;
347 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
348 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
349 | OTHER_LDFLAGS = "-ObjC";
350 | PRODUCT_NAME = "$(TARGET_NAME)";
351 | SKIP_INSTALL = YES;
352 | SWIFT_VERSION = 5.0;
353 | TARGETED_DEVICE_FAMILY = "1,2";
354 | };
355 | name = Release;
356 | };
357 | 4BA377C82D4CE2C700D4E565 /* Debug */ = {
358 | isa = XCBuildConfiguration;
359 | buildSettings = {
360 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
361 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
362 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
364 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
365 | CODE_SIGN_STYLE = Automatic;
366 | CURRENT_PROJECT_VERSION = 1;
367 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUIDemoApp/Preview Content\"";
368 | ENABLE_PREVIEWS = YES;
369 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
370 | GCC_C_LANGUAGE_STANDARD = gnu17;
371 | GENERATE_INFOPLIST_FILE = YES;
372 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
373 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
374 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
375 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
376 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
377 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
378 | LD_RUNPATH_SEARCH_PATHS = (
379 | "$(inherited)",
380 | "@executable_path/Frameworks",
381 | );
382 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
383 | MARKETING_VERSION = 1.0;
384 | OTHER_LDFLAGS = "-all_load";
385 | PRODUCT_BUNDLE_IDENTIFIER = jp.eure.SwiftUIDemoApp;
386 | PRODUCT_NAME = "$(TARGET_NAME)";
387 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
388 | SWIFT_EMIT_LOC_STRINGS = YES;
389 | SWIFT_VERSION = 5.0;
390 | TARGETED_DEVICE_FAMILY = "1,2";
391 | };
392 | name = Debug;
393 | };
394 | 4BA377C92D4CE2C700D4E565 /* Release */ = {
395 | isa = XCBuildConfiguration;
396 | buildSettings = {
397 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
398 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
399 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
401 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
402 | CODE_SIGN_STYLE = Automatic;
403 | CURRENT_PROJECT_VERSION = 1;
404 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUIDemoApp/Preview Content\"";
405 | ENABLE_PREVIEWS = YES;
406 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
407 | GCC_C_LANGUAGE_STANDARD = gnu17;
408 | GENERATE_INFOPLIST_FILE = YES;
409 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
410 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
411 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
412 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
413 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
414 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
415 | LD_RUNPATH_SEARCH_PATHS = (
416 | "$(inherited)",
417 | "@executable_path/Frameworks",
418 | );
419 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
420 | MARKETING_VERSION = 1.0;
421 | OTHER_LDFLAGS = "-all_load";
422 | PRODUCT_BUNDLE_IDENTIFIER = jp.eure.SwiftUIDemoApp;
423 | PRODUCT_NAME = "$(TARGET_NAME)";
424 | SWIFT_EMIT_LOC_STRINGS = YES;
425 | SWIFT_VERSION = 5.0;
426 | TARGETED_DEVICE_FAMILY = "1,2";
427 | };
428 | name = Release;
429 | };
430 | 4BA377ED2D4CE2F000D4E565 /* Debug */ = {
431 | isa = XCBuildConfiguration;
432 | buildSettings = {
433 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
434 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
435 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
436 | CLANG_ENABLE_MODULES = YES;
437 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
438 | CODE_SIGN_STYLE = Automatic;
439 | CURRENT_PROJECT_VERSION = 1;
440 | DEFINES_MODULE = YES;
441 | DYLIB_COMPATIBILITY_VERSION = 1;
442 | DYLIB_CURRENT_VERSION = 1;
443 | DYLIB_INSTALL_NAME_BASE = "@rpath";
444 | ENABLE_MODULE_VERIFIER = YES;
445 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
446 | GCC_C_LANGUAGE_STANDARD = gnu17;
447 | GENERATE_INFOPLIST_FILE = YES;
448 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 eureka, Inc. All rights reserved.";
449 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
450 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
451 | LD_RUNPATH_SEARCH_PATHS = (
452 | "$(inherited)",
453 | "@executable_path/Frameworks",
454 | "@loader_path/Frameworks",
455 | );
456 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
457 | MARKETING_VERSION = 1.0;
458 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
459 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
460 | PRODUCT_BUNDLE_IDENTIFIER = jp.eure.DynamicFrameworkComponents;
461 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
462 | SKIP_INSTALL = YES;
463 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
464 | SWIFT_EMIT_LOC_STRINGS = YES;
465 | SWIFT_INSTALL_OBJC_HEADER = NO;
466 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
467 | SWIFT_VERSION = 5.0;
468 | TARGETED_DEVICE_FAMILY = "1,2";
469 | };
470 | name = Debug;
471 | };
472 | 4BA377EE2D4CE2F000D4E565 /* Release */ = {
473 | isa = XCBuildConfiguration;
474 | buildSettings = {
475 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
476 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
477 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
478 | CLANG_ENABLE_MODULES = YES;
479 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
480 | CODE_SIGN_STYLE = Automatic;
481 | CURRENT_PROJECT_VERSION = 1;
482 | DEFINES_MODULE = YES;
483 | DYLIB_COMPATIBILITY_VERSION = 1;
484 | DYLIB_CURRENT_VERSION = 1;
485 | DYLIB_INSTALL_NAME_BASE = "@rpath";
486 | ENABLE_MODULE_VERIFIER = YES;
487 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
488 | GCC_C_LANGUAGE_STANDARD = gnu17;
489 | GENERATE_INFOPLIST_FILE = YES;
490 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 eureka, Inc. All rights reserved.";
491 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
492 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
493 | LD_RUNPATH_SEARCH_PATHS = (
494 | "$(inherited)",
495 | "@executable_path/Frameworks",
496 | "@loader_path/Frameworks",
497 | );
498 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
499 | MARKETING_VERSION = 1.0;
500 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
501 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
502 | PRODUCT_BUNDLE_IDENTIFIER = jp.eure.DynamicFrameworkComponents;
503 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
504 | SKIP_INSTALL = YES;
505 | SWIFT_EMIT_LOC_STRINGS = YES;
506 | SWIFT_INSTALL_OBJC_HEADER = NO;
507 | SWIFT_VERSION = 5.0;
508 | TARGETED_DEVICE_FAMILY = "1,2";
509 | };
510 | name = Release;
511 | };
512 | 957A89372226610200CDD25D /* Debug */ = {
513 | isa = XCBuildConfiguration;
514 | buildSettings = {
515 | ALWAYS_SEARCH_USER_PATHS = NO;
516 | CLANG_ANALYZER_NONNULL = YES;
517 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
518 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
519 | CLANG_CXX_LIBRARY = "libc++";
520 | CLANG_ENABLE_MODULES = YES;
521 | CLANG_ENABLE_OBJC_ARC = YES;
522 | CLANG_ENABLE_OBJC_WEAK = YES;
523 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
524 | CLANG_WARN_BOOL_CONVERSION = YES;
525 | CLANG_WARN_COMMA = YES;
526 | CLANG_WARN_CONSTANT_CONVERSION = YES;
527 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
528 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
529 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
530 | CLANG_WARN_EMPTY_BODY = YES;
531 | CLANG_WARN_ENUM_CONVERSION = YES;
532 | CLANG_WARN_INFINITE_RECURSION = YES;
533 | CLANG_WARN_INT_CONVERSION = YES;
534 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
535 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
536 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
537 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
538 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
539 | CLANG_WARN_STRICT_PROTOTYPES = YES;
540 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
541 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
542 | CLANG_WARN_UNREACHABLE_CODE = YES;
543 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
544 | CODE_SIGN_IDENTITY = "iPhone Developer";
545 | COPY_PHASE_STRIP = NO;
546 | CURRENT_PROJECT_VERSION = 1;
547 | DEBUG_INFORMATION_FORMAT = dwarf;
548 | ENABLE_STRICT_OBJC_MSGSEND = YES;
549 | ENABLE_TESTABILITY = YES;
550 | GCC_C_LANGUAGE_STANDARD = gnu11;
551 | GCC_DYNAMIC_NO_PIC = NO;
552 | GCC_NO_COMMON_BLOCKS = YES;
553 | GCC_OPTIMIZATION_LEVEL = 0;
554 | GCC_PREPROCESSOR_DEFINITIONS = (
555 | "DEBUG=1",
556 | "$(inherited)",
557 | );
558 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
559 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
560 | GCC_WARN_UNDECLARED_SELECTOR = YES;
561 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
562 | GCC_WARN_UNUSED_FUNCTION = YES;
563 | GCC_WARN_UNUSED_VARIABLE = YES;
564 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
565 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
566 | MTL_FAST_MATH = YES;
567 | ONLY_ACTIVE_ARCH = YES;
568 | SDKROOT = iphoneos;
569 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
570 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
571 | SWIFT_STRICT_CONCURRENCY = complete;
572 | VERSIONING_SYSTEM = "apple-generic";
573 | VERSION_INFO_PREFIX = "";
574 | };
575 | name = Debug;
576 | };
577 | 957A89382226610200CDD25D /* Release */ = {
578 | isa = XCBuildConfiguration;
579 | buildSettings = {
580 | ALWAYS_SEARCH_USER_PATHS = NO;
581 | CLANG_ANALYZER_NONNULL = YES;
582 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
583 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
584 | CLANG_CXX_LIBRARY = "libc++";
585 | CLANG_ENABLE_MODULES = YES;
586 | CLANG_ENABLE_OBJC_ARC = YES;
587 | CLANG_ENABLE_OBJC_WEAK = YES;
588 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
589 | CLANG_WARN_BOOL_CONVERSION = YES;
590 | CLANG_WARN_COMMA = YES;
591 | CLANG_WARN_CONSTANT_CONVERSION = YES;
592 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
593 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
594 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
595 | CLANG_WARN_EMPTY_BODY = YES;
596 | CLANG_WARN_ENUM_CONVERSION = YES;
597 | CLANG_WARN_INFINITE_RECURSION = YES;
598 | CLANG_WARN_INT_CONVERSION = YES;
599 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
600 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
601 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
602 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
603 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
604 | CLANG_WARN_STRICT_PROTOTYPES = YES;
605 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
606 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
607 | CLANG_WARN_UNREACHABLE_CODE = YES;
608 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
609 | CODE_SIGN_IDENTITY = "iPhone Developer";
610 | COPY_PHASE_STRIP = NO;
611 | CURRENT_PROJECT_VERSION = 1;
612 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
613 | ENABLE_NS_ASSERTIONS = NO;
614 | ENABLE_STRICT_OBJC_MSGSEND = YES;
615 | GCC_C_LANGUAGE_STANDARD = gnu11;
616 | GCC_NO_COMMON_BLOCKS = YES;
617 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
618 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
619 | GCC_WARN_UNDECLARED_SELECTOR = YES;
620 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
621 | GCC_WARN_UNUSED_FUNCTION = YES;
622 | GCC_WARN_UNUSED_VARIABLE = YES;
623 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
624 | MTL_ENABLE_DEBUG_INFO = NO;
625 | MTL_FAST_MATH = YES;
626 | SDKROOT = iphoneos;
627 | SWIFT_COMPILATION_MODE = wholemodule;
628 | SWIFT_OPTIMIZATION_LEVEL = "-O";
629 | SWIFT_STRICT_CONCURRENCY = complete;
630 | VALIDATE_PRODUCT = YES;
631 | VERSIONING_SYSTEM = "apple-generic";
632 | VERSION_INFO_PREFIX = "";
633 | };
634 | name = Release;
635 | };
636 | /* End XCBuildConfiguration section */
637 |
638 | /* Begin XCConfigurationList section */
639 | 4B8438D02D4CE44C00BE1DA9 /* Build configuration list for PBXNativeTarget "StaticLibraryComponents" */ = {
640 | isa = XCConfigurationList;
641 | buildConfigurations = (
642 | 4B8438D12D4CE44C00BE1DA9 /* Debug */,
643 | 4B8438D22D4CE44C00BE1DA9 /* Release */,
644 | );
645 | defaultConfigurationIsVisible = 0;
646 | defaultConfigurationName = Release;
647 | };
648 | 4BA377CE2D4CE2C700D4E565 /* Build configuration list for PBXNativeTarget "SwiftUIDemoApp" */ = {
649 | isa = XCConfigurationList;
650 | buildConfigurations = (
651 | 4BA377C82D4CE2C700D4E565 /* Debug */,
652 | 4BA377C92D4CE2C700D4E565 /* Release */,
653 | );
654 | defaultConfigurationIsVisible = 0;
655 | defaultConfigurationName = Release;
656 | };
657 | 4BA377EC2D4CE2F000D4E565 /* Build configuration list for PBXNativeTarget "DynamicFrameworkComponents" */ = {
658 | isa = XCConfigurationList;
659 | buildConfigurations = (
660 | 4BA377ED2D4CE2F000D4E565 /* Debug */,
661 | 4BA377EE2D4CE2F000D4E565 /* Release */,
662 | );
663 | defaultConfigurationIsVisible = 0;
664 | defaultConfigurationName = Release;
665 | };
666 | 957A892B2226610200CDD25D /* Build configuration list for PBXProject "Storybook" */ = {
667 | isa = XCConfigurationList;
668 | buildConfigurations = (
669 | 957A89372226610200CDD25D /* Debug */,
670 | 957A89382226610200CDD25D /* Release */,
671 | );
672 | defaultConfigurationIsVisible = 0;
673 | defaultConfigurationName = Release;
674 | };
675 | /* End XCConfigurationList section */
676 |
677 | /* Begin XCRemoteSwiftPackageReference section */
678 | 4BB655B82A87B0C200F2E2D7 /* XCRemoteSwiftPackageReference "swiftui-support" */ = {
679 | isa = XCRemoteSwiftPackageReference;
680 | repositoryURL = "https://github.com/FluidGroup/swiftui-support/";
681 | requirement = {
682 | kind = upToNextMajorVersion;
683 | minimumVersion = 0.4.1;
684 | };
685 | };
686 | /* End XCRemoteSwiftPackageReference section */
687 |
688 | /* Begin XCSwiftPackageProductDependency section */
689 | 4BA377F32D4CE31D00D4E565 /* StorybookKit */ = {
690 | isa = XCSwiftPackageProductDependency;
691 | productName = StorybookKit;
692 | };
693 | /* End XCSwiftPackageProductDependency section */
694 | };
695 | rootObject = 957A89282226610200CDD25D /* Project object */;
696 | }
697 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "3a6dfeefaa06e81f068454f6d3b99782d6b5b64dffd02869d95d3ca2528f5116",
3 | "pins" : [
4 | {
5 | "identity" : "descriptors",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/FluidGroup/Descriptors",
8 | "state" : {
9 | "revision" : "f41ce2605a76c5d378fe8c5e8c5c98b544dfd108",
10 | "version" : "0.2.3"
11 | }
12 | },
13 | {
14 | "identity" : "resultbuilderkit",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/FluidGroup/ResultBuilderKit",
17 | "state" : {
18 | "revision" : "34aa57fcee5ec3ee0f368b05cb53c7919c188ab2",
19 | "version" : "1.3.0"
20 | }
21 | },
22 | {
23 | "identity" : "swift-custom-dump",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/pointfreeco/swift-custom-dump",
26 | "state" : {
27 | "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1",
28 | "version" : "1.3.3"
29 | }
30 | },
31 | {
32 | "identity" : "swift-macro-testing",
33 | "kind" : "remoteSourceControl",
34 | "location" : "https://github.com/pointfreeco/swift-macro-testing.git",
35 | "state" : {
36 | "revision" : "20c1a8f3b624fb5d1503eadcaa84743050c350f4",
37 | "version" : "0.5.2"
38 | }
39 | },
40 | {
41 | "identity" : "swift-snapshot-testing",
42 | "kind" : "remoteSourceControl",
43 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing",
44 | "state" : {
45 | "revision" : "f5bfff796ee8e3bc9a685b7ffba1bf20663eb370",
46 | "version" : "1.18.0"
47 | }
48 | },
49 | {
50 | "identity" : "swift-syntax",
51 | "kind" : "remoteSourceControl",
52 | "location" : "https://github.com/apple/swift-syntax.git",
53 | "state" : {
54 | "revision" : "0687f71944021d616d34d922343dcef086855920",
55 | "version" : "600.0.1"
56 | }
57 | },
58 | {
59 | "identity" : "swiftui-gesture-velocity",
60 | "kind" : "remoteSourceControl",
61 | "location" : "https://github.com/FluidGroup/swiftui-gesture-velocity",
62 | "state" : {
63 | "revision" : "9c83f8995f9e5efc29db2fca4b9ff058283f1603",
64 | "version" : "1.0.0"
65 | }
66 | },
67 | {
68 | "identity" : "swiftui-support",
69 | "kind" : "remoteSourceControl",
70 | "location" : "https://github.com/FluidGroup/swiftui-support",
71 | "state" : {
72 | "revision" : "8ef53190c33bd345e7a95ef504dafe0f85ad9c4d",
73 | "version" : "0.4.1"
74 | }
75 | },
76 | {
77 | "identity" : "texture",
78 | "kind" : "remoteSourceControl",
79 | "location" : "https://github.com/FluidGroup/Texture.git",
80 | "state" : {
81 | "revision" : "68df47f0d26522da76b06f22e9a97e4d4ab58dad",
82 | "version" : "3.0.2"
83 | }
84 | },
85 | {
86 | "identity" : "texturebridging",
87 | "kind" : "remoteSourceControl",
88 | "location" : "https://github.com/FluidGroup/TextureBridging.git",
89 | "state" : {
90 | "revision" : "4383f8a9846a0507d2c22ee9fac22153f9b86fed",
91 | "version" : "3.2.1"
92 | }
93 | },
94 | {
95 | "identity" : "textureswiftsupport",
96 | "kind" : "remoteSourceControl",
97 | "location" : "https://github.com/FluidGroup/TextureSwiftSupport.git",
98 | "state" : {
99 | "revision" : "33a0adcb38b92debf6b57eda98df06b07055dd73",
100 | "version" : "3.23.0"
101 | }
102 | },
103 | {
104 | "identity" : "xctest-dynamic-overlay",
105 | "kind" : "remoteSourceControl",
106 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
107 | "state" : {
108 | "revision" : "b444594f79844b0d6d76d70fbfb3f7f71728f938",
109 | "version" : "1.5.1"
110 | }
111 | }
112 | ],
113 | "version" : 3
114 | }
115 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // Copyright (c) 2020 Eureka, Inc.
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
16 |
17 |
19 |
21 |
22 |
23 |
24 |
25 |
33 |
34 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/xcshareddata/xcschemes/Storybook-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 |
57 |
58 |
64 |
65 |
67 |
68 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Development/Storybook.xcodeproj/xcshareddata/xcschemes/SwiftUIDemoApp.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Development/SwiftUIDemoApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Development/SwiftUIDemoApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "idiom" : "universal",
16 | "platform" : "ios",
17 | "size" : "1024x1024"
18 | },
19 | {
20 | "appearances" : [
21 | {
22 | "appearance" : "luminosity",
23 | "value" : "tinted"
24 | }
25 | ],
26 | "idiom" : "universal",
27 | "platform" : "ios",
28 | "size" : "1024x1024"
29 | }
30 | ],
31 | "info" : {
32 | "author" : "xcode",
33 | "version" : 1
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Development/SwiftUIDemoApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Development/SwiftUIDemoApp/Component.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | #Preview("Circle") {
25 | Circle()
26 | .fill(.purple)
27 | .frame(width: 100, height: 100)
28 | }
29 |
30 | #Preview("Circle2") {
31 | Circle()
32 | .fill(.purple)
33 | .frame(width: 100, height: 100)
34 | }
35 |
36 | #Preview {
37 | Circle()
38 | .fill(.purple)
39 | .frame(width: 100, height: 100)
40 | }
41 |
--------------------------------------------------------------------------------
/Development/SwiftUIDemoApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import StorybookKit
23 | import SwiftUI
24 |
25 | struct ContentView: View {
26 | var body: some View {
27 | Storybook()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Development/SwiftUIDemoApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Development/SwiftUIDemoApp/SwiftUIDemoAppApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | @main
25 | struct SwiftUIDemoAppApp: App {
26 | var body: some Scene {
27 | WindowGroup {
28 | ContentView()
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Eureka, inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | trunk:
3 | pod trunk push --allow-warnings StorybookKit.podspec
4 | pod trunk push --allow-warnings StorybookUI.podspec
5 | pod trunk push --allow-warnings StorybookKitTextureSupport.podspec
6 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "4eb04f1e4297cdd13b29cb7f5cf1d72921892414696f00aceda4577636790300",
3 | "pins" : [
4 | {
5 | "identity" : "descriptors",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/FluidGroup/Descriptors",
8 | "state" : {
9 | "revision" : "f41ce2605a76c5d378fe8c5e8c5c98b544dfd108",
10 | "version" : "0.2.3"
11 | }
12 | },
13 | {
14 | "identity" : "resultbuilderkit",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/FluidGroup/ResultBuilderKit",
17 | "state" : {
18 | "revision" : "34aa57fcee5ec3ee0f368b05cb53c7919c188ab2",
19 | "version" : "1.3.0"
20 | }
21 | },
22 | {
23 | "identity" : "swift-macro-testing",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/pointfreeco/swift-macro-testing.git",
26 | "state" : {
27 | "revision" : "20c1a8f3b624fb5d1503eadcaa84743050c350f4",
28 | "version" : "0.5.2"
29 | }
30 | },
31 | {
32 | "identity" : "swift-snapshot-testing",
33 | "kind" : "remoteSourceControl",
34 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing",
35 | "state" : {
36 | "revision" : "7b0bbbae90c41f848f90ac7b4df6c4f50068256d",
37 | "version" : "1.17.5"
38 | }
39 | },
40 | {
41 | "identity" : "swift-syntax",
42 | "kind" : "remoteSourceControl",
43 | "location" : "https://github.com/apple/swift-syntax.git",
44 | "state" : {
45 | "revision" : "0687f71944021d616d34d922343dcef086855920",
46 | "version" : "600.0.1"
47 | }
48 | },
49 | {
50 | "identity" : "swiftui-support",
51 | "kind" : "remoteSourceControl",
52 | "location" : "https://github.com/FluidGroup/swiftui-support",
53 | "state" : {
54 | "revision" : "266e052494f8b7432374ad19a4bd31877cf54640",
55 | "version" : "0.10.0"
56 | }
57 | },
58 | {
59 | "identity" : "texture",
60 | "kind" : "remoteSourceControl",
61 | "location" : "https://github.com/FluidGroup/Texture.git",
62 | "state" : {
63 | "revision" : "68df47f0d26522da76b06f22e9a97e4d4ab58dad",
64 | "version" : "3.0.2"
65 | }
66 | },
67 | {
68 | "identity" : "texturebridging",
69 | "kind" : "remoteSourceControl",
70 | "location" : "https://github.com/FluidGroup/TextureBridging.git",
71 | "state" : {
72 | "revision" : "4383f8a9846a0507d2c22ee9fac22153f9b86fed",
73 | "version" : "3.2.1"
74 | }
75 | },
76 | {
77 | "identity" : "textureswiftsupport",
78 | "kind" : "remoteSourceControl",
79 | "location" : "https://github.com/FluidGroup/TextureSwiftSupport.git",
80 | "state" : {
81 | "revision" : "fb748d6a9d0a2dca0635227e1db0360fd26e0e24",
82 | "version" : "3.20.1"
83 | }
84 | }
85 | ],
86 | "version" : 3
87 | }
88 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:6.0
2 | import CompilerPluginSupport
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Storybook",
7 | platforms: [
8 | .iOS(.v16),
9 | .macCatalyst(.v15),
10 | .macOS(.v10_15),
11 | ],
12 | products: [
13 | .library(name: "StorybookKit", targets: ["StorybookKit"]),
14 | .library(name: "StorybookKitTextureSupport", targets: ["StorybookKitTextureSupport"]),
15 | ],
16 | dependencies: [
17 | .package(url: "https://github.com/FluidGroup/TextureBridging.git", from: "3.2.1"),
18 | .package(url: "https://github.com/FluidGroup/TextureSwiftSupport.git", from: "3.20.1"),
19 | .package(url: "https://github.com/FluidGroup/swiftui-support", from: "0.4.1"),
20 | .package(url: "https://github.com/FluidGroup/ResultBuilderKit", from: "1.3.0"),
21 | .package(url: "https://github.com/apple/swift-syntax.git", from: "600.0.1"),
22 | .package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.5.2"),
23 | ],
24 | targets: [
25 | .target(
26 | name: "StorybookKit",
27 | dependencies: [
28 | "StorybookMacrosPlugin",
29 | .product(name: "SwiftUISupport", package: "swiftui-support"),
30 | "ResultBuilderKit",
31 | ]
32 | ),
33 | .macro(
34 | name: "StorybookMacrosPlugin",
35 | dependencies: [
36 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
37 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
38 | ]
39 | ),
40 | .testTarget(
41 | name: "StorybookMacrosTests",
42 | dependencies: [
43 | "StorybookMacrosPlugin",
44 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
45 | .product(name: "MacroTesting", package: "swift-macro-testing"),
46 | ]
47 | ),
48 | .target(
49 | name: "StorybookKitTextureSupport",
50 | dependencies: [
51 | .product(name: "TextureSwiftSupport", package: "TextureSwiftSupport"),
52 | .product(name: "TextureBridging", package: "TextureBridging"),
53 | "StorybookKit",
54 | ]
55 | ),
56 | ],
57 | swiftLanguageModes: [.v6, .v5]
58 | )
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Storybook for iOS
2 |
3 | This library allows you to view UI components in a catalog-style format.
4 | In most cases, it works by simply adding a few lines of code, as it gathers SwiftUI preview codes at runtime.
5 |
6 |
7 |
8 | ## Setup
9 |
10 | 1. Install this package into your project.
11 |
12 | 2. Put the entrypoint view.
13 |
14 | ```swift
15 | import StorybookKit
16 | import SwiftUI
17 |
18 | struct ContentView: View {
19 | var body: some View {
20 | Storybook()
21 | }
22 | }
23 | ```
24 |
25 | ## Example
26 |
27 | In app executable module
28 |
29 | ```swift
30 | #Preview("Circle") {
31 | Circle()
32 | .fill(.purple)
33 | .frame(width: 100, height: 100)
34 | }
35 | ```
36 |
37 | In a dynamic framework module
38 | ```swift
39 | #Preview("Circle") {
40 | Circle()
41 | .fill(.purple)
42 | .frame(width: 100, height: 100)
43 | }
44 | ```
45 |
46 | In a static library module
47 | ```swift
48 | #Preview("Circle") {
49 | Circle()
50 | .fill(.purple)
51 | .frame(width: 100, height: 100)
52 | }
53 | ```
54 |
55 | > [!IMPORTANT]
56 | > To display all preview codes in a statically linked binary, you may need to link the binary with the -all_load linker flag.
57 | > This is because the linker does not load symbols into the target binary if it deems them unnecessary.
58 |
59 |
60 |
61 | ## License
62 |
63 | Storybook-ios is released under the MIT license.
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/BookGenerator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import UIKit
23 | import Foundation
24 |
25 | public enum BookGenerator {
26 |
27 | public static func randomColor() -> UIColor {
28 | let red = CGFloat(Int.random(in: 0 ... 255))
29 | let green = CGFloat(Int.random(in: 0 ... 255))
30 | let blue = CGFloat(Int.random(in: 0 ... 255))
31 | return UIColor.init(red: red / 255, green: green / 255, blue: blue / 255, alpha: 1.0)
32 | }
33 |
34 | public static func loremIpsum(length: Int) -> String {
35 | BookLorem.ipsum(length)
36 | }
37 |
38 | public static func randomEmoji() -> String {
39 | let range = 0x1F601...0x1F64F
40 | let ascii = range.lowerBound + Int(arc4random_uniform(UInt32(range.count)))
41 |
42 | var view = String.UnicodeScalarView()
43 | view.append(UnicodeScalar(ascii)!)
44 |
45 | let emoji = String(view)
46 |
47 | return emoji
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/BookLorem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import Foundation
23 |
24 | // MARK: - Lorem
25 | public enum BookLorem {
26 | // MARK: Public
27 |
28 | public static func ipsum(_ length: Int) -> String {
29 |
30 | var string = lorem
31 | while string.count < length {
32 |
33 | string += ("\n" + lorem)
34 | }
35 | return String(string.prefix(length))
36 | }
37 |
38 | // MARK: Private
39 | private static let lorem: String = """
40 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sodales tristique elit et fringilla. Etiam eget justo non libero rhoncus placerat. Ut augue enim, gravida vitae consectetur ac, porta sit amet justo. Proin sed tristique dui. Vivamus at justo iaculis, dapibus tellus at, vestibulum arcu. Mauris et augue dictum ex rhoncus ultrices. Morbi ac diam eget leo consequat sodales. Phasellus sed luctus sem. Vestibulum sit amet dapibus nulla. Vivamus facilisis non dolor sed ornare.
41 |
42 | Phasellus malesuada diam ac magna feugiat, et malesuada ligula euismod. Donec at commodo ipsum. Etiam nisl lorem, commodo sed condimentum non, mattis accumsan tellus. Donec mollis, quam nec euismod commodo, mi mi dapibus ipsum, sit amet molestie leo mi non erat. Curabitur nec porttitor libero. Sed a rutrum sem. Quisque non tellus aliquet, bibendum elit nec, porta ante. Aenean nibh sapien, euismod id dapibus eu, auctor a nulla. Quisque ultricies sollicitudin dolor, sit amet lobortis justo pellentesque nec. Nulla facilisi. Duis rutrum orci pellentesque felis ultricies venenatis. Donec ullamcorper dui nisl, nec auctor lacus consectetur sit amet. Ut libero ipsum, por ttitor non pellentesque volutpat, accumsan at erat. Quisque at tempor libero. Ut aliquet sem at sem dictum commodo.
43 |
44 | Curabitur commodo varius finibus. Vivamus vestibulum sodales orci, ut consequat augue vestibulum quis. Nunc tempus vulputate dapibus. In euismod, risus at congue rhoncus, magna diam ornare enim, eget semper nunc ipsum vel nisl. Nulla tincidunt, lorem sit amet imperdiet aliquam, magna arcu gravida quam, eu malesuada purus nulla quis odio. Maecenas quis eros tincidunt, tempor leo volutpat, commodo ligula. Vivamus vulputate massa at tellus hendrerit, non congue lacus ullamcorper. Aenean sit amet sem nec nunc molestie mattis. Nam facilisis rutrum mi in mattis. In nec efficitur turpis, sed hendrerit ipsum. Nunc et fermentum tellus. Sed aliquet ligula at elit dictum, eget eleifend leo auctor. Morbi tempus finibus est vel rhoncus. Integer feugiat porttitor justo. Aliquam at nisi quis lorem aliquet faucibus.
45 |
46 | Fusce sodales facilisis felis sed pretium. Nullam arcu nunc, malesuada sit amet mollis eu, semper eu quam. Suspendisse est tellus, tincidunt nec purus vel, ultricies condimentum nulla. Praesent purus arcu, maximus at commodo id, volutpat id erat. Mauris nec erat nec erat vestibulum ultrices sed in dolor. Aliquam lacus ante, feugiat sit amet sapien id, semper pellentesque lacus. Morbi justo massa, molestie nec nulla a, hendrerit vehicula odio. Etiam sollicitudin est quis urna convallis, non maximus quam pharetra.
47 |
48 | Donec at leo a lectus dapibus commodo eu id dui. Cras tristique mauris sed pharetra congue. Duis nisl magna, mollis eget volutpat et, dignissim at neque. Sed in enim eu nisi fermentum hendrerit. Pellentesque sollicitudin, nibh eu auctor iaculis, quam mi pulvinar metus, eget posuere elit dolor ac dolor. Quisque ultricies consequat magna eu semper. Nulla placerat volutpat augue, ut tempus mi tincidunt id. Nunc in sollicitudin orci. Sed at justo at metus luctus scelerisque at sed arcu. Maecenas pulvinar dui a consequat rutrum. Etiam interdum ligula felis. In eu auctor magna, finibus aliquam massa. Integer finibus quam tellus, quis porta metus vehicula vitae. Aliquam id odio interdum, consectetur augue vitae, interdum dolor.
49 | """
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Compositions/BookPadding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | public struct BookSpacer: BookView {
25 |
26 | public let height: CGFloat
27 |
28 | public init(height: CGFloat) {
29 | self.height = height
30 | }
31 |
32 | public var bookBody: any BookView {
33 | fatalError()
34 | }
35 |
36 | public var body: some View {
37 | Spacer(minLength: 0)
38 | .frame(height: height)
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Compositions/BookPattern.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import Foundation
23 | import SwiftUI
24 | import SwiftUISupport
25 |
26 | public struct BookPattern {
27 |
28 | public let patterns: [Element]
29 |
30 | init(_ source: [Element]) {
31 | self.patterns = source
32 | }
33 |
34 | public func makeBody(@ViewBuilder _ content: @escaping (Element) -> Content) -> some View {
35 | ForEach.inefficient(items: patterns) { pattern in
36 | content(pattern)
37 | }
38 | }
39 | }
40 |
41 | extension BookPattern where Element == Any {
42 |
43 | public static func make(_ p0: P0) -> BookPattern<(P0.Element)> {
44 | .init(p0.map { $0 })
45 | }
46 |
47 | public static func make(_ p0: P0, _ p1: P1) -> BookPattern<(P0.Element, P1.Element)> {
48 |
49 | var buffer: [(P0.Element, P1.Element)] = []
50 |
51 | p0.forEach { p0 in
52 | p1.forEach { p1 in
53 | buffer.append((p0, p1))
54 | }
55 | }
56 |
57 | return .init(buffer)
58 |
59 | }
60 |
61 | public static func make(_ p0: P0, _ p1: P1, _ p2: P2) -> BookPattern<(P0.Element, P1.Element, P2.Element)> {
62 |
63 | var buffer: [(P0.Element, P1.Element, P2.Element)] = []
64 |
65 | p0.forEach { p0 in
66 | p1.forEach { p1 in
67 | p2.forEach { p2 in
68 | buffer.append((p0, p1, p2))
69 | }
70 | }
71 | }
72 |
73 | return .init(buffer)
74 |
75 | }
76 |
77 | public static func make(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) -> BookPattern<(P0.Element, P1.Element, P2.Element, P3.Element)> {
78 |
79 | var buffer: [(P0.Element, P1.Element, P2.Element, P3.Element)] = []
80 |
81 | p0.forEach { p0 in
82 | p1.forEach { p1 in
83 | p2.forEach { p2 in
84 | p3.forEach { p3 in
85 | buffer.append((p0, p1, p2, p3))
86 | }
87 | }
88 | }
89 | }
90 |
91 | return .init(buffer)
92 |
93 | }
94 |
95 | public static func make(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) -> BookPattern<(P0.Element, P1.Element, P2.Element, P3.Element, P4.Element)> {
96 |
97 | var buffer: [(P0.Element, P1.Element, P2.Element, P3.Element, P4.Element)] = []
98 |
99 | p0.forEach { p0 in
100 | p1.forEach { p1 in
101 | p2.forEach { p2 in
102 | p3.forEach { p3 in
103 | p4.forEach { p4 in
104 | buffer.append((p0, p1, p2, p3, p4))
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
111 | return .init(buffer)
112 |
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Compositions/BookSection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | public struct BookSection: BookView {
25 |
26 | public let title: String
27 | public let content: Content
28 |
29 | public init(
30 | title: String,
31 | @ViewBuilder content: () -> Content
32 | ) {
33 | self.title = title
34 | self.content = content()
35 | }
36 |
37 | public var body: some View {
38 | BookText(title)
39 | .font(.system(size: 24, weight: .bold))
40 | content
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Internals/Hosting/_ViewControllerHost.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct _ViewControllerHost:
4 | UIViewControllerRepresentable
5 | {
6 |
7 | private let instantiate: @MainActor () -> ContentViewController
8 | private let _update:
9 | @MainActor (_ uiViewController: ContentViewController, _ context: Context) -> Void
10 |
11 | init(
12 | instantiate: @escaping @MainActor () -> ContentViewController,
13 | update: @escaping @MainActor (_ uiViewController: ContentViewController, _ context: Context) ->
14 | Void = { _, _ in }
15 | ) {
16 | self.instantiate = instantiate
17 | self._update = update
18 | }
19 |
20 | func makeUIViewController(context: Context) -> ContentViewController {
21 | let instantiated = instantiate()
22 | return instantiated
23 | }
24 |
25 | func updateUIViewController(_ uiViewController: ContentViewController, context: Context) {
26 | _update(uiViewController, context)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Internals/Hosting/_ViewHost.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct _ViewHost:
4 | UIViewRepresentable
5 | {
6 |
7 | private let instantiate: @MainActor () -> ContentView
8 | private let _update: @MainActor (_ uiView: ContentView, _ context: Context) -> Void
9 | private let maxWidth: Double
10 |
11 | init(
12 | maxWidth: Double,
13 | instantiate: @escaping @MainActor () -> ContentView,
14 | update: @escaping @MainActor (_ uiViewController: ContentView, _ context: Context) ->
15 | Void = { _, _ in }
16 | ) {
17 | self.maxWidth = maxWidth
18 | self.instantiate = instantiate
19 | self._update = update
20 | }
21 |
22 | func makeUIView(context: Context) -> _Label {
23 | let instantiated = instantiate()
24 | return .init(maxWidth: maxWidth, contentView: instantiated)
25 | }
26 |
27 | func updateUIView(_ uiView: _Label, context: Context) {
28 | _update(uiView.contentView, context)
29 | }
30 |
31 | }
32 |
33 | final class _Label: UILabel {
34 |
35 | let contentView: ContentView
36 | let maxWidth: Double
37 |
38 | init(
39 | maxWidth: Double,
40 | contentView: ContentView
41 | ) {
42 | self.maxWidth = maxWidth
43 | self.contentView = contentView
44 | super.init(frame: .null)
45 | addSubview(contentView)
46 | numberOfLines = 0
47 | isUserInteractionEnabled = true
48 | }
49 |
50 | required init?(coder: NSCoder) {
51 | fatalError("init(coder:) has not been implemented")
52 | }
53 |
54 | override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int)
55 | -> CGRect
56 | {
57 |
58 | var targetSize = bounds.size
59 |
60 | if targetSize.width == CGFloat(Float.greatestFiniteMagnitude) {
61 | targetSize.width = UIView.layoutFittingCompressedSize.width
62 | }
63 |
64 | if targetSize.height == CGFloat(Float.greatestFiniteMagnitude) {
65 | targetSize.height = UIView.layoutFittingCompressedSize.height
66 | }
67 |
68 | var size = contentView.systemLayoutSizeFitting(targetSize)
69 |
70 | if size.width > maxWidth {
71 | targetSize.width = maxWidth
72 | size = contentView.systemLayoutSizeFitting(
73 | targetSize,
74 | withHorizontalFittingPriority: .required,
75 | verticalFittingPriority: .fittingSizeLevel
76 | )
77 | }
78 |
79 | return .init(origin: .zero, size: size)
80 | }
81 |
82 | override func layoutSubviews() {
83 | super.layoutSubviews()
84 |
85 | contentView.frame = bounds
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Internals/Preview/PreviewRegistryWrapper.swift:
--------------------------------------------------------------------------------
1 | import DeveloperToolsSupport
2 | import Foundation
3 | import SwiftUI
4 | import UIKit
5 |
6 | @available(iOS 17.0, *)
7 | struct PreviewRegistryWrapper: Comparable {
8 |
9 | let previewType: any DeveloperToolsSupport.PreviewRegistry.Type
10 | let module: String
11 |
12 | init(_ previewType: any DeveloperToolsSupport.PreviewRegistry.Type) {
13 | self.previewType = previewType
14 | self.module = previewType.fileID.components(separatedBy: "/").first!
15 | }
16 |
17 | var fileID: String { previewType.fileID }
18 | var line: Int { previewType.line }
19 | var column: Int { previewType.column }
20 |
21 | @MainActor
22 | var displayName: String? {
23 | guard let rawPreview = try? previewType.makePreview() else {
24 | return nil
25 | }
26 | let preview: FieldReader = .init(rawPreview)
27 | return preview["displayName"]
28 | }
29 |
30 | @MainActor
31 | var makeView: (@MainActor () -> any View) {
32 | guard let rawPreview = try? previewType.makePreview() else {
33 | return { EmptyView() }
34 | }
35 | let preview: FieldReader = .init(rawPreview)
36 | let title: String? = preview["displayName"]
37 | let source: FieldReader = preview["source"]
38 | switch source.typeName {
39 |
40 | case "SwiftUI.ViewPreviewSource": // iOS 17
41 | let makeView: MakeFunctionWrapper = .init(source["makeView"])
42 | return {
43 | VStack {
44 | if let title, !title.isEmpty {
45 | Text(title)
46 | .font(.system(size: 17, weight: .semibold))
47 | }
48 | AnyView(makeView())
49 | Text("\(fileID):\(line)")
50 | .font(.caption.monospacedDigit())
51 | BookSpacer(height: 16)
52 | }
53 | }
54 |
55 | case "UIKit.UIViewPreviewSource": // iOS 17
56 | // Unsupported due to iOS 17 not supporting casting between non-sendable closure types
57 | return {
58 | VStack {
59 | if let title, !title.isEmpty {
60 | Text(title)
61 | .font(.system(size: 17, weight: .semibold))
62 | }
63 | Text("UIView Preview not supported on iOS 17")
64 | .foregroundStyle(Color.red)
65 | .font(.caption.monospacedDigit())
66 | Text("\(fileID):\(line)")
67 | .font(.caption.monospacedDigit())
68 | BookSpacer(height: 16)
69 | }
70 | }
71 |
72 | case "UIKit.UIViewControllerPreviewSource": // iOS 17
73 | // Unsupported due to iOS 17 not supporting casting between non-sendable closure types
74 | return {
75 | VStack {
76 | if let title, !title.isEmpty {
77 | Text(title)
78 | .font(.system(size: 17, weight: .semibold))
79 | }
80 | Text("UIViewController Preview not supported on iOS 17")
81 | .foregroundStyle(Color.red)
82 | .font(.caption.monospacedDigit())
83 | Text("\(fileID):\(line)")
84 | .font(.caption.monospacedDigit())
85 | BookSpacer(height: 16)
86 | }
87 | }
88 |
89 | case "DeveloperToolsSupport.DefaultPreviewSource": // iOS 18
90 | let makeBody: MakeFunctionWrapper = .init(source["structure", "singlePreview", "makeBody"])
91 | return {
92 | VStack {
93 | AnyView(makeBody())
94 | .toolbar {
95 | ToolbarItem(placement: .topBarTrailing) {
96 | Menu {
97 | Button {
98 | UIPasteboard.general.string = "\(fileID):\(line)"
99 | } label: {
100 | Text("\(fileID):\(line)")
101 | .font(.caption.monospacedDigit())
102 | }
103 | } label: {
104 | Image(systemName: "info.circle")
105 | }
106 |
107 | }
108 | }
109 | }
110 | }
111 |
112 | case "DeveloperToolsSupport.DefaultPreviewSource<__C.UIView>": // iOS 18
113 | let makeBody: MakeFunctionWrapper = .init(source["structure", "singlePreview", "makeBody"])
114 | return {
115 | BookPreview(
116 | fileID,
117 | line,
118 | title: title ?? source.typeName,
119 | viewBlock: { _ in
120 | makeBody()
121 | }
122 | )
123 | }
124 |
125 | case "DeveloperToolsSupport.DefaultPreviewSource<__C.UIViewController>": // iOS 18
126 | let makeBody: MakeFunctionWrapper = .init(source["structure", "singlePreview", "makeBody"])
127 | return {
128 | BookPresent(
129 | title: title ?? source.typeName,
130 | presentingViewControllerBlock: {
131 | makeBody()
132 | }
133 | )
134 | }
135 |
136 | case let sourceTypeName:
137 | return {
138 | VStack {
139 | if let title, !title.isEmpty {
140 | Text(title)
141 | .font(.system(size: 17, weight: .semibold))
142 | }
143 | Text("Failed to load preview (\(sourceTypeName))")
144 | .foregroundStyle(Color.red)
145 | .font(.caption.monospacedDigit())
146 | Text("\(fileID):\(line)")
147 | .font(.caption.monospacedDigit())
148 | BookSpacer(height: 16)
149 | }
150 | }
151 | }
152 | }
153 |
154 |
155 | // MARK: Comparable
156 |
157 | static func < (lhs: PreviewRegistryWrapper, rhs: PreviewRegistryWrapper) -> Bool {
158 | if lhs.module == rhs.module {
159 | return lhs.line < rhs.line
160 | }
161 | return lhs.module < rhs.module
162 | }
163 |
164 |
165 | // MARK: Equatable
166 |
167 | static func == (lhs: PreviewRegistryWrapper, rhs: PreviewRegistryWrapper) -> Bool {
168 | lhs.line == rhs.line && lhs.module == rhs.module
169 | }
170 |
171 |
172 | // MARK: - FieldReader
173 |
174 | private struct FieldReader {
175 |
176 | let instance: Any
177 | let typeName: String
178 |
179 | init(_ instance: Any) {
180 | self.instance = instance
181 | self.typeName = String(reflecting: type(of: instance))
182 | let mirror: Mirror = .init(reflecting: instance)
183 | self.fields = .init(
184 | uniqueKeysWithValues: mirror.children.compactMap { (label, value) in
185 | label.map({ ($0, value) })
186 | }
187 | )
188 | }
189 |
190 | subscript(_ key: String, _ nextKeys: String...) -> T {
191 | if nextKeys.isEmpty {
192 | return fields[key]! as! T
193 | }
194 | else {
195 | return Self.traverse(from: fields[key]!, nextKeys: nextKeys) as! T
196 | }
197 | }
198 |
199 | subscript(_ key: String, _ nextKeys: String...) -> FieldReader {
200 | .init(Self.traverse(from: fields[key]!, nextKeys: nextKeys))
201 | }
202 |
203 | private let fields: [String: Any]
204 |
205 | private static func traverse>(from first: Any, nextKeys: C) -> Any {
206 | if let key = nextKeys.first {
207 | let mirror: Mirror = .init(reflecting: first)
208 | return self.traverse(
209 | from: mirror.children.first(where: { $0.label == key })!.value,
210 | nextKeys: nextKeys.dropFirst()
211 | )
212 | }
213 | else {
214 | return first
215 | }
216 | }
217 | }
218 |
219 |
220 | // MARK: - MakeFunctionWrapper
221 |
222 | @MainActor
223 | private struct MakeFunctionWrapper {
224 |
225 | typealias Closure = @MainActor () -> T
226 | private let closure: Closure
227 |
228 | init(_ closure: Any) {
229 | // TODO: We need a workaround to avoid implicit @Sendable from @MainActor closures
230 | self.closure = unsafeBitCast(
231 | closure,
232 | to: Closure.self
233 | )
234 | }
235 |
236 | func callAsFunction() -> T {
237 | closure()
238 | }
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Internals/TargetViewControllerKey.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | extension EnvironmentValues {
4 | @Entry public var storybook_targetViewController: UIViewController?
5 | }
6 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Internals/fuzzyMatch.swift:
--------------------------------------------------------------------------------
1 | private extension String {
2 |
3 | func charAt(_ i: Int) -> Character {
4 | let index = self.index(self.startIndex, offsetBy: i)
5 | return self[index]
6 | }
7 |
8 | func charStrAt(_ i: Int) -> String {
9 | return String(charAt(i))
10 | }
11 | }
12 |
13 | extension String {
14 |
15 | func score(word: String, fuzziness: Double? = nil) -> Double {
16 |
17 | // If the string is equal to the word, perfect match.
18 | if self == word {
19 | return 1
20 | }
21 |
22 | //if it's not a perfect match and is empty return 0
23 | if word.isEmpty || self.isEmpty {
24 | return 0
25 | }
26 |
27 | var runningScore = 0.0
28 | var charScore = 0.0
29 | var finalScore = 0.0
30 |
31 | let string = self
32 | let lString = string.lowercased()
33 | let strLength = lString.count
34 | let lWord = word.lowercased()
35 | let wordLength = word.count
36 |
37 | var idxOf: String.Index!
38 | var startAt = lString.startIndex
39 | var fuzzies = 1.0
40 | var fuzzyFactor = 0.0
41 | var fuzzinessIsNil = true
42 |
43 | // Cache fuzzyFactor for speed increase
44 | if let fuzziness = fuzziness {
45 | fuzzyFactor = 1 - fuzziness
46 | fuzzinessIsNil = false
47 | }
48 |
49 | for i in 0 ..< wordLength {
50 | // Find next first case-insensitive match of word's i-th character.
51 | // The search in "string" begins at "startAt".
52 |
53 | if let range = lString.range(
54 | of: lWord.charStrAt(i),
55 | options: [.caseInsensitive, .diacriticInsensitive],
56 | range: startAt.. [any BookProvider.Type]? {
35 | let moduleName = Bundle.main.bundleURL.deletingPathExtension().lastPathComponent
36 | guard !moduleName.isEmpty else {
37 | return nil
38 | }
39 | var results: [any BookProvider.Type] = []
40 | for imageIndex in 0 ..< _dyld_image_count() {
41 | self.findAllBookProviders(
42 | inImageIndex: .init(imageIndex),
43 | filterByStorybookPageMacro: filterByStorybookPageMacro,
44 | results: &results
45 | )
46 | }
47 | return results
48 | }
49 |
50 | @available(iOS 17.0, *)
51 | static func findAllPreviews(
52 | excludeStorybookPageMacro: Bool = true
53 | ) -> [PreviewRegistryWrapper]? {
54 | let moduleName = Bundle.main.bundleURL.deletingPathExtension().lastPathComponent
55 | guard !moduleName.isEmpty else {
56 | return nil
57 | }
58 | var results: [PreviewRegistryWrapper] = []
59 | for imageIndex in 0 ..< _dyld_image_count() {
60 | self.findAllPreviews(
61 | inImageIndex: .init(imageIndex),
62 | excludeStorybookPageMacro: excludeStorybookPageMacro,
63 | results: &results
64 | )
65 | }
66 | return results
67 | }
68 |
69 | private static func findAllBookProviders(
70 | inImageIndex imageIndex: UInt32,
71 | filterByStorybookPageMacro: Bool,
72 | results: inout [any BookProvider.Type]
73 | ) {
74 | // Follows same approach here: https://github.com/apple/swift-testing/blob/main/Sources/TestingInternals/Discovery.cpp#L318
75 | guard
76 | let headerRawPtr: UnsafeRawPointer = _dyld_get_image_header(imageIndex)
77 | .map(UnsafeRawPointer.init(_:))
78 | else {
79 | return
80 | }
81 | let headerPtr = headerRawPtr.assumingMemoryBound(
82 | to: mach_header_64.self
83 | )
84 | // https://derekselander.github.io/dsdump/
85 | var size: UInt = 0
86 | guard
87 | let sectionRawPtr = getsectiondata(
88 | headerPtr,
89 | SEG_TEXT,
90 | "__swift5_types",
91 | &size
92 | )
93 | .map(UnsafeRawPointer.init(_:))
94 | else {
95 | return
96 | }
97 | let capacity: Int = .init(size) / MemoryLayout.size
98 | let sectionPtr = sectionRawPtr.assumingMemoryBound(
99 | to: SwiftTypeMetadataRecord.self
100 | )
101 | for index in 0 ..< capacity {
102 | let record = sectionPtr.advanced(by: index)
103 | guard
104 | let contextDescriptor = record.pointee.contextDescriptor(
105 | from: record
106 | )
107 | else {
108 | continue
109 | }
110 | guard !contextDescriptor.pointee.isGeneric() else {
111 | continue
112 | }
113 | guard
114 | contextDescriptor.pointee.kind().canConformToProtocol,
115 | !filterByStorybookPageMacro || contextDescriptor.nameContains(self._magicSubstring)
116 | else {
117 | continue
118 | }
119 | let metadataClosure = contextDescriptor.resolveValue(for: \.metadataAccessFunction)
120 | let metadata = metadataClosure(0xFF)
121 | guard
122 | let metadataAccessFunction = metadata.value
123 | else {
124 | continue
125 | }
126 | let anyType = unsafeBitCast(
127 | metadataAccessFunction,
128 | to: Any.Type.self
129 | )
130 | guard let bookProviderType = anyType as? any BookProvider.Type else {
131 | continue
132 | }
133 | results.append(bookProviderType)
134 | }
135 | }
136 |
137 | @available(iOS 17.0, *)
138 | private static func findAllPreviews(
139 | inImageIndex imageIndex: UInt32,
140 | excludeStorybookPageMacro: Bool,
141 | results: inout [PreviewRegistryWrapper]
142 | ) {
143 | // Follows same approach here: https://github.com/apple/swift-testing/blob/main/Sources/TestingInternals/Discovery.cpp#L318
144 | guard
145 | let headerRawPtr: UnsafeRawPointer = _dyld_get_image_header(imageIndex)
146 | .map(UnsafeRawPointer.init(_:))
147 | else {
148 | return
149 | }
150 | let headerPtr = headerRawPtr.assumingMemoryBound(
151 | to: mach_header_64.self
152 | )
153 | // https://derekselander.github.io/dsdump/
154 | var size: UInt = 0
155 | guard
156 | let sectionRawPtr = getsectiondata(
157 | headerPtr,
158 | SEG_TEXT,
159 | "__swift5_types",
160 | &size
161 | )
162 | .map(UnsafeRawPointer.init(_:))
163 | else {
164 | return
165 | }
166 | let capacity: Int = .init(size) / MemoryLayout.size
167 | let sectionPtr = sectionRawPtr.assumingMemoryBound(
168 | to: SwiftTypeMetadataRecord.self
169 | )
170 | for index in 0 ..< capacity {
171 | let record = sectionPtr.advanced(by: index)
172 | guard
173 | let contextDescriptor = record.pointee.contextDescriptor(
174 | from: record
175 | )
176 | else {
177 | continue
178 | }
179 | guard !contextDescriptor.pointee.isGeneric() else {
180 | continue
181 | }
182 | guard
183 | case .structType = contextDescriptor.pointee.kind(),
184 | contextDescriptor.nameContains("PreviewRegistry"),
185 | !excludeStorybookPageMacro || !contextDescriptor.nameContains(self._magicSubstring)
186 | else {
187 | continue
188 | }
189 | let metadataClosure = contextDescriptor.resolveValue(for: \.metadataAccessFunction)
190 | let metadata = metadataClosure(0xFF)
191 | guard
192 | let metadataAccessFunction = metadata.value
193 | else {
194 | continue
195 | }
196 | let anyType = unsafeBitCast(
197 | metadataAccessFunction,
198 | to: Any.Type.self
199 | )
200 | guard
201 | let previewType = anyType as? DeveloperToolsSupport.PreviewRegistry.Type
202 | else {
203 | continue
204 | }
205 | results.append(.init(previewType))
206 | }
207 | }
208 | }
209 |
210 | extension UnsafePointer where Pointee: SwiftLayoutPointer {
211 |
212 | fileprivate func resolvePointer(for keyPath: KeyPath>) -> UnsafePointer {
213 | let base: UnsafeRawPointer = .init(self)
214 | let fieldOffset = MemoryLayout.offset(of: keyPath)!
215 | let relativePointer = self.pointee[keyPath: keyPath]
216 | return base
217 | .advanced(by: fieldOffset)
218 | .advanced(by: .init(relativePointer.offset))
219 | .assumingMemoryBound(to: U.self)
220 | }
221 |
222 | fileprivate func resolveValue(for keyPath: KeyPath>) -> U {
223 | let base: UnsafeRawPointer = .init(self)
224 | let fieldOffset = MemoryLayout.offset(of: keyPath)!
225 | let relativePointer = self.pointee[keyPath: keyPath]
226 | let pointer = base
227 | .advanced(by: fieldOffset)
228 | .advanced(by: .init(relativePointer.offset))
229 | return unsafeBitCast(pointer, to: U.self)
230 | }
231 | }
232 |
233 | extension UnsafePointer where Pointee == SwiftTypeContextDescriptor {
234 | fileprivate func nameContains(_ string: String) -> Bool {
235 | string.withCString(
236 | {
237 | let nameCString = self.resolvePointer(for: \.name)
238 | return nil != strstr(nameCString, $0)
239 | }
240 | )
241 | }
242 | }
243 |
244 | fileprivate protocol SwiftLayoutPointer {
245 | static var maskValue: Int32 { get }
246 | }
247 |
248 | extension SwiftLayoutPointer {
249 | fileprivate static var maskValue: Int32 {
250 | return 0
251 | }
252 | }
253 |
254 | fileprivate struct SwiftRelativePointer {
255 |
256 | var offset: Int32 = 0
257 |
258 | func pointer(
259 | from base: UnsafeRawPointer,
260 | vmAddrSlide: Int
261 | ) -> UnsafePointer? {
262 | let maskedOffset: Int = .init(offset)
263 | guard maskedOffset != 0 else {
264 | return nil
265 | }
266 | return base
267 | .advanced(by: -vmAddrSlide)
268 | .advanced(by: maskedOffset)
269 | .assumingMemoryBound(to: T.self)
270 | }
271 | }
272 |
273 | extension SwiftRelativePointer where T: SwiftLayoutPointer {
274 |
275 | func value() -> Int32 {
276 | offset & T.maskValue
277 | }
278 |
279 | func pointer(
280 | from base: UnsafeRawPointer
281 | ) -> UnsafePointer? {
282 | let maskedOffset: Int = .init(offset & ~T.maskValue)
283 | guard maskedOffset != 0 else {
284 | return nil
285 | }
286 | return base
287 | .advanced(by: maskedOffset)
288 | .assumingMemoryBound(to: T.self)
289 | }
290 | }
291 |
292 | fileprivate struct SwiftTypeMetadataRecord: SwiftLayoutPointer {
293 |
294 | var pointer: SwiftRelativePointer
295 |
296 | func contextDescriptor(
297 | from base: UnsafeRawPointer
298 | ) -> UnsafePointer? {
299 | switch pointer.value() {
300 | case 0:
301 | return pointer.pointer(from: base)
302 |
303 | case 1:
304 | // Untested
305 | return pointer.pointer(from: base).map {
306 | let indirection: UnsafeRawPointer = .init($0)
307 | return indirection
308 | .assumingMemoryBound(to: UnsafePointer.self)
309 | .pointee
310 | }
311 |
312 | default:
313 | return nil
314 | }
315 | }
316 | }
317 |
318 | fileprivate struct SwiftTypeContextDescriptor: SwiftLayoutPointer {
319 | var flags: UInt32 = 0
320 | var parent: SwiftRelativePointer = .init()
321 | var name: SwiftRelativePointer = .init()
322 | var metadataAccessFunction: SwiftRelativePointer<(@convention(thin) (Int) -> MetadataAccessResponse)> = .init()
323 |
324 | func isGeneric() -> Bool {
325 | return (flags & 0x80) != 0
326 | }
327 |
328 | func kind() -> Kind {
329 | // https://github.com/blacktop/go-macho/blob/master/types/swift/types.go#L589
330 | return .init(rawValue: flags & 0x1F)
331 | }
332 |
333 | struct MetadataAccessResponse: SwiftLayoutPointer {
334 | var value: UnsafeRawPointer?
335 | var state: Int = 0
336 | }
337 |
338 | static let maskValue: Int32 = .init(MemoryLayout.alignment - 1)
339 |
340 | struct Kind: RawRepresentable, Equatable {
341 | static let module: Self = .init(rawValue: 0)
342 | static let `extension`: Self = .init(rawValue: 1)
343 | static let anonymous: Self = .init(rawValue: 2)
344 | static let `protocol`: Self = .init(rawValue: 3)
345 | static let opaqueType: Self = .init(rawValue: 4)
346 |
347 | static let typesStart: Self = .init(rawValue: 16)
348 |
349 | static let classType: Self = .init(rawValue: Self.typesStart.rawValue)
350 | static let structType: Self = .init(rawValue: Self.typesStart.rawValue + 1)
351 | static let enumType: Self = .init(rawValue: Self.typesStart.rawValue + 2)
352 |
353 | static let typesEnd: Self = .init(rawValue: 31)
354 |
355 | let rawValue: UInt32
356 |
357 | init(rawValue: UInt32) {
358 | self.rawValue = rawValue
359 | }
360 |
361 | var canConformToProtocol: Bool {
362 | return (Self.typesStart.rawValue ... Self.typesEnd.rawValue).contains(self.rawValue)
363 | }
364 | }
365 | }
366 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/Book.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct Book: BookView, Identifiable {
4 |
5 | public enum Node: Identifiable {
6 |
7 | public var id: String {
8 | switch self {
9 | case .folder(let v): return "folder.\(v.id)"
10 | case .page(let v): return "page.\(v.id)"
11 | }
12 | }
13 |
14 | case folder(Book)
15 | case page(BookPage)
16 |
17 | var sortingKey: String {
18 | switch self {
19 | case .folder(let v): return v.title
20 | case .page(let v): return v.title
21 | }
22 | }
23 |
24 | @MainActor
25 | var children: [Node]? {
26 | switch self {
27 | case .folder(let v): return v.contents
28 | case .page: return nil
29 | }
30 | }
31 |
32 | }
33 |
34 | public var id: UUID = .init()
35 |
36 | public let title: String
37 | public let contents: [Node]
38 |
39 | @State private var isExpandedAll: Bool = false
40 |
41 | public init(
42 | title: String,
43 | @FolderBuilder contents: () -> [Node]
44 | ) {
45 |
46 | self.title = title
47 |
48 | let _contents = contents()
49 |
50 | let folders =
51 | _contents
52 | .filter {
53 | switch $0 {
54 | case .folder:
55 | return true
56 | case .page:
57 | return false
58 | }
59 | }
60 | .sorted { a, b in
61 | a.sortingKey < b.sortingKey
62 | }
63 |
64 | let pages =
65 | _contents
66 | .filter {
67 | switch $0 {
68 | case .folder:
69 | return false
70 | case .page:
71 | return true
72 | }
73 | }
74 | .sorted { a, b in
75 | a.sortingKey < b.sortingKey
76 | }
77 |
78 | self.contents = folders + pages
79 |
80 | }
81 |
82 | public var body: some View {
83 | Section {
84 | ForEach(contents) { node in
85 | NodeOutlineGroup(expandsAll: isExpandedAll, node, children: \.children) { content in
86 | switch content {
87 | case .folder(let folder):
88 |
89 | HStack {
90 | Image.init(systemName: "folder.fill")
91 | Text(folder.title)
92 | }
93 |
94 | case .page(let page):
95 | page
96 | }
97 | }
98 |
99 | }
100 | } header: {
101 | HStack {
102 | Text(title)
103 | Spacer()
104 | Button(isExpandedAll ? " Collapse All" : " Expand All") {
105 | isExpandedAll.toggle()
106 | }
107 | .font(.caption)
108 | }
109 | }
110 |
111 | }
112 |
113 | func allPages() -> [BookPage] {
114 | contents.flatMap { node -> [BookPage] in
115 | switch node {
116 | case .folder(let folder):
117 | return folder.allPages()
118 | case .page(let page):
119 | return [page]
120 | }
121 | }
122 | }
123 |
124 | }
125 |
126 | // MARK: Runtime creation
127 | extension Book {
128 |
129 | /// All conformers to `BookProvider`, including those declared from the `#StorybookPage` macro
130 | public static func allBookProviders() -> [any BookProvider.Type] {
131 | self.findAllBookProviders(filterByStorybookPageMacro: false) ?? []
132 | }
133 |
134 | /// All conformers to `BookProvider` that were declared from the `#StorybookPage` macro
135 | public static func allStorybookPages() -> [any BookProvider.Type] {
136 | self.findAllBookProviders(filterByStorybookPageMacro: true) ?? []
137 | }
138 |
139 | /// All `#Preview`s as `BookPage`s
140 | @available(iOS 17.0, *)
141 | public static func allBookPreviews() -> [Node]? {
142 | guard let sortedPreviewRegistries = self.findAllPreviews() else {
143 | return nil
144 | }
145 | var fileIDsByModule: [String: Set] = [:]
146 | var registriesByFileID: [String: [PreviewRegistryWrapper]] = [:]
147 | for item in sortedPreviewRegistries {
148 | fileIDsByModule[item.module, default: []].insert(item.fileID)
149 | registriesByFileID[item.fileID, default: []].append(item)
150 | }
151 | return fileIDsByModule.keys.sorted().map { module in
152 | return Node.folder(
153 | .init(
154 | title: module,
155 | contents: { [fileIDs = fileIDsByModule[module]!.sorted()] in
156 | fileIDs.map { fileID in
157 |
158 | let name = String(fileID[fileID.index(after: module.endIndex)...])
159 | let registries = registriesByFileID[fileID]!
160 |
161 | return Node.folder(
162 | .init(
163 | title: name,
164 | contents: {
165 | registries.map { registry in
166 |
167 | let pageName: String
168 |
169 | if let displayName = registry.displayName {
170 | pageName = "\(displayName)"
171 | } else {
172 | pageName = "line: \(registry.line)"
173 | }
174 |
175 | return Node.page(
176 | .init(
177 | fileID,
178 | registry.line,
179 | title: pageName,
180 | usesScrollView: false,
181 | destination: {
182 | AnyView(registry.makeView())
183 | }
184 | )
185 | )
186 |
187 | }
188 | }))
189 |
190 | }
191 | }
192 | )
193 | )
194 | }
195 | }
196 |
197 | }
198 |
199 | @resultBuilder
200 | public struct FolderBuilder {
201 |
202 | public typealias Element = Book.Node
203 |
204 | @MainActor
205 | public static func buildExpression(_ expression: Provider.Type)
206 | -> [FolderBuilder.Element]
207 | {
208 | return [.page(expression.bookBody)]
209 | }
210 |
211 | public static func buildExpression(_ expression: BookPage) -> [FolderBuilder.Element] {
212 | return [.page(expression)]
213 | }
214 |
215 | public static func buildExpression(_ expression: [BookPage]) -> [FolderBuilder.Element] {
216 | return expression.map { .page($0) }
217 | }
218 |
219 | public static func buildExpression(_ expression: Book) -> [FolderBuilder.Element] {
220 | return [.folder(expression)]
221 | }
222 |
223 | public static func buildExpression(_ expression: [Book]) -> [FolderBuilder.Element] {
224 | return expression.map { .folder($0) }
225 | }
226 |
227 | public static func buildBlock() -> [Element] {
228 | []
229 | }
230 |
231 | public static func buildBlock(_ contents: C...) -> [Element]
232 | where C.Element == Element {
233 | return contents.flatMap { $0 }
234 | }
235 |
236 | public static func buildOptional(_ component: [Element]?) -> [Element] {
237 | return component ?? []
238 | }
239 |
240 | public static func buildEither(first component: [Element]) -> [Element] {
241 | return component
242 | }
243 |
244 | public static func buildEither(second component: [Element]) -> [Element] {
245 | return component
246 | }
247 |
248 | public static func buildArray(_ components: [[Element]]) -> [Element] {
249 | components.flatMap { $0 }
250 | }
251 |
252 | public static func buildExpression(_ element: Element?) -> [Element] {
253 | return element.map { [$0] } ?? []
254 | }
255 |
256 | public static func buildExpression(_ element: Element) -> [Element] {
257 | return [element]
258 | }
259 |
260 | public static func buildExpression(_ elements: C) -> [Element]
261 | where C.Element == Element {
262 | Array(elements)
263 | }
264 |
265 | public static func buildExpression(_ elements: C) -> [Element]
266 | where C.Element == Element? {
267 | elements.compactMap { $0 }
268 | }
269 |
270 | }
271 |
272 | private struct NodeOutlineGroup: View where Node: Identifiable, Content: View {
273 |
274 | let node: Node
275 | let childKeyPath: KeyPath
276 |
277 | let expandsAll: Bool
278 |
279 | @State var isExpanded: Bool
280 |
281 | let content: (Node) -> Content
282 |
283 | init(
284 | expandsAll: Bool,
285 | _ node: Node, children childKeyPath: KeyPath,
286 | @ViewBuilder content: @escaping (Node) -> Content
287 | ) {
288 | self._isExpanded = .init(wrappedValue: expandsAll)
289 | self.expandsAll = expandsAll
290 | self.node = node
291 | self.childKeyPath = childKeyPath
292 | self.content = content
293 | }
294 |
295 | var body: some View {
296 | Group {
297 | if node[keyPath: childKeyPath] != nil {
298 | DisclosureGroup(
299 | isExpanded: $isExpanded,
300 | content: {
301 | ForEach(node[keyPath: childKeyPath]!) { childNode in
302 | NodeOutlineGroup(
303 | expandsAll: expandsAll,
304 | childNode,
305 | children: childKeyPath,
306 | content: content
307 | )
308 | }
309 | },
310 | label: {
311 | content(node)
312 | }
313 | )
314 |
315 | } else {
316 | content(node)
317 | }
318 | }
319 | .onChange(of: expandsAll) { value in
320 | withAnimation(.default) {
321 | isExpanded = value
322 | }
323 | }
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | public struct BookAction: BookView {
25 |
26 | @Environment(\.storybook_targetViewController) var targetViewController
27 |
28 | public let declarationIdentifier: DeclarationIdentifier
29 |
30 | public let action: @MainActor (UIViewController) -> Void
31 |
32 | public let title: String
33 |
34 | public init(
35 | title: String,
36 | action: @escaping @MainActor (UIViewController) -> Void
37 | ) {
38 | self.title = title
39 | self.action = action
40 | self.declarationIdentifier = .init()
41 | }
42 |
43 | public var body: some View {
44 | Button(title) {
45 | if let targetViewController {
46 | action(targetViewController)
47 | } else {
48 |
49 | }
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookPage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import Foundation
23 | import SwiftUI
24 | import ResultBuilderKit
25 |
26 | public struct DeclarationIdentifier: Hashable, Codable, Sendable {
27 |
28 | public let index: Int
29 |
30 | nonisolated init() {
31 | index = issueUniqueNumber()
32 | }
33 |
34 | public init(raw index: Int) {
35 | self.index = index
36 | }
37 | }
38 |
39 | private let _lock = NSLock()
40 | private nonisolated(unsafe) var _counter: Int = 0
41 | private func issueUniqueNumber() -> Int {
42 | _lock.lock()
43 | defer {
44 | _lock.unlock()
45 | }
46 | _counter += 1
47 | return _counter
48 | }
49 |
50 | /// A component that displays a disclosure view.
51 | public struct BookPage: BookView, Identifiable {
52 |
53 | @Environment(\.bookContext) var context
54 |
55 | public nonisolated var id: DeclarationIdentifier {
56 | declarationIdentifier
57 | }
58 |
59 | public let usesScrollView: Bool
60 | public let title: String
61 | public let destination: AnyView
62 | public nonisolated let declarationIdentifier: DeclarationIdentifier
63 | private let fileID: any StringProtocol
64 | private let line: any FixedWidthInteger
65 |
66 | public init(
67 | _ fileID: any StringProtocol = #fileID,
68 | _ line: any FixedWidthInteger = #line,
69 | title: String,
70 | usesScrollView: Bool = true,
71 | @ViewBuilder destination: @MainActor () -> Destination
72 | ) {
73 | self.fileID = fileID
74 | self.line = line
75 | self.title = title
76 | self.usesScrollView = usesScrollView
77 | self.destination = AnyView(destination())
78 | self.declarationIdentifier = .init()
79 | }
80 |
81 | public var body: some View {
82 |
83 | NavigationLink {
84 | Group {
85 | if usesScrollView {
86 | ScrollView {
87 | destination
88 | }
89 | } else {
90 | destination
91 | }
92 | }
93 | .listStyle(.plain)
94 | .navigationTitle(title)
95 | .navigationBarTitleDisplayMode(.inline)
96 | .onAppear(perform: {
97 | context?.onOpen(pageID: id)
98 | })
99 | } label: {
100 | LinkLabel(title: title, fileID: fileID, line: line)
101 | .contextMenu(menuItems: {
102 | Text(title)
103 | }) {
104 | destination
105 | }
106 | }
107 |
108 | }
109 | }
110 |
111 | private struct LinkLabel: View {
112 |
113 | let title: any StringProtocol
114 | let fileID: any StringProtocol
115 | let line: any FixedWidthInteger
116 |
117 | var body: some View {
118 | HStack {
119 | Image.init(systemName: "doc")
120 | VStack(alignment: .leading) {
121 | Text(title)
122 | Text("\(fileID):\(line)")
123 | .font(.caption.monospacedDigit())
124 | .opacity(0.8)
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookPresent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | /// A component descriptor that just displays UI-Component
25 | public struct BookPresent: BookView {
26 |
27 | @Environment(\.storybook_targetViewController) private var targetViewController
28 |
29 | public let declarationIdentifier: DeclarationIdentifier
30 | public let presentedViewControllerBlock: @MainActor () -> UIViewController
31 |
32 | public let title: String
33 |
34 | public init(
35 | title: String,
36 | presentingViewControllerBlock: @escaping @MainActor () -> UIViewController
37 | ) {
38 | self.title = title
39 | self.presentedViewControllerBlock = presentingViewControllerBlock
40 | self.declarationIdentifier = .init()
41 | }
42 |
43 | public var body: some View {
44 | Button(title) {
45 | let viewController = presentedViewControllerBlock()
46 | targetViewController?.present(viewController, animated: true)
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookPreview.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | private struct FrameConstraint {
25 | var minWidth: CGFloat? = nil
26 | var idealWidth: CGFloat? = nil
27 | var maxWidth: CGFloat? = nil
28 | var minHeight: CGFloat? = nil
29 | var idealHeight: CGFloat? = nil
30 | var maxHeight: CGFloat? = nil
31 | }
32 |
33 | public struct BookPreview: BookView {
34 |
35 | public struct Context {
36 |
37 | var controlView: AnyView?
38 |
39 | public mutating func control(_ content: () -> Content) {
40 | controlView = AnyView(content())
41 | }
42 |
43 | }
44 |
45 | public let viewBlock: @MainActor (inout Context) -> UIView
46 |
47 | public let declarationIdentifier: DeclarationIdentifier
48 |
49 | private let fileID: any StringProtocol
50 | private let line: any FixedWidthInteger
51 | private let title: String?
52 | private var frameConstraint: FrameConstraint = .init()
53 |
54 | public init(
55 | _ fileID: any StringProtocol = #fileID,
56 | _ line: any FixedWidthInteger = #line,
57 | title: String? = nil,
58 | viewBlock: @escaping @MainActor (inout Context) -> UIView
59 | ) {
60 |
61 | self.title = title
62 | self.fileID = fileID
63 | self.line = line
64 | self.viewBlock = viewBlock
65 |
66 | self.declarationIdentifier = .init()
67 | }
68 |
69 | @State var controlView: AnyView?
70 |
71 | public var body: some View {
72 |
73 | VStack {
74 | if let title {
75 | Text(title)
76 | .font(.system(size: 17, weight: .semibold))
77 | }
78 |
79 | _ViewHost(
80 | maxWidth: UIScreen.main.bounds.size.width /* so bad but it's for sizing with autolayout */,
81 | instantiate: {
82 |
83 | var context: Context = .init()
84 | let view = viewBlock(&context)
85 |
86 | // TODO: currently using workaround
87 | Task {
88 | controlView = context.controlView
89 | }
90 |
91 | return _View(element: view, frameConstraint: frameConstraint)
92 | },
93 | update: { _, _ in }
94 | )
95 |
96 | controlView
97 |
98 | Text("\(fileID):\(line)")
99 | .font(.caption.monospacedDigit())
100 |
101 | BookSpacer(height: 16)
102 |
103 | }
104 |
105 | }
106 |
107 | public func previewFrame(
108 | width: CGFloat?,
109 | height: CGFloat?
110 | ) -> Self {
111 | modified {
112 | $0.frameConstraint.idealWidth = width
113 | $0.frameConstraint.idealHeight = height
114 | }
115 | }
116 |
117 | public func previewFrame(
118 | minWidth: CGFloat? = nil,
119 | idealWidth: CGFloat? = nil,
120 | maxWidth: CGFloat? = nil,
121 | minHeight: CGFloat? = nil,
122 | idealHeight: CGFloat? = nil,
123 | maxHeight: CGFloat? = nil
124 | ) -> Self {
125 | modified {
126 | $0.frameConstraint.minWidth = minWidth
127 | $0.frameConstraint.maxWidth = maxWidth
128 | $0.frameConstraint.minHeight = minHeight
129 | $0.frameConstraint.maxHeight = maxHeight
130 |
131 | $0.frameConstraint.idealWidth = idealWidth
132 | $0.frameConstraint.idealHeight = idealHeight
133 | }
134 | }
135 |
136 | }
137 |
138 | private final class _View: UIView {
139 |
140 | override class var requiresConstraintBasedLayout: Bool { true }
141 |
142 | init() {
143 | super.init(frame: .zero)
144 |
145 | }
146 |
147 | @available(*, unavailable)
148 | required init?(
149 | coder aDecoder: NSCoder
150 | ) {
151 | fatalError("init(coder:) has not been implemented")
152 | }
153 |
154 | convenience init(
155 | element: UIView,
156 | frameConstraint: FrameConstraint
157 | ) {
158 |
159 | self.init()
160 |
161 | element.translatesAutoresizingMaskIntoConstraints = false
162 | element.setContentHuggingPriority(.defaultLow, for: .horizontal)
163 | element.setContentHuggingPriority(.defaultLow, for: .vertical)
164 |
165 | addSubview(element)
166 |
167 | var constraints: [NSLayoutConstraint] = []
168 |
169 | if let maxWidth = frameConstraint.maxWidth {
170 | if (maxWidth == .infinity) || (maxWidth == .greatestFiniteMagnitude) {
171 | let c = element.widthAnchor.constraint(equalToConstant: 5000)
172 | c.priority = .defaultHigh + 1
173 | constraints.append(
174 | c
175 | )
176 | } else {
177 | constraints.append(
178 | element.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth)
179 | )
180 | }
181 | }
182 |
183 | if let maxHeight = frameConstraint.minHeight {
184 |
185 | if (maxHeight == .infinity) || (maxHeight == .greatestFiniteMagnitude) {
186 | let c = element.heightAnchor.constraint(equalToConstant: 5000)
187 | c.priority = .defaultHigh + 1
188 | constraints.append(
189 | c
190 | )
191 | } else {
192 |
193 | constraints.append(
194 | element.heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight)
195 | )
196 | }
197 | }
198 |
199 | if let minWidth = frameConstraint.minWidth {
200 | constraints.append(
201 | element.widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth)
202 | )
203 | }
204 |
205 | if let minHeight = frameConstraint.minHeight {
206 |
207 | constraints.append(
208 | element.heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
209 | )
210 | }
211 |
212 | if let idealWidth = frameConstraint.idealWidth {
213 | constraints.append(
214 | element.widthAnchor.constraint(equalToConstant: idealWidth)
215 | )
216 | }
217 |
218 | if let idealHeight = frameConstraint.idealHeight {
219 | constraints.append(
220 | element.heightAnchor.constraint(equalToConstant: idealHeight)
221 | )
222 | }
223 |
224 | constraints.append(contentsOf: [
225 |
226 | element.centerXAnchor.constraint(equalTo: centerXAnchor),
227 | element.centerYAnchor.constraint(equalTo: centerYAnchor),
228 |
229 | element.topAnchor.constraint(greaterThanOrEqualTo: topAnchor),
230 | element.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor),
231 | element.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor),
232 | element.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
233 |
234 | ])
235 |
236 | NSLayoutConstraint.activate(constraints)
237 |
238 | }
239 |
240 | }
241 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import ResultBuilderKit
23 | import SwiftUI
24 |
25 | @_alwaysEmitConformanceMetadata
26 | public protocol BookProvider {
27 | @MainActor
28 | static var bookBody: BookPage { get }
29 | }
30 |
31 | extension EnvironmentValues {
32 |
33 | @Entry public var bookContext: BookStore?
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookPush.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | /// A component descriptor that previewing with push presentation.
25 | public struct BookPush: BookView {
26 |
27 | @Environment(\.storybook_targetViewController) var targetViewController
28 |
29 | public let pushingViewControllerBlock: @MainActor () -> UIViewController
30 | public let declarationIdentifier: DeclarationIdentifier
31 |
32 | public let title: String
33 |
34 | public init(
35 | title: String,
36 | pushingViewControllerBlock: @escaping @MainActor () -> UIViewController
37 | ) {
38 | self.title = title
39 | self.pushingViewControllerBlock = pushingViewControllerBlock
40 | self.declarationIdentifier = .init()
41 | }
42 |
43 | public var body: some View {
44 |
45 | NavigationLink(title, destination: {
46 | _ViewControllerHost(instantiate: pushingViewControllerBlock)
47 | .environment(\.storybook_targetViewController, targetViewController)
48 | })
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookStore.swift:
--------------------------------------------------------------------------------
1 |
2 | @MainActor
3 | public final class BookStore: ObservableObject {
4 |
5 | @Published var historyPages: [BookPage] = []
6 |
7 | public let title: String
8 | public let book: Book
9 |
10 | private let allPages: [BookPage.ID: BookPage]
11 |
12 | private let userDefaults = UserDefaults(suiteName: "jp.eure.storybook2") ?? .standard
13 |
14 | public init(
15 | book: Book
16 | ) {
17 |
18 | self.title = book.title
19 | self.book = book
20 |
21 | self.allPages = book.allPages().reduce(
22 | into: [BookPage.ID: BookPage](),
23 | { partialResult, item in
24 | partialResult[item.id] = item
25 | }
26 | )
27 |
28 | updateHistory()
29 | }
30 |
31 | private func updateHistory() {
32 |
33 | let indexes = userDefaults.array(forKey: "history") as? [Int] ?? []
34 |
35 | let _pages = indexes.compactMap { (index: Int) -> BookPage? in
36 | let id = DeclarationIdentifier(raw: index)
37 | guard let page = allPages[id] else {
38 | return nil
39 | }
40 | return page
41 | }
42 |
43 | historyPages = _pages
44 |
45 | }
46 |
47 | func onOpen(pageID: DeclarationIdentifier) {
48 |
49 | guard allPages.keys.contains(pageID) else {
50 | return
51 | }
52 |
53 | let index = pageID.index
54 |
55 | var current = userDefaults.array(forKey: "history") as? [Int] ?? []
56 | if let index = current.firstIndex(of: index) {
57 | current.remove(at: index)
58 | }
59 |
60 | current.insert(index, at: 0)
61 | current = Array(current.prefix(5))
62 |
63 | userDefaults.set(current, forKey: "history")
64 |
65 | print("Update history", current)
66 |
67 | updateHistory()
68 | }
69 |
70 | nonisolated func search(query: String) async -> [BookPage] {
71 |
72 | // find pages using query but fuzzy
73 | let pages = allPages.values
74 | .map { page -> (score: Double, page: BookPage) in
75 | let score = page.title.score(word: query)
76 | return (score, page)
77 | }
78 | .filter { $0.score > 0 }
79 | .sorted { $0.score > $1.score }
80 | .map { $0.page }
81 |
82 | return pages
83 | }
84 |
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | public struct BookText: View {
25 |
26 | public let text: String
27 | public var foregroundColor: UIColor = {
28 | if #available(iOS 13.0, *) {
29 | return .label
30 | } else {
31 | return .darkText
32 | }
33 | }()
34 |
35 | public var font: UIFont = .preferredFont(forTextStyle: .body)
36 |
37 | public init(_ text: String) {
38 | self.text = text
39 | }
40 |
41 | public var body: some View {
42 | Text(text)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Primitives/BookView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import UIKit
23 | import SwiftUI
24 |
25 | public protocol BookView: SwiftUI.View {
26 |
27 | }
28 |
29 | extension BookView {
30 |
31 | public func modified(_ modify: (inout Self) -> Void) -> Self {
32 | var s = self
33 | modify(&s)
34 | return s
35 | }
36 |
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/SearchBar.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct SearchBar: View {
4 |
5 | @Binding var text: String
6 |
7 | var body: some View {
8 |
9 | ZStack {
10 |
11 | RoundedRectangle(cornerRadius: 8)
12 | .fill(Color(red: 239 / 255,
13 | green: 239 / 255,
14 | blue: 241 / 255))
15 | .frame(height: 36)
16 |
17 | HStack(spacing: 6) {
18 |
19 | Image(systemName: "magnifyingglass")
20 | .foregroundColor(.gray)
21 |
22 | if #available(iOS 15, *) {
23 | TextField("Search", text: $text)
24 | .autocorrectionDisabled()
25 | .textInputAutocapitalization(.never)
26 | } else {
27 | TextField("Search", text: $text)
28 | .autocorrectionDisabled()
29 | }
30 |
31 | if !text.isEmpty {
32 | Button {
33 | text.removeAll()
34 | } label: {
35 | Image(systemName: "xmark.circle.fill")
36 | .foregroundColor(.gray)
37 | }
38 | .padding(.trailing, 6)
39 | }
40 | }
41 | .padding()
42 | }
43 |
44 | }
45 | }
46 |
47 | #if DEBUG
48 |
49 | enum Preview_SearchBar: PreviewProvider {
50 |
51 | static var previews: some View {
52 |
53 | Group {
54 | SearchBar(text: .constant("A"))
55 | }
56 |
57 | }
58 |
59 | }
60 |
61 | #endif
62 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/Storybook.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @available(iOS 17.0, *)
4 | public struct Storybook: View {
5 |
6 | public init() {
7 |
8 | }
9 |
10 | public var body: some View {
11 | StorybookDisplayRootView(
12 | bookStore: .init(
13 | book: Book.init(title: "Contents") {
14 |
15 | if let nodes = Book.allBookPreviews() {
16 | Book(title: "#Preview") {
17 | nodes
18 | }
19 | }
20 |
21 | }
22 | )
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/StorybookDisplayRootView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import SwiftUISupport
3 | import UIKit
4 |
5 | public struct StorybookDisplayRootView: View {
6 |
7 | let book: BookContainer
8 |
9 | @MainActor
10 | public init(bookStore: BookStore) {
11 | self.book = .init(store: bookStore)
12 | }
13 |
14 | public var body: some View {
15 |
16 | _ViewControllerHost {
17 | let controller = _ViewController(content: book)
18 | return controller
19 | }
20 | .ignoresSafeArea()
21 |
22 | }
23 | }
24 |
25 | public struct BookActionHosting: View {
26 |
27 | private let content: Content
28 |
29 | public init(@ViewBuilder content: () -> Content) {
30 | self.content = content()
31 | }
32 |
33 | public var body: some View {
34 |
35 | _ViewControllerHost {
36 | let controller = _ViewController(content: content)
37 | return controller
38 | }
39 | .ignoresSafeArea()
40 |
41 | }
42 | }
43 |
44 | struct BookContainer: View {
45 |
46 | struct UniqueBox: Hashable {
47 |
48 | static func == (lhs: UniqueBox, rhs: UniqueBox) -> Bool {
49 | lhs.uuid == rhs.uuid
50 | }
51 |
52 | func hash(into hasher: inout Hasher) {
53 | hasher.combine(uuid)
54 | }
55 |
56 | let uuid: UUID = .init()
57 | let value: T
58 |
59 | init(value: T) {
60 | self.value = value
61 | }
62 | }
63 |
64 | // MARK: Properties
65 |
66 | @ObservedObject private var store: BookStore
67 |
68 | @State private var lastUsedItem: UniqueBox?
69 | @State private var query: String = ""
70 | @State private var result: [BookPage] = []
71 | @State private var currentTask: Task?
72 |
73 | @State var path: NavigationPath = .init()
74 |
75 | // MARK: Initializers
76 |
77 | @MainActor
78 | public init(
79 | store: BookStore
80 | ) {
81 | self.store = store
82 | }
83 |
84 | // MARK: View
85 |
86 | public var body: some View {
87 |
88 | NavigationStack(path: $path) {
89 | List {
90 |
91 | if result.isEmpty == false {
92 | Section {
93 | ForEach(result) { link in
94 | link
95 | }
96 | } header: {
97 | Text("Search Result")
98 | }
99 | }
100 |
101 | Section {
102 | ForEach(store.historyPages) { link in
103 | link
104 | }
105 | } header: {
106 | Text("History")
107 | }
108 |
109 | store.book
110 |
111 | }
112 | .navigationTitle(store.title)
113 | .searchable(text: $query, prompt: "Search")
114 | .navigationDestination(for: UniqueBox.self) { page in
115 | page
116 | .value
117 | .destination
118 | .environment(\.bookContext, store)
119 | }
120 | }
121 |
122 | .tabItem {
123 | Image(systemName: "list.bullet")
124 | Text("List")
125 | }
126 | .environment(\.bookContext, store)
127 | .onAppear {
128 | if let value = store.historyPages.first {
129 | path.append(UniqueBox(value: value))
130 | }
131 | }
132 | .onChange(of: query, perform: { value in
133 |
134 | guard value.isEmpty == false else {
135 | currentTask?.cancel()
136 | result = []
137 | return
138 | }
139 |
140 | currentTask?.cancel()
141 | currentTask = Task {
142 |
143 | let result = await store.search(query: value)
144 |
145 | print(result.map { $0.title })
146 |
147 | guard Task.isCancelled == false else {
148 | return
149 | }
150 |
151 | self.result = result
152 | }
153 |
154 | })
155 | }
156 |
157 | }
158 |
159 | final class _ViewController: UIViewController {
160 |
161 | private let content: Content
162 |
163 | init(content: Content) {
164 | self.content = content
165 | super.init(nibName: nil, bundle: nil)
166 | }
167 |
168 | required init?(coder: NSCoder) {
169 | fatalError("init(coder:) has not been implemented")
170 | }
171 |
172 | override func viewDidLoad() {
173 | super.viewDidLoad()
174 |
175 | let hosting = UIHostingController(
176 | rootView:
177 | content
178 | .environment(\.storybook_targetViewController, self)
179 | )
180 |
181 | addChild(hosting)
182 | view.addSubview(hosting.view)
183 | hosting.view.translatesAutoresizingMaskIntoConstraints = false
184 |
185 | NSLayoutConstraint.activate([
186 | hosting.view.topAnchor.constraint(equalTo: view.topAnchor),
187 | hosting.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
188 | hosting.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
189 | hosting.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
190 | ])
191 |
192 | hosting.didMove(toParent: self)
193 |
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/Sources/StorybookKit/StorybookKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import Foundation
23 |
24 | @_exported import SwiftUI
25 |
26 | @freestanding(declaration)
27 | public macro StorybookPage(
28 | title: String,
29 | @ViewBuilder contents: @escaping @MainActor () -> any View
30 | ) = #externalMacro(
31 | module: "StorybookMacrosPlugin",
32 | type: "StorybookPageMacro"
33 | )
34 |
35 | @freestanding(declaration)
36 | public macro StorybookPage(
37 | target: Target.Type = Target.self,
38 | @ViewBuilder contents: @escaping @MainActor () -> any View
39 | ) = #externalMacro(
40 | module: "StorybookMacrosPlugin",
41 | type: "StorybookPageMacro"
42 | )
43 |
44 | @freestanding(expression)
45 | public macro StorybookPreview(
46 | title: String,
47 | @ViewBuilder contents: @escaping @MainActor () -> any View
48 | ) -> AnyView = #externalMacro(
49 | module: "StorybookMacrosPlugin",
50 | type: "StorybookPreviewMacro"
51 | )
52 |
53 | @freestanding(expression)
54 | public macro StorybookPreview(
55 | target: Target.Type = Target.self,
56 | @ViewBuilder contents: @escaping @MainActor () -> any View
57 | ) -> AnyView = #externalMacro(
58 | module: "StorybookMacrosPlugin",
59 | type: "StorybookPreviewMacro"
60 | )
61 |
--------------------------------------------------------------------------------
/Sources/StorybookKitTextureSupport/BookNodePreview.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import AsyncDisplayKit
23 | import SwiftUI
24 | import StorybookKit
25 | import TextureBridging
26 | import TextureSwiftSupport
27 |
28 | public struct BookNodePreview: BookView {
29 |
30 | private var backing: BookPreview
31 |
32 | public init(
33 | _ fileID: any StringProtocol = #fileID,
34 | _ line: any FixedWidthInteger = #line,
35 | title: String? = nil,
36 | nodeBlock: @escaping @MainActor (inout BookPreview.Context) -> ASDisplayNode
37 | ) {
38 |
39 | self.backing = .init(fileID, line, title: title) { context in
40 | let body = nodeBlock(&context)
41 |
42 | let node = AnyDisplayNode { _, size in
43 | return LayoutSpec {
44 | VStackLayout { body.flexGrow(1).flexShrink(1) }
45 | }
46 | }
47 | return NodeView(node: node)
48 | }
49 |
50 | }
51 |
52 | public var body: some View {
53 | backing
54 | }
55 |
56 | public func previewFrame(
57 | width: CGFloat?,
58 | height: CGFloat?
59 | ) -> Self {
60 | modified {
61 | $0.backing = $0.backing.previewFrame(width: width, height: height)
62 | }
63 | }
64 |
65 | public func previewFrame(
66 | minWidth: CGFloat? = nil,
67 | idealWidth: CGFloat? = nil,
68 | maxWidth: CGFloat? = nil,
69 | minHeight: CGFloat? = nil,
70 | idealHeight: CGFloat? = nil,
71 | maxHeight: CGFloat? = nil
72 | ) -> Self {
73 | modified {
74 | $0.backing = $0.backing.previewFrame(
75 | minWidth: minWidth,
76 | idealWidth: idealWidth,
77 | maxWidth: maxWidth,
78 | minHeight: minHeight,
79 | idealHeight: idealHeight,
80 | maxHeight: maxHeight
81 | )
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/StorybookKitTextureSupport/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/StorybookKitTextureSupport/StorybookKitTextureSupport.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | #import
23 |
24 | //! Project version number for StorybookKitTextureSupport.
25 | FOUNDATION_EXPORT double StorybookKitTextureSupportVersionNumber;
26 |
27 | //! Project version string for StorybookKitTextureSupport.
28 | FOUNDATION_EXPORT const unsigned char StorybookKitTextureSupportVersionString[];
29 |
30 | // In this header, you should import all the public headers of your framework using statements like #import
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Sources/StorybookMacrosPlugin/StorybookMacrosPlugin.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2024 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import SwiftCompilerPlugin
23 | import SwiftSyntaxMacros
24 |
25 | // MARK: - StorybookMacrosPlugin
26 |
27 | @main
28 | struct StorybookMacrosPlugin: CompilerPlugin {
29 |
30 | // MARK: CompilerPlugin
31 |
32 | let providingMacros: [Macro.Type] = [
33 | StorybookPageMacro.self,
34 | StorybookPreviewMacro.self
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/StorybookMacrosPlugin/StorybookPageMacro.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2024 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import Foundation
23 | import SwiftCompilerPlugin
24 | import SwiftSyntax
25 | import SwiftSyntaxBuilder
26 | import SwiftSyntaxMacros
27 |
28 | // MARK: - StorybookPageMacro
29 |
30 | public struct StorybookPageMacro: DeclarationMacro {
31 |
32 | // MARK: Internal
33 |
34 | /// Should match `Book._magicSubstring`
35 | static let _magicSubstring: String = "__🤖🛠️_StorybookMagic_"
36 |
37 | // MARK: DeclarationMacro
38 |
39 | public static func expansion(
40 | of node: some FreestandingMacroExpansionSyntax,
41 | in context: some MacroExpansionContext
42 | ) throws -> [DeclSyntax] {
43 | let (title, closure) = try self.parseArguments(from: node)
44 | let enumName = context.makeUniqueName(
45 | self._magicSubstring
46 | )
47 | return [
48 | .init(
49 | stringLiteral: """
50 | enum \(enumName): BookProvider {
51 | @MainActor
52 | static var bookBody: BookPage {
53 | .init(
54 | title: \(title),
55 | destination: \(closure)
56 | )
57 | }
58 | }
59 | """
60 | )
61 | ]
62 | }
63 |
64 |
65 | // MARK: Private
66 |
67 | private static func parseArguments(
68 | from node: some FreestandingMacroExpansionSyntax
69 | ) throws -> (
70 | title: ExprSyntax,
71 | closure: ClosureExprSyntax
72 | ) {
73 | var argumentsIterator = node.argumentList.makeIterator()
74 | var title: ExprSyntax? = (node.genericArgumentClause?.arguments.first?.argument)
75 | .flatMap { genericType -> TypeSyntaxProtocol? in
76 | genericType.as(IdentifierTypeSyntax.self)
77 | ?? genericType.as(MemberTypeSyntax.self)
78 | }
79 | .map({ .init(stringLiteral: "_typeName(\($0).self)") })
80 |
81 | var closure: ClosureExprSyntax? = node.trailingClosure
82 | while let argument = argumentsIterator.next()?
83 | .as(LabeledExprSyntax.self) {
84 |
85 | switch argument.label?.text {
86 |
87 | case "title"?:
88 | title = argument.expression
89 |
90 | case "target"?:
91 | title = .init(stringLiteral: "_typeName(\(argument))")
92 |
93 | case "contents"?:
94 | closure = argument.expression
95 | .as(ClosureExprSyntax.self)
96 |
97 | default:
98 | fatalError()
99 | }
100 | }
101 | return (
102 | title: title!,
103 | closure: closure!
104 | )
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Sources/StorybookMacrosPlugin/StorybookPreviewMacro.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2024 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import Foundation
23 | import SwiftCompilerPlugin
24 | import SwiftSyntax
25 | import SwiftSyntaxBuilder
26 | import SwiftSyntaxMacros
27 |
28 | // MARK: - StorybookPreviewMacro
29 |
30 | public struct StorybookPreviewMacro: ExpressionMacro {
31 |
32 | // MARK: Internal
33 |
34 | /// Should match `Book._magicSubstring`
35 | static let _magicSubstring: String = "__🤖🛠️_StorybookMagic_"
36 |
37 | // MARK: ExpressionMacro
38 |
39 | public static func expansion(
40 | of node: some FreestandingMacroExpansionSyntax,
41 | in context: some MacroExpansionContext
42 | ) throws -> ExprSyntax {
43 | let (title, closure) = try self.parseArguments(from: node)
44 | let enumName = context.makeUniqueName(
45 | self._magicSubstring
46 | )
47 | return .init(
48 | stringLiteral: """
49 | {
50 | enum \(enumName): BookProvider {
51 | @MainActor
52 | static var bookBody: BookPage {
53 | .init(
54 | title: \(title),
55 | destination: \(closure)
56 | )
57 | }
58 | }
59 | return \(enumName).bookBody.destination
60 | }()
61 | """
62 | )
63 | }
64 |
65 |
66 | // MARK: Private
67 |
68 | private static func parseArguments(
69 | from node: some FreestandingMacroExpansionSyntax
70 | ) throws -> (
71 | title: ExprSyntax,
72 | closure: ClosureExprSyntax
73 | ) {
74 | var argumentsIterator = node.argumentList.makeIterator()
75 | var title: ExprSyntax? = (node.genericArgumentClause?.arguments.first?.argument)
76 | .flatMap { genericType -> TypeSyntaxProtocol? in
77 | genericType.as(IdentifierTypeSyntax.self)
78 | ?? genericType.as(MemberTypeSyntax.self)
79 | }
80 | .map({ .init(stringLiteral: "_typeName(\($0).self)") })
81 |
82 | var closure: ClosureExprSyntax? = node.trailingClosure
83 | while let argument = argumentsIterator.next()?
84 | .as(LabeledExprSyntax.self) {
85 |
86 | switch argument.label?.text {
87 |
88 | case "title"?:
89 | title = argument.expression
90 |
91 | case "target"?:
92 | title = .init(stringLiteral: "_typeName(\(argument))")
93 |
94 | case "contents"?:
95 | closure = argument.expression
96 | .as(ClosureExprSyntax.self)
97 |
98 | default:
99 | fatalError()
100 | }
101 | }
102 | return (
103 | title: title!,
104 | closure: closure!
105 | )
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Sources/StorybookMacrosTests/StorybookPageTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2024 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import MacroTesting
23 | import XCTest
24 | import StorybookMacrosPlugin
25 |
26 | final class StorybookPageTests: XCTestCase {
27 |
28 | override func invokeTest() {
29 | withMacroTesting(
30 | isRecording: false,
31 | macros: ["StorybookPage": StorybookPageMacro.self]
32 | ) {
33 | super.invokeTest()
34 | }
35 | }
36 |
37 | func testUnlabeledTitleUnlabeledContent() {
38 | assertMacro {
39 | """
40 | #StorybookPage {
41 | VStack {
42 | Text("test")
43 | }
44 | .tint(.accentColor)
45 | }
46 | """
47 | } expansion: {
48 | """
49 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
50 | @MainActor
51 | static var bookBody: BookPage {
52 | .init(
53 | title: _typeName(UIView.self),
54 | destination: {
55 | VStack {
56 | Text("test")
57 | }
58 | .tint(.accentColor)
59 | }
60 | )
61 | }
62 | }
63 | """
64 | }
65 |
66 | assertMacro {
67 | """
68 | #StorybookPage(title: "Path1.Path2.Title") {
69 | VStack {
70 | Text("test")
71 | }
72 | .tint(.accentColor)
73 | }
74 | """
75 | } expansion: {
76 | """
77 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
78 | @MainActor
79 | static var bookBody: BookPage {
80 | .init(
81 | title: "Path1.Path2.Title",
82 | destination: {
83 | VStack {
84 | Text("test")
85 | }
86 | .tint(.accentColor)
87 | }
88 | )
89 | }
90 | }
91 | """
92 | }
93 | }
94 |
95 | func testLabeledTitleUnlabeledContent() {
96 | assertMacro {
97 | """
98 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
99 | #StorybookPage(target: Namespace1.Namespace2.Namespace3.TestableView.self) {
100 | VStack {
101 | Text("test")
102 | }
103 | .tint(.accentColor)
104 | }
105 | """
106 | } expansion: {
107 | """
108 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
109 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
110 | @MainActor
111 | static var bookBody: BookPage {
112 | .init(
113 | title: _typeName(target: Namespace1.Namespace2.Namespace3.TestableView.self),
114 | destination: {
115 | VStack {
116 | Text("test")
117 | }
118 | .tint(.accentColor)
119 | }
120 | )
121 | }
122 | }
123 | """
124 | }
125 |
126 | assertMacro {
127 | """
128 | #StorybookPage(title: "Path1.Path2.Title") {
129 | VStack {
130 | Text("test")
131 | }
132 | .tint(.accentColor)
133 | }
134 | """
135 | } expansion: {
136 | """
137 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
138 | @MainActor
139 | static var bookBody: BookPage {
140 | .init(
141 | title: "Path1.Path2.Title",
142 | destination: {
143 | VStack {
144 | Text("test")
145 | }
146 | .tint(.accentColor)
147 | }
148 | )
149 | }
150 | }
151 | """
152 | }
153 | }
154 |
155 | func testUnlabeledTitleLabeledContent() {
156 | assertMacro {
157 | """
158 | #StorybookPage(
159 | contents: {
160 | VStack {
161 | Text("test")
162 | }
163 | .tint(.accentColor)
164 | }
165 | )
166 | """
167 | } expansion: {
168 | """
169 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
170 | @MainActor
171 | static var bookBody: BookPage {
172 | .init(
173 | title: _typeName(UIView.self),
174 | destination: {
175 | VStack {
176 | Text("test")
177 | }
178 | .tint(.accentColor)
179 | }
180 | )
181 | }
182 | }
183 | """
184 | }
185 |
186 | assertMacro {
187 | """
188 | #StorybookPage(
189 | title: "Path1.Path2.Title",
190 | contents: {
191 | VStack {
192 | Text("test")
193 | }
194 | .tint(.accentColor)
195 | }
196 | )
197 | """
198 | } expansion: {
199 | """
200 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
201 | @MainActor
202 | static var bookBody: BookPage {
203 | .init(
204 | title: "Path1.Path2.Title",
205 | destination: {
206 | VStack {
207 | Text("test")
208 | }
209 | .tint(.accentColor)
210 | }
211 | )
212 | }
213 | }
214 | """
215 | }
216 | }
217 |
218 | func testLabeledTitleLabeledContent() {
219 | assertMacro {
220 | """
221 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
222 | #StorybookPage(
223 | target: Namespace1.Namespace2.Namespace3.TestableView.self,
224 | contents: {
225 | VStack {
226 | Text("test")
227 | }
228 | .tint(.accentColor)
229 | }
230 | )
231 | """
232 | } expansion: {
233 | """
234 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
235 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
236 | @MainActor
237 | static var bookBody: BookPage {
238 | .init(
239 | title: _typeName(
240 | target: Namespace1.Namespace2.Namespace3.TestableView.self,),
241 | destination: {
242 | VStack {
243 | Text("test")
244 | }
245 | .tint(.accentColor)
246 | }
247 | )
248 | }
249 | }
250 | """
251 | }
252 |
253 | assertMacro {
254 | """
255 | #StorybookPage(
256 | title: "Path1.Path2.Title",
257 | contents: {
258 | VStack {
259 | Text("test")
260 | }
261 | .tint(.accentColor)
262 | }
263 | )
264 | """
265 | } expansion: {
266 | """
267 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
268 | @MainActor
269 | static var bookBody: BookPage {
270 | .init(
271 | title: "Path1.Path2.Title",
272 | destination: {
273 | VStack {
274 | Text("test")
275 | }
276 | .tint(.accentColor)
277 | }
278 | )
279 | }
280 | }
281 | """
282 | }
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/Sources/StorybookMacrosTests/StorybookPreviewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2024 Eureka, Inc.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import MacroTesting
23 | import XCTest
24 | import StorybookMacrosPlugin
25 |
26 | final class StorybookPreviewTests: XCTestCase {
27 |
28 | override func invokeTest() {
29 | withMacroTesting(
30 | isRecording: false,
31 | macros: ["StorybookPreview": StorybookPreviewMacro.self]
32 | ) {
33 | super.invokeTest()
34 | }
35 | }
36 |
37 | func testUnlabeledTitleUnlabeledContent() {
38 | assertMacro {
39 | """
40 | #Preview("Some title") {
41 | #StorybookPreview {
42 | VStack {
43 | Text("test")
44 | }
45 | .tint(.accentColor)
46 | }
47 | }
48 | """
49 | } expansion: {
50 | """
51 | #Preview("Some title") {
52 | {
53 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
54 | @MainActor
55 | static var bookBody: BookPage {
56 | .init(
57 | title: _typeName(UIView.self),
58 | destination: {
59 | VStack {
60 | Text("test")
61 | }
62 | .tint(.accentColor)
63 | }
64 | )
65 | }
66 | }
67 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
68 | }()
69 | }
70 | """
71 | }
72 |
73 | assertMacro {
74 | """
75 | #Preview {
76 | #StorybookPreview(title: "Path1.Path2.Title") {
77 | VStack {
78 | Text("test")
79 | }
80 | .tint(.accentColor)
81 | }
82 | }
83 | """
84 | } expansion: {
85 | """
86 | #Preview {
87 | {
88 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
89 | @MainActor
90 | static var bookBody: BookPage {
91 | .init(
92 | title: "Path1.Path2.Title",
93 | destination: {
94 | VStack {
95 | Text("test")
96 | }
97 | .tint(.accentColor)
98 | }
99 | )
100 | }
101 | }
102 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
103 | }()
104 | }
105 | """
106 | }
107 | }
108 |
109 | func testLabeledTitleUnlabeledContent() {
110 | assertMacro {
111 | """
112 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
113 | #Preview {
114 | #StorybookPreview(target: Namespace1.Namespace2.Namespace3.TestableView.self) {
115 | VStack {
116 | Text("test")
117 | }
118 | .tint(.accentColor)
119 | }
120 | }
121 | """
122 | } expansion: {
123 | """
124 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
125 | #Preview {
126 | {
127 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
128 | @MainActor
129 | static var bookBody: BookPage {
130 | .init(
131 | title: _typeName(target: Namespace1.Namespace2.Namespace3.TestableView.self),
132 | destination: {
133 | VStack {
134 | Text("test")
135 | }
136 | .tint(.accentColor)
137 | }
138 | )
139 | }
140 | }
141 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
142 | }()
143 | }
144 | """
145 | }
146 |
147 | assertMacro {
148 | """
149 | #Preview {
150 | #StorybookPreview(title: "Path1.Path2.Title") {
151 | VStack {
152 | Text("test")
153 | }
154 | .tint(.accentColor)
155 | }
156 | }
157 | """
158 | } expansion: {
159 | """
160 | #Preview {
161 | {
162 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
163 | @MainActor
164 | static var bookBody: BookPage {
165 | .init(
166 | title: "Path1.Path2.Title",
167 | destination: {
168 | VStack {
169 | Text("test")
170 | }
171 | .tint(.accentColor)
172 | }
173 | )
174 | }
175 | }
176 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
177 | }()
178 | }
179 | """
180 | }
181 | }
182 |
183 | func testUnlabeledTitleLabeledContent() {
184 | assertMacro {
185 | """
186 | #Preview {
187 | #StorybookPreview(
188 | contents: {
189 | VStack {
190 | Text("test")
191 | }
192 | .tint(.accentColor)
193 | }
194 | )
195 | }
196 | """
197 | } expansion: {
198 | """
199 | #Preview {
200 | {
201 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
202 | @MainActor
203 | static var bookBody: BookPage {
204 | .init(
205 | title: _typeName(UIView.self),
206 | destination: {
207 | VStack {
208 | Text("test")
209 | }
210 | .tint(.accentColor)
211 | }
212 | )
213 | }
214 | }
215 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
216 | }()
217 | }
218 | """
219 | }
220 |
221 | assertMacro {
222 | """
223 | #Preview {
224 | #StorybookPreview(
225 | title: "Path1.Path2.Title",
226 | contents: {
227 | VStack {
228 | Text("test")
229 | }
230 | .tint(.accentColor)
231 | }
232 | )
233 | }
234 | """
235 | } expansion: {
236 | """
237 | #Preview {
238 | {
239 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
240 | @MainActor
241 | static var bookBody: BookPage {
242 | .init(
243 | title: "Path1.Path2.Title",
244 | destination: {
245 | VStack {
246 | Text("test")
247 | }
248 | .tint(.accentColor)
249 | }
250 | )
251 | }
252 | }
253 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
254 | }()
255 | }
256 | """
257 | }
258 | }
259 |
260 | func testLabeledTitleLabeledContent() {
261 | assertMacro {
262 | """
263 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
264 | #Preview {
265 | #StorybookPreview(
266 | target: Namespace1.Namespace2.Namespace3.TestableView.self,
267 | contents: {
268 | VStack {
269 | Text("test")
270 | }
271 | .tint(.accentColor)
272 | }
273 | )
274 | }
275 | """
276 | } expansion: {
277 | """
278 | enum Namespace1 { enum Namespace2 { enum Namespace3 { class TestableView: UIView {} } } }
279 | #Preview {
280 | {
281 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
282 | @MainActor
283 | static var bookBody: BookPage {
284 | .init(
285 | title: _typeName(
286 | target: Namespace1.Namespace2.Namespace3.TestableView.self,),
287 | destination: {
288 | VStack {
289 | Text("test")
290 | }
291 | .tint(.accentColor)
292 | }
293 | )
294 | }
295 | }
296 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
297 | }()
298 | }
299 | """
300 | }
301 |
302 | assertMacro {
303 | """
304 | #Preview {
305 | #StorybookPreview(
306 | title: "Path1.Path2.Title",
307 | contents: {
308 | VStack {
309 | Text("test")
310 | }
311 | .tint(.accentColor)
312 | }
313 | )
314 | }
315 | """
316 | } expansion: {
317 | """
318 | #Preview {
319 | {
320 | enum __macro_local_20__🤖🛠️_StorybookMagic_fMu_: BookProvider {
321 | @MainActor
322 | static var bookBody: BookPage {
323 | .init(
324 | title: "Path1.Path2.Title",
325 | destination: {
326 | VStack {
327 | Text("test")
328 | }
329 | .tint(.accentColor)
330 | }
331 | )
332 | }
333 | }
334 | return __macro_local_20__🤖🛠️_StorybookMagic_fMu_.bookBody.destination
335 | }()
336 | }
337 | """
338 | }
339 | }
340 | }
341 |
--------------------------------------------------------------------------------