Module:InfoboxNew

Parts of this Module have been copied from Module:InfoboxNeue of the Star Citizen wiki.

This module is used by Lua modules to build infobox.

Components

5:7 crop of the Marathon 2026 Steam library capsule art. Full box art is File:Marathon2026Info.jpg, Previous version is File:Marathon_2025_Infobox_Crop.png
Indicator
Title
Subtitle
Item label
Item data
Item label
Item data
Item label
Item data
Section title
Item label
Item data
Item label
Item data
Item label
Item data

Image

infobox:renderImage( 'Marathon_2026_Infobox_Crop.png' )

Indicator

Parameter Description Type Status
data Data of the indicator string required
desc Description of the indicator string optional
class HTML classes to be added to the indicator string optional
infobox:renderIndicator( {
	data = 'Indicator'
} )

Header

Parameter Description Type Status
title Title of the infobox string required
subtitle Subtitle of the infobox string optional
infobox:renderHeader( {
	title = 'Title',
	subtitle = 'Subtitle'
} )

Message

This is a shortcut way to create a message wrapped in a section.

Parameter Description Type Status
title Title of the message string required
desc Description of the message string optional
infobox:renderMessage( {
	title = 'Message title',
	desc = 'Message description'
} )

Item

Parameter Description Type Status
data Data of the item string required
label Label of the item string optional
desc Description of the item string optional
row Whether to display the item in a row boolean optional
spacebetween Whether to put space between elements in the item boolean optional
colspan Number of columns that the item spans int optional
infobox:renderItem( {
	label = 'Item label',
	data = 'Item data'
} )

Section

This is used to wrap items into a section.

Parameter Description Type Status
content Content of the section string required
title Title of the section string optional
subtitle Subtitle of the section string optional
col Number of columns in the section int optional
class HTML classes to be added to the section string optional
contentClass HTML classes to be added to the sectionContent string optional
infobox:renderSection( {
	title = 'Section title',
	content = table.concat( sectionTable ),
	col = 3
} )

Layout

Row

Row layout
This is an example of the row layout.
Bacon
Good
Pancetta
Great
Prosciutto
Wonderful
-- Create items
sectionTable = {
	infobox:renderItem( {
		label = 'Bacon',
		data = 'Good',
		row = true,
		spacebetween = true
	} ),
	infobox:renderItem( {
		label = 'Pancetta',
		data = 'Great',
		row = true,
		spacebetween = true
	} ),
	infobox:renderItem( {
		label = 'Prosciutto',
		data = 'Wonderful',
		row = true,
		spacebetween = true
	} )
}

-- Create section with items
infobox:renderSection( {
	title = 'Row layout',
	subtitle = 'This is an example of the row layout.',
	content = table.concat( sectionTable )
} )

List

List layout
This is an example of the list layout.
Bacon is good
Bacon ipsum dolor amet burgdoggen boudin spare ribs pork pork chop drumstick beef. Jowl turkey pork, kevin shankle shank shoulder.
Pancetta is great
Kevin pig fatback, alcatra pancetta sirloin venison tri-tip shankle kielbasa meatloaf spare ribs beef. Corned beef salami kielbasa tenderloin swine spare ribs andouille.
Prosciutto is wonderful
Venison chicken meatloaf, ground round swine short ribs shankle short loin tenderloin jerky capicola. Prosciutto venison sirloin beef brisket pancetta.
-- Create items
sectionTable = {
	infobox:renderItem( {
		data = 'Bacon is good',
		desc = 'Bacon ipsum dolor amet burgdoggen boudin spare ribs pork pork chop drumstick beef. Jowl turkey pork, kevin shankle shank shoulder. ',
	} ),
	infobox:renderItem( {
		data = 'Pancetta is great',
		desc = 'Kevin pig fatback, alcatra pancetta sirloin venison tri-tip shankle kielbasa meatloaf spare ribs beef. Corned beef salami kielbasa tenderloin swine spare ribs andouille.',
	} ),
	infobox:renderItem( {
		data = 'Prosciutto is wonderful',
		desc = 'Venison chicken meatloaf, ground round swine short ribs shankle short loin tenderloin jerky capicola. Prosciutto venison sirloin beef brisket pancetta.',
	} )
}

