Add `TextEditor` implementation (#329)

* Add `TextEditor` implementation

Resolves #173.

* Clean up and bump requirements in the demo project

* Use a single `_tokamak-formcontrol` CSS class

* Add missing CSS class to `TextEditor.swift`

Co-authored-by: Jed Fox <git@jedfox.com>

Co-authored-by: Jed Fox <git@jedfox.com>
This commit is contained in:
Max Desiatov 2020-12-07 21:13:24 +00:00 committed by GitHub
parent 302cd3b108
commit 99581929a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 133 additions and 38 deletions

View File

@ -43,6 +43,8 @@
B5DBA22C24D509B4003D3347 /* RedactDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBA22A24D509B4003D3347 /* RedactDemo.swift */; }; B5DBA22C24D509B4003D3347 /* RedactDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBA22A24D509B4003D3347 /* RedactDemo.swift */; };
B5F2BE032571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F2BE022571443D00FB3653 /* PreferenceKeyDemo.swift */; }; B5F2BE032571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F2BE022571443D00FB3653 /* PreferenceKeyDemo.swift */; };
B5F2BE042571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F2BE022571443D00FB3653 /* PreferenceKeyDemo.swift */; }; B5F2BE042571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F2BE022571443D00FB3653 /* PreferenceKeyDemo.swift */; };
D120FDDB257E7145008FFBAD /* TextEditorDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D120FDDA257E7145008FFBAD /* TextEditorDemo.swift */; };
D120FDDC257E7145008FFBAD /* TextEditorDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D120FDDA257E7145008FFBAD /* TextEditorDemo.swift */; };
D1B4229024B3B9BB00682F74 /* ListDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228E24B3B9BB00682F74 /* ListDemo.swift */; }; D1B4229024B3B9BB00682F74 /* ListDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228E24B3B9BB00682F74 /* ListDemo.swift */; };
D1B4229124B3B9BB00682F74 /* ListDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228E24B3B9BB00682F74 /* ListDemo.swift */; }; D1B4229124B3B9BB00682F74 /* ListDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228E24B3B9BB00682F74 /* ListDemo.swift */; };
D1B4229224B3B9BB00682F74 /* OutlineGroupDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */; }; D1B4229224B3B9BB00682F74 /* OutlineGroupDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */; };
@ -113,6 +115,7 @@
B5C76E4924C73ED4003EABB2 /* AppStorageDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorageDemo.swift; sourceTree = "<group>"; }; B5C76E4924C73ED4003EABB2 /* AppStorageDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorageDemo.swift; sourceTree = "<group>"; };
B5DBA22A24D509B4003D3347 /* RedactDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedactDemo.swift; sourceTree = "<group>"; }; B5DBA22A24D509B4003D3347 /* RedactDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedactDemo.swift; sourceTree = "<group>"; };
B5F2BE022571443D00FB3653 /* PreferenceKeyDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceKeyDemo.swift; sourceTree = "<group>"; }; B5F2BE022571443D00FB3653 /* PreferenceKeyDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceKeyDemo.swift; sourceTree = "<group>"; };
D120FDDA257E7145008FFBAD /* TextEditorDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEditorDemo.swift; sourceTree = "<group>"; };
D1B4228E24B3B9BB00682F74 /* ListDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListDemo.swift; sourceTree = "<group>"; }; D1B4228E24B3B9BB00682F74 /* ListDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListDemo.swift; sourceTree = "<group>"; };
D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineGroupDemo.swift; sourceTree = "<group>"; }; D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineGroupDemo.swift; sourceTree = "<group>"; };
D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStyleDemo.swift; sourceTree = "<group>"; }; D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStyleDemo.swift; sourceTree = "<group>"; };
@ -179,6 +182,7 @@
85ED189924AD425E0085DFA0 /* TokamakDemo */ = { 85ED189924AD425E0085DFA0 /* TokamakDemo */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D120FDDA257E7145008FFBAD /* TextEditorDemo.swift */,
D1D6B62224D817350041E1D9 /* GeometryReaderDemo.swift */, D1D6B62224D817350041E1D9 /* GeometryReaderDemo.swift */,
D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */, D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */,
B5C76E4924C73ED4003EABB2 /* AppStorageDemo.swift */, B5C76E4924C73ED4003EABB2 /* AppStorageDemo.swift */,
@ -354,6 +358,7 @@
85ED18A324AD425E0085DFA0 /* SpacerDemo.swift in Sources */, 85ED18A324AD425E0085DFA0 /* SpacerDemo.swift in Sources */,
D1B4229024B3B9BB00682F74 /* ListDemo.swift in Sources */, D1B4229024B3B9BB00682F74 /* ListDemo.swift in Sources */,
D1EE7EA724C0DD2100C0D127 /* PickerDemo.swift in Sources */, D1EE7EA724C0DD2100C0D127 /* PickerDemo.swift in Sources */,
D120FDDB257E7145008FFBAD /* TextEditorDemo.swift in Sources */,
B5F2BE032571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */, B5F2BE032571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */,
8500293F24D2FF3E001A2E84 /* SliderDemo.swift in Sources */, 8500293F24D2FF3E001A2E84 /* SliderDemo.swift in Sources */,
85ED18A924AD425E0085DFA0 /* TokamakDemo.swift in Sources */, 85ED18A924AD425E0085DFA0 /* TokamakDemo.swift in Sources */,
@ -383,6 +388,7 @@
85ED18B024AD425E0085DFA0 /* EnvironmentDemo.swift in Sources */, 85ED18B024AD425E0085DFA0 /* EnvironmentDemo.swift in Sources */,
D1B4229124B3B9BB00682F74 /* ListDemo.swift in Sources */, D1B4229124B3B9BB00682F74 /* ListDemo.swift in Sources */,
D1EE7EA824C0DD2100C0D127 /* PickerDemo.swift in Sources */, D1EE7EA824C0DD2100C0D127 /* PickerDemo.swift in Sources */,
D120FDDC257E7145008FFBAD /* TextEditorDemo.swift in Sources */,
B5F2BE042571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */, B5F2BE042571443D00FB3653 /* PreferenceKeyDemo.swift in Sources */,
8500294024D2FF3E001A2E84 /* SliderDemo.swift in Sources */, 8500294024D2FF3E001A2E84 /* SliderDemo.swift in Sources */,
85ED18B624AD42D70085DFA0 /* NSAppDelegate.swift in Sources */, 85ED18B624AD42D70085DFA0 /* NSAppDelegate.swift in Sources */,
@ -546,7 +552,8 @@
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "iOS Info.plist"; INFOPLIST_FILE = "iOS Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -572,7 +579,8 @@
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "iOS Info.plist"; INFOPLIST_FILE = "iOS Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -604,6 +612,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = "dev.tokamak.Tokamak-Native"; PRODUCT_BUNDLE_IDENTIFIER = "dev.tokamak.Tokamak-Native";
PRODUCT_NAME = "TokamakDemo Native"; PRODUCT_NAME = "TokamakDemo Native";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -630,6 +639,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = "dev.tokamak.Tokamak-Native"; PRODUCT_BUNDLE_IDENTIFIER = "dev.tokamak.Tokamak-Native";
PRODUCT_NAME = "TokamakDemo Native"; PRODUCT_NAME = "TokamakDemo Native";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -107,7 +107,8 @@ app.
## Requirements for app developers ## Requirements for app developers
- macOS 10.15 and Xcode 11.4 or later. - macOS 10.15 and Xcode 11.4 or later. macOS 11.0 and Xcode 12.0 or later are required if you're
building a multi-platform app with Tokamak that also needs to support SwiftUI on macOS.
- [Swift 5.2 or later](https://swift.org/download/) and Ubuntu 18.04 if you'd like to use Linux. - [Swift 5.2 or later](https://swift.org/download/) and Ubuntu 18.04 if you'd like to use Linux.
Other Linux distributions are currently not supported. Other Linux distributions are currently not supported.
@ -204,7 +205,7 @@ doesn't provide an official build of the extension on the VSCode Marketplace unf
### Modular structure ### Modular structure
Tokamak is built with modularity in mind, providing a cross-platform `TokamakCore` module and Tokamak is built with modularity in mind, providing a multi-platform `TokamakCore` module and
separate modules for platform-specific renderers. Currently, the only available renderer modules are separate modules for platform-specific renderers. Currently, the only available renderer modules are
`TokamakDOM` and `TokamakStaticHTML`, the latter can be used for static websites and server-side `TokamakDOM` and `TokamakStaticHTML`, the latter can be used for static websites and server-side
rendering. If you'd like to implement your own custom renderer, please refer to our [renderers rendering. If you'd like to implement your own custom renderer, please refer to our [renderers

View File

@ -0,0 +1,33 @@
// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
public struct TextEditor: View {
let textBinding: Binding<String>
public init(text: Binding<String>) {
textBinding = text
}
public var body: some View {
neverBody("TextEditor")
}
}
public struct _TextEditorProxy {
public let subject: TextEditor
public init(_ subject: TextEditor) { self.subject = subject }
public var textBinding: Binding<String> { subject.textBinding }
}

View File

@ -108,6 +108,7 @@ public typealias SecureField = TokamakCore.SecureField
public typealias Slider = TokamakCore.Slider public typealias Slider = TokamakCore.Slider
public typealias Spacer = TokamakCore.Spacer public typealias Spacer = TokamakCore.Spacer
public typealias Text = TokamakCore.Text public typealias Text = TokamakCore.Text
public typealias TextEditor = TokamakCore.TextEditor
public typealias TextField = TokamakCore.TextField public typealias TextField = TokamakCore.TextField
public typealias Toggle = TokamakCore.Toggle public typealias Toggle = TokamakCore.Toggle
public typealias VStack = TokamakCore.VStack public typealias VStack = TokamakCore.VStack

View File

@ -21,7 +21,7 @@ extension _PickerContainer: ViewDeferredToRenderer {
AnyView(HTML("label") { AnyView(HTML("label") {
label label
Text(" ") Text(" ")
DynamicHTML("select", ["class": "_tokamak-picker"], listeners: ["change": { DynamicHTML("select", ["class": "_tokamak-formcontrol"], listeners: ["change": {
guard guard
let valueString = $0.target.object!.value.string, let valueString = $0.target.object!.value.string,
let value = Int(valueString) as? SelectionValue let value = Int(valueString) as? SelectionValue

View File

@ -24,7 +24,7 @@ extension SecureField: ViewDeferredToRenderer where Label == Text {
"type": "password", "type": "password",
.value: proxy.textBinding.wrappedValue, .value: proxy.textBinding.wrappedValue,
"placeholder": proxy.label.rawText, "placeholder": proxy.label.rawText,
"class": "_tokamak-securefield", "class": "_tokamak-formcontrol",
], listeners: [ ], listeners: [
"keypress": { event in if event.key == "Enter" { proxy.onCommit() } }, "keypress": { event in if event.key == "Enter" { proxy.onCommit() } },
"input": { event in "input": { event in

View File

@ -0,0 +1,31 @@
// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import TokamakCore
extension TextEditor: ViewDeferredToRenderer {
public var deferredBody: AnyView {
let proxy = _TextEditorProxy(self)
return AnyView(DynamicHTML("textarea", [
"class": "_tokamak-formcontrol _tokamak-texteditor",
], listeners: [
"input": { event in
if let newValue = event.target.object?.value.string {
proxy.textBinding.wrappedValue = newValue
}
},
]))
}
}

View File

@ -32,10 +32,8 @@ extension TextField: ViewDeferredToRenderer where Label == Text {
func className(for style: TextFieldStyle) -> String { func className(for style: TextFieldStyle) -> String {
switch style { switch style {
case is DefaultTextFieldStyle: case is DefaultTextFieldStyle, is RoundedBorderTextFieldStyle:
return "_tokamak-textfield-default" return "_tokamak-formcontrol"
case is RoundedBorderTextFieldStyle:
return "_tokamak-textfield-roundedborder"
default: default:
return "" return ""
} }

View File

@ -17,7 +17,6 @@
import TokamakShim import TokamakShim
@available(OSX 11.0, iOS 14.0, *)
struct AppStorageButtons: View { struct AppStorageButtons: View {
@AppStorage("count") var count: Int = 0 @AppStorage("count") var count: Int = 0
@SceneStorage("count") var sceneCount: Int = 0 @SceneStorage("count") var sceneCount: Int = 0
@ -30,7 +29,6 @@ struct AppStorageButtons: View {
} }
} }
@available(OSX 11.0, iOS 14.0, *)
struct AppStorageDemo: View { struct AppStorageDemo: View {
@AppStorage("count") var count: Int = 0 @AppStorage("count") var count: Int = 0
@SceneStorage("count") var sceneCount: Int = 0 @SceneStorage("count") var sceneCount: Int = 0

View File

@ -17,7 +17,6 @@
import TokamakShim import TokamakShim
@available(OSX 10.16, iOS 14.0, *)
public struct GridDemo: View { public struct GridDemo: View {
public var body: some View { public var body: some View {
Group { Group {

View File

@ -23,7 +23,6 @@ struct File: Identifiable {
let children: [File]? let children: [File]?
} }
@available(OSX 10.16, iOS 14.0, *)
struct OutlineGroupDemo: View { struct OutlineGroupDemo: View {
let fs: [File] = [ let fs: [File] = [
.init(id: 0, name: "Users", children: [ .init(id: 0, name: "Users", children: [

View File

@ -24,7 +24,6 @@ struct TestPreferenceKey: PreferenceKey {
} }
} }
@available(macOS 11, iOS 14, *)
struct PreferenceKeyDemo: View { struct PreferenceKeyDemo: View {
@State private var testKeyValue: Color = .yellow @State private var testKeyValue: Color = .yellow
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@ -144,7 +143,6 @@ struct PreferenceKeyDemo: View {
} }
} }
@available(macOS 11, iOS 14, *)
extension PreferenceKeyDemo.SetColor where Content == EmptyView { extension PreferenceKeyDemo.SetColor where Content == EmptyView {
init(_ level: Int, _ color: Color) { init(_ level: Int, _ color: Color) {
self.init(level, color) { EmptyView() } self.init(level, color) { EmptyView() }

View File

@ -17,7 +17,6 @@
import TokamakShim import TokamakShim
@available(OSX 11.0, iOS 14.0, *)
struct RedactionDemo: View { struct RedactionDemo: View {
func title(_ text: String) -> some View { func title(_ text: String) -> some View {
Group { Group {

View File

@ -0,0 +1,25 @@
// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import TokamakShim
struct TextEditorDemo: View {
@State var text = ""
var body: some View {
Text("Word count: \(text.split(separator: " ").count)")
TextEditor(text: $text)
.frame(width: 300, height: 300)
}
}

View File

@ -128,6 +128,7 @@ struct TokamakDemoView: View {
Section(header: Text("Text")) { Section(header: Text("Text")) {
NavItem("Text", destination: TextDemo()) NavItem("Text", destination: TextDemo())
NavItem("TextField", destination: TextFieldDemo()) NavItem("TextField", destination: TextFieldDemo())
NavItem("TextEditor", destination: TextEditorDemo())
} }
Section(header: Text("Misc")) { Section(header: Text("Misc")) {
NavItem("Path", destination: PathDemo()) NavItem("Path", destination: PathDemo())

View File

@ -96,10 +96,7 @@ public let tokamakStyles = """
height: 100%; height: 100%;
} }
._tokamak-securefield, ._tokamak-formcontrol {
._tokamak-textfield-default,
._tokamak-textfield-roundedborder,
._tokamak-picker {
color-scheme: light dark; color-scheme: light dark;
} }
@ -107,6 +104,11 @@ public let tokamakStyles = """
text-decoration: none; text-decoration: none;
} }
._tokamak-texteditor {
width: 100%;
height: 100%;
}
@media (prefers-color-scheme:dark) { @media (prefers-color-scheme:dark) {
._tokamak-text-redacted::after { ._tokamak-text-redacted::after {
background-color: rgb(100, 100, 100); background-color: rgb(100, 100, 100);

View File

@ -19,7 +19,7 @@ Table columns:
| 🚧 | [Text](https://developer.apple.com/documentation/swiftui/text) | | | 🚧 | [Text](https://developer.apple.com/documentation/swiftui/text) | |
| 🚧 | [TextField](https://developer.apple.com/documentation/swiftui/textfield) | | | 🚧 | [TextField](https://developer.apple.com/documentation/swiftui/textfield) | |
| 🚧 | [SecureField](https://developer.apple.com/documentation/swiftui/securefield) | | | 🚧 | [SecureField](https://developer.apple.com/documentation/swiftui/securefield) | |
| | [TextEditor](https://developer.apple.com/documentation/swiftui/texteditor) | | | | [TextEditor](https://developer.apple.com/documentation/swiftui/texteditor) | |
### Images ### Images
@ -51,12 +51,12 @@ Table columns:
### Value Indicators ### Value Indicators
| | | | | | | |
|--- | ------------------------------------------------------------------------------ | :-: | | --- | ------------------------------------------------------------------------------ | :-: |
| | [ProgressView](https://developer.apple.com/documentation/swiftui/progressview) | | | | [ProgressView](https://developer.apple.com/documentation/swiftui/progressview) | |
| | [Gauge](https://developer.apple.com/documentation/swiftui/gauge) | | | | [Gauge](https://developer.apple.com/documentation/swiftui/gauge) | |
| | [Label](https://developer.apple.com/documentation/swiftui/label) | | | | [Label](https://developer.apple.com/documentation/swiftui/label) | |
| ✅ | [Link](https://developer.apple.com/documentation/swiftui/link) | | | ✅ | [Link](https://developer.apple.com/documentation/swiftui/link) | |
## View Layout and Presentation ## View Layout and Presentation
@ -72,11 +72,11 @@ Table columns:
### Grids ### Grids
| | | | | | | |
| --- | --------------------------------------------------------------------- | :-: | | --- | ------------------------------------------------------------------------ | :-: |
|🚧| [LazyHGrid](https://developer.apple.com/documentation/swiftui/lazyhgrid) | | | 🚧 | [LazyHGrid](https://developer.apple.com/documentation/swiftui/lazyhgrid) | |
|🚧| [LazyVGrid](https://developer.apple.com/documentation/swiftui/lazyvgrid) | | | 🚧 | [LazyVGrid](https://developer.apple.com/documentation/swiftui/lazyvgrid) | |
|🚧| [GridItem](https://developer.apple.com/documentation/swiftui/griditem) | | | 🚧 | [GridItem](https://developer.apple.com/documentation/swiftui/griditem) | |
### Lists and Scroll Views ### Lists and Scroll Views
@ -96,14 +96,14 @@ Table columns:
| | [Form](https://developer.apple.com/documentation/swiftui/form) | | | | [Form](https://developer.apple.com/documentation/swiftui/form) | |
| ✅ | [Group](https://developer.apple.com/documentation/swiftui/group) | | | ✅ | [Group](https://developer.apple.com/documentation/swiftui/group) | |
| | [GroupBox](https://developer.apple.com/documentation/swiftui/groupbox) | | | | [GroupBox](https://developer.apple.com/documentation/swiftui/groupbox) | |
| 🚧 | [Section](https://developer.apple.com/documentation/swiftui/section) | | | 🚧 | [Section](https://developer.apple.com/documentation/swiftui/section) | |
### Hierarchical Views ### Hierarchical Views
| | | | | | | |
| --- | --------------------------------------------------------------------------------- | :-: | | --- | ------------------------------------------------------------------------------------ | :-: |
|🚧| [OutlineGroup](https://developer.apple.com/documentation/swiftui/outlinegroup) | | | 🚧 | [OutlineGroup](https://developer.apple.com/documentation/swiftui/outlinegroup) | |
|🚧| [DisclosureGroup](https://developer.apple.com/documentation/swiftui/disclosuregroup) | | | 🚧 | [DisclosureGroup](https://developer.apple.com/documentation/swiftui/disclosuregroup) | |
### Spacers and Dividers ### Spacers and Dividers