$customOptions button options: variant, size, iconLeft, iconRight * @param array $customAttributes Additional attributes */ function button( string $label = '', string $uri = '', array $customOptions = [], array $customAttributes = [] ): string { $defaultOptions = [ 'variant' => 'default', 'size' => 'base', 'iconLeft' => null, 'iconRight' => null, 'isSquared' => false, ]; $options = array_merge($defaultOptions, $customOptions); $baseClass = 'inline-flex items-center font-semibold shadow-xs rounded-full focus:outline-none focus:ring'; $variantClass = [ 'default' => 'text-black bg-gray-300 hover:bg-gray-400', 'primary' => 'text-white bg-pine-700 hover:bg-pine-800', 'secondary' => 'text-white bg-gray-700 hover:bg-gray-800', 'accent' => 'text-white bg-rose-600 hover:bg-rose-800', 'success' => 'text-white bg-green-600 hover:bg-green-700', 'danger' => 'text-white bg-red-600 hover:bg-red-700', 'warning' => 'text-black bg-yellow-500 hover:bg-yellow-600', 'info' => 'text-white bg-blue-500 hover:bg-blue-600', ]; $sizeClass = [ 'small' => 'text-xs md:text-sm', 'base' => 'text-sm md:text-base', 'large' => 'text-lg md:text-xl', ]; $basePaddings = [ 'small' => 'px-2 md:px-3 md:py-1', 'base' => 'px-3 py-1 md:px-4 md:py-2', 'large' => 'px-3 py-2 md:px-5', ]; $squaredPaddings = [ 'small' => 'p-1', 'base' => 'p-2', 'large' => 'p-3', ]; $buttonClass = $baseClass . ' ' . ($options['isSquared'] ? $squaredPaddings[$options['size']] : $basePaddings[$options['size']]) . ' ' . $sizeClass[$options['size']] . ' ' . $variantClass[$options['variant']]; if (array_key_exists('class', $customAttributes)) { $buttonClass .= ' ' . $customAttributes['class']; unset($customAttributes['class']); } if ($options['iconLeft']) { $label = icon($options['iconLeft'], 'mr-2') . $label; } if ($options['iconRight']) { $label .= icon($options['iconRight'], 'ml-2'); } if ($uri !== '') { return anchor($uri, $label, array_merge([ 'class' => $buttonClass, ], $customAttributes,),); } $defaultButtonAttributes = [ 'type' => 'button', ]; $attributes = stringify_attributes(array_merge($defaultButtonAttributes, $customAttributes),); return << {$label} CODE_SAMPLE; } } // ------------------------------------------------------------------------ if (! function_exists('icon_button')) { /** * Icon Button component * * Abstracts the `button()` helper to create a stylized icon button * * @param string $icon The button icon * @param string $title The button label * @param array $customOptions button options: variant, size, iconLeft, iconRight * @param array $customAttributes Additional attributes */ function icon_button( string $icon, string $title, string $uri = '', array $customOptions = [], array $customAttributes = [] ): string { $defaultOptions = [ 'isSquared' => true, ]; $options = array_merge($defaultOptions, $customOptions); $defaultAttributes = [ 'title' => $title, 'data-toggle' => 'tooltip', 'data-placement' => 'bottom', ]; $attributes = array_merge($defaultAttributes, $customAttributes); return button(icon($icon), $uri, $options, $attributes); } } // ------------------------------------------------------------------------ if (! function_exists('hint_tooltip')) { /** * Hint component * * Used to produce tooltip with a question mark icon for hint texts * * @param string $hintText The hint text */ function hint_tooltip(string $hintText = '', string $class = ''): string { $tooltip = '' . icon('question') . ''; } } // ------------------------------------------------------------------------ if (! function_exists('data_table')) { /** * Data table component * * Creates a stylized table. * * @param array> $columns array of associate arrays with `header` and `cell` keys where `cell` is a function with a row of $data as parameter * @param mixed[] $data data to loop through and display in rows * @param mixed ...$rest Any other argument to pass to the `cell` function */ function data_table(array $columns, array $data = [], ...$rest): string { $table = new Table(); $template = [ 'table_open' => '', 'thead_open' => '', 'heading_cell_start' => '', 'row_alt_start' => '', ]; $table->setTemplate($template); $tableHeaders = []; foreach ($columns as $column) { $tableHeaders[] = $column['header']; } $table->setHeading($tableHeaders); if (($dataCount = count($data)) !== 0) { for ($i = 0; $i < $dataCount; ++$i) { $row = $data[$i]; $rowData = []; foreach ($columns as $column) { $rowData[] = $column['cell']($row, ...$rest); } $table->addRow($rowData); } } else { return lang('Common.no_data'); } return '
' . $table->generate() . '
'; } } // ------------------------------------------------------------------------ if (! function_exists('publication_pill')) { /** * Publication pill component * * Shows the stylized publication datetime in regards to current datetime. */ function publication_pill(?Time $publicationDate, string $publicationStatus, string $customClass = ''): string { if ($publicationDate === null) { return ''; } $class = $publicationStatus === 'published' ? 'text-pine-500 border-pine-500' : 'text-red-600 border-red-600'; $langOptions = [ '', ]; $label = lang('Episode.publication_status.' . $publicationStatus, $langOptions,); return '' . $label . ''; } } // ------------------------------------------------------------------------ if (! function_exists('publication_button')) { /** * Publication button component * * Displays the appropriate publication button depending on the publication status. */ function publication_button(int $podcastId, int $episodeId, string $publicationStatus): string { /** @phpstan-ignore-next-line */ switch ($publicationStatus) { case 'not_published': $label = lang('Episode.publish'); $route = route_to('episode-publish', $podcastId, $episodeId); $variant = 'primary'; $iconLeft = 'upload-cloud'; break; case 'scheduled': $label = lang('Episode.publish_edit'); $route = route_to('episode-publish_edit', $podcastId, $episodeId,); $variant = 'accent'; $iconLeft = 'upload-cloud'; break; case 'published': $label = lang('Episode.unpublish'); $route = route_to('episode-unpublish', $podcastId, $episodeId); $variant = 'danger'; $iconLeft = 'cloud-off'; break; default: $label = ''; $route = ''; $variant = ''; $iconLeft = ''; break; } return button($label, $route, [ 'variant' => $variant, 'iconLeft' => $iconLeft, ]); } } // ------------------------------------------------------------------------ if (! function_exists('episode_numbering')) { /** * Returns relevant translated episode numbering. * * @param bool $isAbbr component will show abbreviated numbering if true */ function episode_numbering( ?int $episodeNumber = null, ?int $seasonNumber = null, string $class = '', bool $isAbbr = false ): string { if (! $episodeNumber && ! $seasonNumber) { return ''; } $transKey = ''; $args = []; if ($episodeNumber !== null) { $args['episodeNumber'] = $episodeNumber; } if ($seasonNumber !== null) { $args['seasonNumber'] = $seasonNumber; } if ($episodeNumber !== null && $seasonNumber !== null) { $transKey = 'Episode.season_episode'; } elseif ($episodeNumber !== null && $seasonNumber === null) { $transKey = 'Episode.number'; } elseif ($episodeNumber === null && $seasonNumber !== null) { $transKey = 'Episode.season'; } if ($isAbbr) { return '' . lang($transKey . '_abbr', $args) . ''; } return '' . lang($transKey, $args) . ''; } } if (! function_exists('location_link')) { /** * Returns link to display from location info */ function location_link(?Location $location, string $class = ''): string { if ($location === null) { return ''; } return anchor( $location->url, icon('map-pin', 'mr-2') . $location->name, [ 'class' => 'inline-flex items-baseline hover:underline' . ($class === '' ? '' : " {$class}"), 'target' => '_blank', 'rel' => 'noreferrer noopener', ], ); } } // ------------------------------------------------------------------------ if (! function_exists('person_list')) { /** * Returns list of persons images * * @param Person[] $persons */ function person_list(array $persons, string $class = ''): string { if ($persons === []) { return ''; } $personList = "
"; foreach ($persons as $person) { $personList .= anchor( $person->information_url ?? '#', "{$person->full_name}", [ 'class' => 'flex-shrink-0 focus:outline-none focus:ring focus:ring-inset', 'target' => '_blank', 'rel' => 'noreferrer noopener', 'title' => '' . $person->full_name . '' . implode( array_map(function ($role) { return '
' . lang( 'PersonsTaxonomy.persons.' . $role->group . '.roles.' . $role->role . '.label', ); }, $person->roles), ), 'data-toggle' => 'tooltip', 'data-placement' => 'bottom', ], ); } $personList .= '
'; return $personList; } } // ------------------------------------------------------------------------
', 'cell_start' => '', 'cell_alt_start' => '', 'row_start' => '