-- Create section with items
infobox:renderSection( {
	title = 'List layout',
	subtitle = 'This is an example of the list layout.',
	content = table.concat( sectionTable )
} )

Grid

2 col grid layout
This is an example of the two column grid layout.
Bacon
Good
Pancetta
Great
Prosciutto
Wonderful
Capicola
Delightful
3 col grid layout
This is an example of the three column grid layout.
Bacon
Good
Pancetta
Great
Prosciutto
Wonderful
Capicola
Delightful
4 col grid layout
This is an example of the four column grid layout.
Bacon
Good
Pancetta
Great
Prosciutto
Wonderful
Capicola
Delightful
-- Create items
sectionTable = {
	infobox:renderItem( {
		label = 'Bacon',
		data = 'Good'
	} ),
	infobox:renderItem( {
		label = 'Pancetta',
		data = 'Great'
	} ),
	infobox:renderItem( {
		label = 'Prosciutto',
		data = 'Wonderful'
	} ),
	infobox:renderItem( {
		label = 'Capicola',
		data = 'Delightful'
	} )
}

-- Create section with items
infobox:renderSection( {
	title = '2 col grid layout',
	subtitle = 'This is an example of the two column grid layout.',
	content = table.concat( sectionTable ),
	col = 2
} )

infobox:renderSection( {
	title = '3 col grid layout',
	subtitle = 'This is an example of the three column grid layout.',
	content = table.concat( sectionTable ),
	col = 3
} )

infobox:renderSection( {
	title = '4 col grid layout',
	subtitle = 'This is an example of the four column grid layout.',
	content = table.concat( sectionTable ),
	col = 4
} )

require( 'strict' )

local InfoboxNew = {}

local metatable = {}
local methodtable = {}

local libraryUtil = require( 'libraryUtil' )
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti

metatable.__index = methodtable

metatable.__tostring = function ( self )
    return tostring( self:renderInfobox() )
end


--- Helper function to restore underscore from space
--- so that it does not screw up the external link wikitext syntax
--- For some reason SMW property converts underscore into space
--- mw.uri.encode can't be used on full URL
local function restoreUnderscore( s )
    return s:gsub( ' ', '%%5F' )
end

--- Helper function to format string to number with separators
--- It is usually use to re-format raw number from SMW into more readable format
local function formatNumber( s )
    local lang = mw.getContentLanguage()
    if s == nil then
        return
    end

    if type( s ) ~= 'number' then
        s = tonumber( s )
    end

    if type( s ) == 'number' then
        return lang:formatNum( s )
    end

    return s
end

-- TODO: Perhaps we should turn this into another module
local function getDetailsHTML( data, frame )
    local details = frame:extensionTag {
        name = 'div',
        content = data.details.content,
        args = {
            class = data.details.class,
            open = true
        }
    }
    return details
end


--- Put table values into a comma-separated list
---
--- @param data table
--- @return string
function methodtable.tableToCommaList( data )
    if type( data ) == 'table' then
        return table.concat( data, ', ' )
    else
        return data
    end
end

--- Show range if value1 and value2 are different
---
--- @param s1 string|nil
--- @param s2 string|nil
--- @return string|nil
function methodtable.formatRange( s1, s2, formatNum )
    if s1 == nil and s2 == nil then
        return
    end

    formatNum = formatNum or false

    if formatNum then
        if s1 then
            s1 = formatNumber( s1 )
        end
        if s2 then
            s2 = formatNumber( s2 )
        end
    end

    if s1 and s2 and s1 ~= s2 then
        return s1 .. ' – ' .. s2
    end

    return s1 or s2
