Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 127 additions & 2 deletions docs/source/syntax/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ title: Tabs

Tabbed content is created using the `tab-set` directive with individual `tab-item` blocks for each tab's content. You can embed other directives, like admonitions directly in tabs.

## Syntax
## Tabs Simple

### Example

#### Syntax

```markdown
::::{tab-set}
Expand All @@ -20,6 +24,8 @@ This is where the content for tab #2 goes.
::::
```

#### Example

::::{tab-set}

:::{tab-item} Tab #1 title
Expand All @@ -32,6 +38,124 @@ This is where the content for tab #2 goes.

::::

---

## Tab Groups

Tabs can be grouped together by setting the `group` attribute to the same value for each `tab-set`.
This allows for multiple sets of tabs to be controlled together.

You need to set both the `group` and `sync` attributes to the same value for each `tab-item` to sync the tabs.

This means tabs with the same group and the same sync value will be selected together.

### Example

In the following example we have three tab sets, but only the first two are grouped together.
Hence, the first two tab sets will be in sync, but the third tab set will not be in sync with the first two.

#### Syntax
```markdown
::::{tab-set}
:group: languages // This is the group name
:::{tab-item} Java
:sync: java // This is the sync name
Content for Java tab
:::

:::{tab-item} Golang
:sync: golang
Content for Golang tab
:::

:::{tab-item} C#
:sync: csharp
Content for C# tab
:::

::::

::::{tab-set}
:group: languages
:::{tab-item} Java
:sync: java
Content for Java tab
:::

:::{tab-item} Golang
:sync: golang
Content for Golang tab
:::

:::{tab-item} C#
:sync: csharp
Content for C# tab
:::

::::
```

#### Result

##### Grouped Tabs

::::{tab-set}
:group: languages
:::{tab-item} Java
:sync: java
Content for Java tab
:::

:::{tab-item} Golang
:sync: golang
Content for Golang tab
:::

:::{tab-item} C#
:sync: csharp
Content for C# tab
:::

::::

::::{tab-set}
:group: languages
:::{tab-item} Java
:sync: java
Other content for Java tab
:::

:::{tab-item} Golang
:sync: golang
Other content for Golang tab
:::

:::{tab-item} C#
:sync: csharp
Other content for C# tab
:::
::::

##### Ungrouped Tabs

::::{tab-set}
:::{tab-item} Java
:sync: java
Other content for Java tab that is not in the same group
:::

:::{tab-item} Golang
:sync: golang
Other content for Golang tab that is not in the same group
:::

:::{tab-item} C#
:sync: csharp
Other content for Golang tab that is not in the same group
:::

::::

## Asciidoc syntax

`````asciidoc
Expand Down Expand Up @@ -79,4 +203,5 @@ This is where the content for tab #1 goes.
This is where the content for tab #2 goes.
// end::reg-config[]
----
```
```
`````
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,9 @@ private void WriteTabItem(HtmlRenderer renderer, TabItemBlock block)
{
Index = block.Index,
Title = block.Title,
TabSetIndex = block.TabSetIndex
TabSetIndex = block.TabSetIndex,
SyncKey = block.SyncKey,
TabSetGroupKey = block.TabSetGroupKey
});
RenderRazorSlice(slice, renderer, block);
}
Expand Down
11 changes: 9 additions & 2 deletions src/Elastic.Markdown/Myst/Directives/TabSetBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using Elastic.Markdown.Diagnostics;
using Elastic.Markdown.Slices.Directives;

namespace Elastic.Markdown.Myst.Directives;

Expand All @@ -12,6 +13,8 @@ public class TabSetBlock(DirectiveBlockParser parser, ParserContext context)
public override string Directive => "tab-set";

public int Index { get; set; }
public string? GetGroupKey() => Prop("group");

public override void FinalizeAndValidate(ParserContext context) => Index = FindIndex();

private int _index = -1;
Expand All @@ -32,7 +35,7 @@ public class TabItemBlock(DirectiveBlockParser parser, ParserContext context)
public string Title { get; private set; } = default!;
public int Index { get; private set; }
public int TabSetIndex { get; private set; }

public string? TabSetGroupKey { get; private set; }
public string? SyncKey { get; private set; }
public bool Selected { get; private set; }