end

--- Append unit to the value if exists
---
--- @param s string
--- @param unit string
--- @return string|nil
function methodtable.addUnitIfExists( s, unit )
    if s == nil then
        return
    end

    return s .. ' ' .. unit
end

--- Shortcut to return the HTML of the infobox message component as string
---
--- @param data table {title, desc)
--- @return string html
function methodtable.renderMessage( self, data, noInsert )
    checkType( 'Module:InfoboxNew.renderMessage', 1, self, 'table' )
    checkType( 'Module:InfoboxNew.renderMessage', 2, data, 'table' )
    checkType( 'Module:InfoboxNew.renderMessage', 3, noInsert, 'boolean', true )

    noInsert = noInsert or false

    local item = self:renderSection( { content = self:renderItem( { data = data.title, desc = data.desc } ) }, noInsert )

    if not noInsert then
        table.insert( self.entries, item )
    end

    return item
end

--- Return the HTML of the infobox image component as string
---
--- @param filename string
--- @return string html
function methodtable.renderImage( self, filename )
    checkType( 'Module:InfoboxNew.renderImage', 1, self, 'table' )

    local hasPlaceholderImage = false

    if type( filename ) ~= 'string' and self.config.displayPlaceholder == true then
        hasPlaceholderImage = true
        filename = self.config.placeholderImage
        -- Add tracking category for infoboxes using placeholder image
        --table.insert( self.categories,
        --        string.format( '[[Category:%s]]', '' )
        --)
    end

    if type( filename ) ~= 'string' then
        return ''
    end

    local parts = mw.text.split( filename, ':', true )
    if #parts > 1 then
        table.remove( parts, 1 )
        filename = table.concat( parts, ':' )
    end

    local html = mw.html.create( 'div' )
                   :addClass( 'infobox__image' )
                   :wikitext( string.format( '[[File:%s|400px]]', filename ) )

    if hasPlaceholderImage == true then
        local icon = mw.html.create( 'span' ):addClass( 'mw-ui-icon-wikimedia-upload' )
        -- TODO: Point the Upload link to a specific file name
        html:tag( 'div' ):addClass( 'infobox__image-upload' )
            :wikitext( string.format( '[[%s|%s]]', 'Special:UploadWizard',
                tostring( icon ) .. 'Upload Image' ) )
    end

    local item = tostring( html )

    table.insert( self.entries, item )

    return item
end

--- Return the HTML of the infobox indicator component as string
---
--- @param data table {data, class, color, nopadding)
--- @return string html
function methodtable.renderIndicator( self, data )
    checkType( 'Module:InfoboxNew.renderIndicator', 1, self, 'table' )
    checkType( 'Module:InfoboxNew.renderIndicator', 2, data, 'table' )

    if data == nil or data['data'] == nil or data['data'] == '' then return '' end

    local html = mw.html.create( 'div' ):addClass( 'infobox__indicators' )

    local htmlClasses = {
        'infobox__indicator'
    }

    if data['class'] then
        table.insert( htmlClasses, data['class'] )
    end

    if data['color'] then
        table.insert( htmlClasses, 'infobox__indicator--' .. data['color'] )
    end

    if data['nopadding'] == true then
        table.insert( htmlClasses, 'infobox__indicator--nopadding' )
    end

    html:wikitext(
            self:renderItem(
                    {
                        ['data'] = data['data'],
                        ['class'] = table.concat( htmlClasses, ' ' ),
                        ['tooltip'] = data.tooltip,
                        row = true,
                        spacebetween = true
                    }
            )
    )

    local item = tostring( html )

    table.insert( self.entries, item )

    return item
end