Expand All @@ -43,7 +46,11 @@ public override void FinalizeAndValidate(ParserContext context)

Title = Arguments ?? "{undefined}";
Index = Parent!.IndexOf(this);
TabSetIndex = Parent is TabSetBlock tb ? tb.FindIndex() : -1;

var tabSet = Parent as TabSetBlock;

TabSetIndex = tabSet?.FindIndex() ?? -1;
TabSetGroupKey = tabSet?.GetGroupKey();

SyncKey = Prop("sync");
Selected = PropBool("selected");
Expand Down
4 changes: 2 additions & 2 deletions src/Elastic.Markdown/Slices/Directives/TabItem.cshtml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@inherits RazorSlice<TabItemViewModel>
<input checked="@(Model.Index == 0 ? "checked" : "")" id="sd-tab-item-@Model.Index" name="sd-tab-set-@Model.TabSetIndex" type="radio">
<label class="sd-tab-label" for="sd-tab-item-@Model.Index">@Model.Title</label>
<input checked="@(Model.Index == 0 ? "checked" : "")" id="sd-tab-item-@Model.TabSetIndex-@Model.Index" name="sd-tab-set-@Model.TabSetIndex" type="radio">
<label class="sd-tab-label" data-sync-id="@Model.SyncKey" data-sync-group="@Model.TabSetGroupKey" for="sd-tab-item-@Model.TabSetIndex-@Model.Index">@Model.Title</label>
<div class="sd-tab-content docutils">
[CONTENT]
</div>
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Slices/Directives/TabSet.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@inherits RazorSlice<TabSetViewModel>
<div class="sd-tab-set docutils">
[CONTENT]
</div>
</div>
3 changes: 2 additions & 1 deletion src/Elastic.Markdown/Slices/Directives/_ViewModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ public class TabItemViewModel
public required int Index { get; init; }
public required int TabSetIndex { get; init; }
public required string Title { get; init; }
public required string? SyncKey { get; init; }
public required string? TabSetGroupKey { get; init; }
}

public class IncludeViewModel
{
public required string Html { get; init; }
Expand Down
84 changes: 84 additions & 0 deletions tests/Elastic.Markdown.Tests/Directives/TabTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information
using Elastic.Markdown.Myst.Directives;
using FluentAssertions;
using Markdig;
using Xunit.Abstractions;

namespace Elastic.Markdown.Tests.Directives;
Expand Down Expand Up @@ -92,3 +93,86 @@ public void ParsesMultipleTabSets()
}
}
}

public class GroupTabTests(ITestOutputHelper output) : DirectiveTest<TabSetBlock>(output,
"""
::::{tab-set}
:group: languages
:::{tab-item} Java
:sync: java
Content for Java tab
:::

:::{tab-item} Golang
:sync: golang
Content for Golang tab
:::

:::{tab-item} C#
:sync: csharp
Content for C# tab
:::

::::

::::{tab-set}
:group: languages
:::{tab-item} Java
:sync: java
Content for Java tab
:::

:::{tab-item} Golang
:sync: golang
Content for Golang tab
:::

:::{tab-item} C#
:sync: csharp
Content for C# tab
:::

::::
"""
)
{
[Fact]
public void ParsesMultipleTabSets()
{
var sets = Document.OfType<TabSetBlock>().ToArray();
sets.Length.Should().Be(2);
for (var s = 0; s < sets.Length; s++)
{
var items = sets[s].OfType<TabItemBlock>().ToArray();
items.Should().NotBeNull().And.HaveCount(3);
for (var i = 0; i < items.Length; i++)
{
items[i].Index.Should().Be(i);
items[i].TabSetIndex.Should().Be(s);
}
}
}

[Fact]
public void ParsesGroup()
{
var sets = Document.OfType<TabSetBlock>().ToArray();
sets.Length.Should().Be(2);

foreach (var t in sets)
{
t.GetGroupKey().Should().Be("languages");
}
}

[Fact]
public void ParsesSyncKey()
{
var set = Document.OfType<TabSetBlock>().First();
var items = set.OfType<TabItemBlock>().ToArray();
items.Should().HaveCount(3);
items[0].SyncKey.Should().Be("java");
items[1].SyncKey.Should().Be("golang");
items[2].SyncKey.Should().Be("csharp");
}
}
Loading