--- Return the HTML of the infobox header component as string
---
--- @param data table {title, subtitle, badge)
--- @return string html
function methodtable.renderHeader( self, data )
    checkType( 'Module:InfoboxNew.renderHeader', 1, self, 'table' )
    checkTypeMulti( 'Module:InfoboxNew.renderHeader', 2, data, { 'table', 'string' } )

    if type( data ) == 'string' then
        data = {
            title = data
        }
    end

    if data == nil or data['title'] == nil then return '' end

    local html = mw.html.create( 'div' ):addClass( 'infobox__header' )

    if data['badge'] then
        html:tag( 'div' )
            :addClass( 'infobox__item infobox__badge' )
            :wikitext( data['badge'] )
    end

    local titleItem = mw.html.create( 'div' ):addClass( 'infobox__item' )

    titleItem:tag( 'div' )
             :addClass( 'infobox__title' )
             :wikitext( data['title'] )

    if data['subtitle'] then
        titleItem:tag( 'div' )
        -- Subtitle is always data
                 :addClass( 'infobox__subtitle infobox__data' )
                 :wikitext( data['subtitle'] )
    end

    html:node( titleItem )

    local item = tostring( html )

    table.insert( self.entries, item )

    return item
end

--- Wrap the HTML into an infobox section
---
--- @param data table {title, subtitle, content, border, col, class, contentClass}
--- @param noInsert boolean whether to insert this section into the internal table table
--- @return string html
function methodtable.renderSection( self, data, noInsert )
    checkType( 'Module:InfoboxNew.renderSection', 1, self, 'table' )
    checkType( 'Module:InfoboxNew.renderSection', 2, data, 'table' )
    checkType( 'Module:InfoboxNew.renderSection', 3, noInsert, 'boolean', true )

    noInsert = noInsert or false

    if type( data.content ) == 'table' then
        data.content = table.concat( data.content )
    end

    if data == nil or data['content'] == nil or data['content'] == '' then return '' end

    local html = mw.html.create( 'div' ):addClass( 'infobox__section' )

    if data['title'] then
        local header = html:tag( 'div' ):addClass( 'infobox__sectionHeader' )
        header:tag( 'div' )
              :addClass( 'infobox__sectionTitle' )
              :wikitext( data['title'] )
        if data['subtitle'] then
            header:tag( 'div' )
                  :addClass( 'infobox__sectionSubtitle' )
                  :wikitext( data['subtitle'] )
        end
    end

    local content = html:tag( 'div' )
    content:addClass( 'infobox__sectionContent' )
           :wikitext( data['content'] )

    if data['border'] == false then html:addClass( 'infobox__section--noborder' ) end
    if data['col'] then content:addClass( 'infobox__grid--cols-' .. data['col'] ) end
    if data['class'] then html:addClass( data['class'] ) end
    if data['contentClass'] then content:addClass( data['contentClass'] ) end

    local item = tostring( html )

    if not noInsert then
        table.insert( self.entries, item )
    end

    return item
end

--- Return the HTML of the infobox link button component as string
---
--- @param data table {label, link, page}
--- @return string html
function methodtable.renderLinkButton( self, data )
    checkType( 'Module:InfoboxNew.renderLinkButton', 1, self, 'table' )
    checkType( 'Module:InfoboxNew.renderLinkButton', 2, data, 'table' )

    if data == nil or data['label'] == nil or (data['link'] == nil and data['page'] == nil) then return '' end

    --- Render multiple linkButton when link is a table
    if type( data['link'] ) == 'table' then
        local htmls = {}

        for i, url in ipairs( data['link'] ) do
            table.insert( htmls,
                    self:renderLinkButton( {
                        label = string.format( '%s %d', data['label'], i ),
                        link = url
                    } )
            )
        end

        return table.concat( htmls )
    end

    local html = mw.html.create( 'div' ):addClass( 'infobox__linkButton' )

    if data['link'] then
        html:wikitext( string.format( '[%s %s]', restoreUnderscore( data['link'] ), data['label'] ) )
    elseif data['page'] then
        html:wikitext( string.format( '[[%s|%s]]', data['page'], data['label'] ) )
    end

    return tostring( html )
end

--- Return the HTML of the infobox footer component as string
---
--- @param data table {content, button}
--- @return string html
function methodtable.renderFooter( self, data )
    checkType( 'Module:InfoboxNew.renderFooter', 1, self, 'table' )
    checkType( 'Module:InfoboxNew.renderFooter', 2, data, 'table' )

    if data == nil then return '' end

    -- Checks if an input is of type 'table' or 'string' and if it is not empty
    local function isNonEmpty( input )
        return (type( input ) == 'table' and next( input ) ~= nil) or (type( input ) == 'string' and #input > 0)
    end

    local hasContent = isNonEmpty( data['content'] )
    local hasButton = isNonEmpty( data['button'] ) and isNonEmpty( data['button']['content'] ) and
            isNonEmpty( data['button']['label'] )

    if not hasContent and not hasButton then return '' end

    local html = mw.html.create( 'div' ):addClass( 'infobox__footer' )

    if hasContent then
        local content = data['content']
        if type( content ) == 'table' then content = table.concat( content ) end

        html:addClass( 'infobox__footer--has-content' )
        html:tag( 'div' )
            :addClass( 'infobox__section' )
            :wikitext( content )
    end

    if hasButton then
        html:addClass( 'infobox__footer--has-button' )
        local buttonData = data['button']
        local button = html:tag( 'div' ):addClass( 'infobox__button' )
        local label = button:tag( 'div' ):addClass( 'infobox__buttonLabel' )

        if buttonData['icon'] ~= nil then
            label:wikitext( string.format( '[[File:%s|16px|link=]]%s', buttonData['icon'], buttonData['label'] ) )
        else
            label:wikitext( buttonData['label'] )
        end

        if buttonData['type'] == 'link' then
            button:tag( 'div' )
                  :addClass( 'infobox__buttonLink' )
                  :wikitext( buttonData['content'] )
        elseif buttonData['type'] == 'popup' then
            button:tag( 'div' )
                  :addClass( 'infobox__buttonCard' )
                  :wikitext( buttonData['content'] )
        end
    end

    local item = tostring( html )

    table.insert( self.entries, item )

    return item
end

--- Return the HTML of the infobox footer button component as string
---
--- @param data table {icon, label, type, content}
--- @return string html
function methodtable.renderFooterButton( self, data )
    checkType( 'Module:InfoboxNew.renderFooterButton', 1, self, 'table' )
    checkType( 'Module:InfoboxNew.renderFooterButton', 2, data, 'table' )

    if data == nil then return '' end

    return self:renderFooter( { button = data } )
end

--- Helper function to render icon element within an infobox item
--- @param html table The HTML builder object
--- @param icon string The icon filename
--- @return table The text wrapper element
local function renderItemIconElement( html, icon )
    html:addClass( 'infobox__item--hasIcon' )
    html:tag( 'div' )
        :addClass( 'infobox__icon' )
        :wikitext( string.format( '[[File:%s|16px|link=]]', icon ) )
        :done()
    return html:tag( 'div' ):addClass( 'infobox__text' )
end

--- Helper function to render link elements within an infobox item
--- @param html table The HTML builder object
--- @param data table The data containing link information
local function renderItemLinkElements( html, data )
    if data.link then
        html:addClass( 'infobox__itemButton' )
        html:tag( 'div' )
            :addClass( 'infobox__itemButtonLink' )
            :wikitext( string.format( '[%s]', data.link ) )
            :done()
    elseif data.page then
        html:addClass( 'infobox__itemButton' )
        html:tag( 'div' )
            :addClass( 'infobox__itemButtonLink' )
            :wikitext( string.format( '[[%s]]', data.page ) )
            :done()
    end
end

--- Helper function to render text elements within an infobox item
--- @param wrapper table The HTML wrapper element
--- @param data table The data containing text elements
local function renderItemTextElements( wrapper, data )
    local dataOrder = { 'label', 'data', 'desc' }
    for _, key in ipairs( dataOrder ) do
        if data[key] then
            if type( data[key] ) == 'table' then
                data[key] = table.concat( data[key], ', ' )
            end
            wrapper:tag( 'div' )
                   :addClass( 'infobox__' .. key )
                   :wikitext( data[key] )
        end
    end
end

--- Return the HTML of the infobox item component as string
--- @param data table {label, data, desc, class, tooltip, icon, row, spacebetween, colspan}
--- @param content string|number|nil optional
--- @return string html
function methodtable.renderItem( self, data, content )
    checkType( 'Module:InfoboxNew.renderItem', 1, self, 'table' )
    checkTypeMulti( 'Module:InfoboxNew.renderItem', 2, data, { 'table', 'string' } )
    checkTypeMulti( 'Module:InfoboxNew.renderItem', 3, content, { 'string', 'number', 'nil' } )

    -- Handle simplified parameter format
    if content ~= nil then
        data = {
            label = data,
            data = content
        }
    end

    -- Skip empty items
    if data == nil or data.data == nil or data.data == '' then return '' end
    if self.config.removeEmpty == true and data.data == self.config.emptyString then
        return ''
    end

    -- Create base HTML structure
    local html = mw.html.create( 'div' ):addClass( 'infobox__item' )

    -- Add classes based on configuration
    if data.class then html:addClass( data.class ) end
    if data.row == true then html:addClass( 'infobox__grid--row' ) end
    if data.spacebetween == true then html:addClass( 'infobox__grid--space-between' ) end
    if data.colspan then html:addClass( 'infobox__grid--col-span-' .. data.colspan ) end

    -- Create link elements if needed
    renderItemLinkElements( html, data )

    -- Handle icon and text wrapper
    local textWrapper = data.icon and renderItemIconElement( html, data.icon ) or html

    -- Render text elements
    renderItemTextElements( textWrapper, data )

    -- Add arrow indicator for links
    if data.link or data.page then
        html:tag( 'div' )
            :addClass( 'infobox__itemButtonArrow mw-ui-icon-wikimedia-collapse' )
            :done()
    end

    -- Linter does not like data.range.end
    if data.range then
        html:addClass( 'infobox__item--is-range' )
            :tag( 'div' )
            :addClass( 'infobox__bar' )
            :tag( 'div' )
            :addClass( 'infobox__bar-item' )
            :css( '--infobox-bar-item-range-start', data.range['start'] )
            :css( '--infobox-bar-item-range-end', data.range['end'] )
            :allDone()
    end

    -- Handle tooltip if present
    if data.tooltip then
        self.modules.FloatingUI = self.modules.FloatingUI or require( 'Module:FloatingUI' )
        html:addClass( 'infobox__item--has-tooltip' )
        return self.modules.FloatingUI.render( tostring( html ), data.tooltip )
    end

    return tostring( html )
end

--- Wrap the infobox HTML
---
--- @param innerHtml string inner html of the infobox
--- @param snippetText string text used in snippet in mobile view
--- @return string html infobox html with templatestyles
function methodtable.renderInfobox( self, innerHtml, snippetText )
    checkType( 'Module:InfoboxNew.renderInfobox', 1, self, 'table' )
    checkTypeMulti( 'Module:InfoboxNew.renderInfobox', 2, innerHtml, { 'table', 'string', 'nil' } )
    checkType( 'Module:InfoboxNew.renderInfobox', 3, snippetText, 'string', true )

    innerHtml = innerHtml or self.entries
    if type( innerHtml ) == 'table' then
        innerHtml = table.concat( self.entries )
    end

    local function renderContent()
        local html = mw.html.create( 'div' )
                       :addClass( 'infobox__content' )
                       :wikitext( innerHtml )
        return tostring( html )
    end

    local function renderSnippet()
        if snippetText == nil then snippetText = mw.title.getCurrentTitle().text end

        local html = mw.html.create()

        html:tag( 'div' )
            :addClass( 'mw-ui-icon-wikimedia-collapse' )
            :done()
            :tag( 'div' )
            :addClass( 'infobox__data' )
            :wikitext( string.format( '%s:', 'Quick Facts' ) )
            :done()
            :tag( 'div' )
            :addClass( 'infobox__desc' )
            :wikitext( snippetText )

        return tostring( html )
    end

    local frame = mw.getCurrentFrame()
    local output = getDetailsHTML( {
        details = {
            class = 'infobox floatright',
            content = renderContent()
        },
        summary = {
            class = 'infobox__snippet',
            content = renderSnippet()
        }
    }, frame )

    if self.modules.FloatingUI then
        output = self.modules.FloatingUI.load( frame ) .. output
    end

    return frame:extensionTag {
        name = 'templatestyles', args = { src = "Module:InfoboxNew/styles.css" }
    } .. output .. table.concat( self.categories )
end

--- Just an accessor for the class method
function methodtable.showDescIfDiff( s1, s2 )
    return InfoboxNew.showDescIfDiff( s1, s2 )
end

--- Format text to show comparison as desc text if two strings are different
---
--- @param s1 string|nil base
--- @param s2 string|nil comparsion
--- @return string|nil html
function InfoboxNew.showDescIfDiff( s1, s2 )
    if s1 == nil or s2 == nil or s1 == s2 then return s1 end
    return string.format( '%s <span class="infobox__desc">(%s)</span>', s1, s2 )
end

--- New Instance
---
--- @return table InfoboxNew
function InfoboxNew.new( self, config )
    local baseConfig = {
        -- Flag to discard empty rows
        removeEmpty = false,
        -- Optional string which is valued as empty
        emptyString = nil,
        -- Display a placeholder image if addImage does not find an image
        displayPlaceholder = true,
        -- Placeholder Image
        placeholderImage = 'Platzhalter.webp',
    }

    for k, v in pairs( config or {} ) do
        baseConfig[k] = v
    end

    local instance = {
        categories = {},
        config = baseConfig,
        entries = {},
        modules = {
            FloatingUI = nil
        }
    }

    setmetatable( instance, metatable )

    return instance
end

--- Create an Infobox from args
---
--- @param frame table
--- @return string
function InfoboxNew.fromArgs( frame )
    local instance = InfoboxNew:new()
    local args = require( 'Module:Arguments' ).getArgs( frame )

    local sections = {
        { content = {}, col = args['col'] or 2 }
    }

    local sectionMap = { default = 1 }

    local currentSection

    if args['image'] then
        instance:renderImage( args['image'] )
    end

    if args['indicator'] then
        instance:renderIndicator( {
            data = args['indicator'],
            class = args['indicatorClass']
        } )
    end

    if args['title'] then
        instance:renderHeader( {
            title = args['title'],
            subtitle = args['subtitle'],
        } )
    end

    for i = 1, 50, 1 do
        if args['section' .. i] then
            currentSection = args['section' .. i]

            table.insert( sections, {
                title = currentSection,
                subtitle = args['section-subtitle' .. i],
                col = args['section-col' .. i] or args['col'] or 2,
                content = {}
            } )

            sectionMap[currentSection] = #sections
        end

        if args['label' .. i] and args['content' .. i] then
            table.insert( sections[sectionMap[(currentSection or 'default')]].content,
                    instance:renderItem( args['label' .. i], args['content' .. i] ) )
        end
    end

    for _, section in ipairs( sections ) do
        instance:renderSection( {
            title = section.title,
            subtitle = section.subtitle,
            col = section.col,
            content = section.content,
        } )
    end

    return instance:renderInfobox( nil, args['snippet'] )
end

return InfoboxNew