recent-posts-widget.php

I’ll build this as a complete advanced recent-posts-widget.php file for your RX theme: secure widget class, many display options, caching, thumbnails, meta, taxonomy filters, ordering, schema-friendly markup, and safe output.

Below is an advanced inc/widgets/recent-posts-widget.php file for your RX Theme.
You can replace your current file with this code.

<?php
/**
 * RX Theme - Advanced Recent Posts Widget
 *
 * File: inc/widgets/recent-posts-widget.php
 *
 * Features:
 * - Recent / popular / random / modified / commented posts
 * - Post type support
 * - Category, tag, taxonomy filters
 * - Include / exclude post IDs
 * - Author filter
 * - Date range filter
 * - Thumbnail support
 * - Meta display: date, author, comments, categories, tags, reading time
 * - Excerpt support
 * - Custom title length
 * - Custom excerpt length
 * - Sticky posts option
 * - Query order options
 * - Offset support
 * - Pagination-like “View All” link
 * - Schema-friendly HTML
 * - Transient caching
 * - Object cache friendly
 * - Accessibility-friendly markup
 * - RTL-safe / theme-friendly classes
 * - Elementor/block-theme compatible sidebar widget
 *
 * @package RX_Theme
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'RX_Advanced_Recent_Posts_Widget' ) ) :

class RX_Advanced_Recent_Posts_Widget extends WP_Widget {

	/**
	 * Widget defaults.
	 */
	protected $defaults = array(
		'title'                  => 'Recent Posts',
		'post_type'              => 'post',
		'posts_per_page'         => 5,
		'offset'                 => 0,
		'orderby'                => 'date',
		'order'                  => 'DESC',

		'category_ids'           => '',
		'tag_ids'                => '',
		'taxonomy'               => '',
		'taxonomy_terms'         => '',
		'author_ids'             => '',
		'include_posts'          => '',
		'exclude_posts'          => '',
		'date_after'             => '',
		'date_before'            => '',

		'show_thumbnail'         => 1,
		'thumbnail_size'         => 'thumbnail',
		'thumbnail_width'        => 90,
		'thumbnail_height'       => 90,
		'thumbnail_position'     => 'left',
		'fallback_image'         => '',

		'show_title'             => 1,
		'title_words'            => 12,
		'show_excerpt'           => 0,
		'excerpt_words'          => 18,

		'show_date'              => 1,
		'show_modified_date'     => 0,
		'show_author'            => 0,
		'show_comments'          => 0,
		'show_category'          => 0,
		'show_tags'              => 0,
		'show_reading_time'      => 0,

		'show_view_all'          => 0,
		'view_all_text'          => 'View All Posts',
		'view_all_url'           => '',

		'open_new_tab'           => 0,
		'nofollow_links'         => 0,
		'ignore_sticky'          => 1,
		'only_sticky'            => 0,
		'hide_current_post'      => 1,

		'enable_cache'           => 1,
		'cache_time'             => 30,

		'layout_style'           => 'list',
		'wrapper_class'          => '',
		'item_class'             => '',
		'before_item'            => '',
		'after_item'             => '',
	);

	/**
	 * Constructor.
	 */
	public function __construct() {
		parent::__construct(
			'rx_advanced_recent_posts_widget',
			esc_html__( 'RX Advanced Recent Posts', 'rx-theme' ),
			array(
				'classname'                   => 'rx-advanced-recent-posts-widget',
				'description'                 => esc_html__( 'Powerful recent posts widget with thumbnail, meta, taxonomy filter, cache, and layout options.', 'rx-theme' ),
				'customize_selective_refresh' => true,
			)
		);

		add_action( 'save_post', array( $this, 'flush_widget_cache' ) );
		add_action( 'deleted_post', array( $this, 'flush_widget_cache' ) );
		add_action( 'switch_theme', array( $this, 'flush_widget_cache' ) );
	}

	/**
	 * Front-end widget output.
	 */
	public function widget( $args, $instance ) {
		$instance = wp_parse_args( (array) $instance, $this->defaults );

		$cache_key = $this->get_cache_key( $args, $instance );

		if ( ! empty( $instance['enable_cache'] ) ) {
			$cached = get_transient( $cache_key );

			if ( false !== $cached ) {
				echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				return;
			}
		}

		ob_start();

		echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		$title = apply_filters(
			'widget_title',
			! empty( $instance['title'] ) ? $instance['title'] : '',
			$instance,
			$this->id_base
		);

		if ( ! empty( $title ) ) {
			echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		}

		$query_args = $this->build_query_args( $instance );

		$recent_query = new WP_Query( $query_args );

		$wrapper_classes = array(
			'rx-recent-posts',
			'rx-recent-posts-layout-' . sanitize_html_class( $instance['layout_style'] ),
			'rx-thumb-' . sanitize_html_class( $instance['thumbnail_position'] ),
		);

		if ( ! empty( $instance['wrapper_class'] ) ) {
			$wrapper_classes[] = sanitize_html_class( $instance['wrapper_class'] );
		}

		if ( $recent_query->have_posts() ) :
			?>
			<div class="<?php echo esc_attr( implode( ' ', array_filter( $wrapper_classes ) ) ); ?>">
				<ul class="rx-recent-posts-list" itemscope itemtype="https://schema.org/ItemList">
					<?php
					$count = 0;

					while ( $recent_query->have_posts() ) :
						$recent_query->the_post();

						$count++;

						$post_id = get_the_ID();

						$item_classes = array(
							'rx-recent-post-item',
							'rx-recent-post-item-' . absint( $count ),
							'clearfix',
						);

						if ( has_post_thumbnail( $post_id ) ) {
							$item_classes[] = 'has-post-thumbnail';
						} else {
							$item_classes[] = 'no-post-thumbnail';
						}

						if ( ! empty( $instance['item_class'] ) ) {
							$item_classes[] = sanitize_html_class( $instance['item_class'] );
						}

						$link_attrs = $this->get_link_attrs( $instance );
						?>
						<li class="<?php echo esc_attr( implode( ' ', array_filter( $item_classes ) ) ); ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
							<meta itemprop="position" content="<?php echo esc_attr( $count ); ?>">

							<?php
							if ( ! empty( $instance['before_item'] ) ) {
								echo wp_kses_post( $instance['before_item'] );
							}
							?>

							<article class="rx-recent-post-article" itemscope itemtype="https://schema.org/BlogPosting">

								<?php if ( ! empty( $instance['show_thumbnail'] ) ) : ?>
									<?php echo $this->get_thumbnail_html( $post_id, $instance, $link_attrs ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
								<?php endif; ?>

								<div class="rx-recent-post-content">

									<?php if ( ! empty( $instance['show_title'] ) ) : ?>
										<h4 class="rx-recent-post-title" itemprop="headline">
											<a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>" <?php echo $link_attrs; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> itemprop="url">
												<?php echo esc_html( $this->trim_words( get_the_title( $post_id ), absint( $instance['title_words'] ) ) ); ?>
											</a>
										</h4>
									<?php endif; ?>

									<?php echo $this->get_meta_html( $post_id, $instance ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>

									<?php if ( ! empty( $instance['show_excerpt'] ) ) : ?>
										<div class="rx-recent-post-excerpt" itemprop="description">
											<?php echo esc_html( $this->get_excerpt( $post_id, absint( $instance['excerpt_words'] ) ) ); ?>
										</div>
									<?php endif; ?>

								</div>
							</article>

							<?php
							if ( ! empty( $instance['after_item'] ) ) {
								echo wp_kses_post( $instance['after_item'] );
							}
							?>
						</li>
					<?php endwhile; ?>
				</ul>

				<?php if ( ! empty( $instance['show_view_all'] ) ) : ?>
					<?php
					$view_all_url = ! empty( $instance['view_all_url'] ) ? $instance['view_all_url'] : get_post_type_archive_link( $instance['post_type'] );

					if ( empty( $view_all_url ) && 'post' === $instance['post_type'] ) {
						$view_all_url = get_permalink( get_option( 'page_for_posts' ) );
					}
					?>
					<?php if ( ! empty( $view_all_url ) ) : ?>
						<div class="rx-recent-posts-view-all">
							<a class="rx-recent-posts-view-all-link" href="<?php echo esc_url( $view_all_url ); ?>" <?php echo $this->get_link_attrs( $instance ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
								<?php echo esc_html( $instance['view_all_text'] ); ?>
							</a>
						</div>
					<?php endif; ?>
				<?php endif; ?>
			</div>
			<?php
		else :
			?>
			<p class="rx-recent-posts-empty">
				<?php esc_html_e( 'No posts found.', 'rx-theme' ); ?>
			</p>
			<?php
		endif;

		wp_reset_postdata();

		echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		$output = ob_get_clean();

		if ( ! empty( $instance['enable_cache'] ) ) {
			$cache_minutes = max( 1, absint( $instance['cache_time'] ) );
			set_transient( $cache_key, $output, $cache_minutes * MINUTE_IN_SECONDS );
		}

		echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Admin form.
	 */
	public function form( $instance ) {
		$instance = wp_parse_args( (array) $instance, $this->defaults );

		$post_types = get_post_types(
			array(
				'public' => true,
			),
			'objects'
		);

		$thumbnail_sizes = get_intermediate_image_sizes();

		$orderby_options = array(
			'date'          => esc_html__( 'Published Date', 'rx-theme' ),
			'modified'      => esc_html__( 'Modified Date', 'rx-theme' ),
			'title'         => esc_html__( 'Title', 'rx-theme' ),
			'comment_count' => esc_html__( 'Comment Count', 'rx-theme' ),
			'rand'          => esc_html__( 'Random', 'rx-theme' ),
			'menu_order'    => esc_html__( 'Menu Order', 'rx-theme' ),
			'ID'            => esc_html__( 'Post ID', 'rx-theme' ),
		);

		$layout_options = array(
			'list'    => esc_html__( 'List', 'rx-theme' ),
			'card'    => esc_html__( 'Card', 'rx-theme' ),
			'compact' => esc_html__( 'Compact', 'rx-theme' ),
			'grid'    => esc_html__( 'Grid', 'rx-theme' ),
			'minimal' => esc_html__( 'Minimal', 'rx-theme' ),
		);

		$thumb_positions = array(
			'left'  => esc_html__( 'Left', 'rx-theme' ),
			'right' => esc_html__( 'Right', 'rx-theme' ),
			'top'   => esc_html__( 'Top', 'rx-theme' ),
			'none'  => esc_html__( 'None', 'rx-theme' ),
		);
		?>

		<div class="rx-widget-admin rx-recent-posts-widget-admin">

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
					<?php esc_html_e( 'Widget Title:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
					type="text"
					value="<?php echo esc_attr( $instance['title'] ); ?>">
			</p>

			<hr>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'post_type' ) ); ?>">
					<?php esc_html_e( 'Post Type:', 'rx-theme' ); ?>
				</label>
				<select class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'post_type' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'post_type' ) ); ?>">
					<?php foreach ( $post_types as $post_type ) : ?>
						<option value="<?php echo esc_attr( $post_type->name ); ?>" <?php selected( $instance['post_type'], $post_type->name ); ?>>
							<?php echo esc_html( $post_type->labels->singular_name ); ?>
						</option>
					<?php endforeach; ?>
				</select>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'posts_per_page' ) ); ?>">
					<?php esc_html_e( 'Number of Posts:', 'rx-theme' ); ?>
				</label>
				<input class="tiny-text"
					id="<?php echo esc_attr( $this->get_field_id( 'posts_per_page' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'posts_per_page' ) ); ?>"
					type="number"
					min="1"
					max="50"
					value="<?php echo esc_attr( absint( $instance['posts_per_page'] ) ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'offset' ) ); ?>">
					<?php esc_html_e( 'Offset:', 'rx-theme' ); ?>
				</label>
				<input class="tiny-text"
					id="<?php echo esc_attr( $this->get_field_id( 'offset' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'offset' ) ); ?>"
					type="number"
					min="0"
					value="<?php echo esc_attr( absint( $instance['offset'] ) ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'orderby' ) ); ?>">
					<?php esc_html_e( 'Order By:', 'rx-theme' ); ?>
				</label>
				<select class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'orderby' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'orderby' ) ); ?>">
					<?php foreach ( $orderby_options as $key => $label ) : ?>
						<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $instance['orderby'], $key ); ?>>
							<?php echo esc_html( $label ); ?>
						</option>
					<?php endforeach; ?>
				</select>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'order' ) ); ?>">
					<?php esc_html_e( 'Order:', 'rx-theme' ); ?>
				</label>
				<select class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'order' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'order' ) ); ?>">
					<option value="DESC" <?php selected( $instance['order'], 'DESC' ); ?>><?php esc_html_e( 'Descending', 'rx-theme' ); ?></option>
					<option value="ASC" <?php selected( $instance['order'], 'ASC' ); ?>><?php esc_html_e( 'Ascending', 'rx-theme' ); ?></option>
				</select>
			</p>

			<hr>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'category_ids' ) ); ?>">
					<?php esc_html_e( 'Category IDs:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'category_ids' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'category_ids' ) ); ?>"
					type="text"
					placeholder="1,2,3"
					value="<?php echo esc_attr( $instance['category_ids'] ); ?>">
				<small><?php esc_html_e( 'Comma separated category IDs.', 'rx-theme' ); ?></small>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'tag_ids' ) ); ?>">
					<?php esc_html_e( 'Tag IDs:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'tag_ids' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'tag_ids' ) ); ?>"
					type="text"
					placeholder="4,5,6"
					value="<?php echo esc_attr( $instance['tag_ids'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'taxonomy' ) ); ?>">
					<?php esc_html_e( 'Custom Taxonomy Slug:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'taxonomy' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'taxonomy' ) ); ?>"
					type="text"
					placeholder="medical_category"
					value="<?php echo esc_attr( $instance['taxonomy'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'taxonomy_terms' ) ); ?>">
					<?php esc_html_e( 'Custom Taxonomy Term IDs:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'taxonomy_terms' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_terms' ) ); ?>"
					type="text"
					placeholder="10,11,12"
					value="<?php echo esc_attr( $instance['taxonomy_terms'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'author_ids' ) ); ?>">
					<?php esc_html_e( 'Author IDs:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'author_ids' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'author_ids' ) ); ?>"
					type="text"
					placeholder="1,2"
					value="<?php echo esc_attr( $instance['author_ids'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'include_posts' ) ); ?>">
					<?php esc_html_e( 'Include Post IDs Only:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'include_posts' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'include_posts' ) ); ?>"
					type="text"
					placeholder="100,101,102"
					value="<?php echo esc_attr( $instance['include_posts'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'exclude_posts' ) ); ?>">
					<?php esc_html_e( 'Exclude Post IDs:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'exclude_posts' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'exclude_posts' ) ); ?>"
					type="text"
					placeholder="200,201"
					value="<?php echo esc_attr( $instance['exclude_posts'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'date_after' ) ); ?>">
					<?php esc_html_e( 'Date After:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'date_after' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'date_after' ) ); ?>"
					type="date"
					value="<?php echo esc_attr( $instance['date_after'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'date_before' ) ); ?>">
					<?php esc_html_e( 'Date Before:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'date_before' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'date_before' ) ); ?>"
					type="date"
					value="<?php echo esc_attr( $instance['date_before'] ); ?>">
			</p>

			<hr>

			<p>
				<input class="checkbox"
					id="<?php echo esc_attr( $this->get_field_id( 'show_thumbnail' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'show_thumbnail' ) ); ?>"
					type="checkbox"
					value="1" <?php checked( $instance['show_thumbnail'], 1 ); ?>>
				<label for="<?php echo esc_attr( $this->get_field_id( 'show_thumbnail' ) ); ?>">
					<?php esc_html_e( 'Show Thumbnail', 'rx-theme' ); ?>
				</label>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'thumbnail_size' ) ); ?>">
					<?php esc_html_e( 'Thumbnail Size:', 'rx-theme' ); ?>
				</label>
				<select class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'thumbnail_size' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'thumbnail_size' ) ); ?>">
					<?php foreach ( $thumbnail_sizes as $size ) : ?>
						<option value="<?php echo esc_attr( $size ); ?>" <?php selected( $instance['thumbnail_size'], $size ); ?>>
							<?php echo esc_html( $size ); ?>
						</option>
					<?php endforeach; ?>
					<option value="full" <?php selected( $instance['thumbnail_size'], 'full' ); ?>>
						<?php esc_html_e( 'Full', 'rx-theme' ); ?>
					</option>
				</select>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'thumbnail_position' ) ); ?>">
					<?php esc_html_e( 'Thumbnail Position:', 'rx-theme' ); ?>
				</label>
				<select class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'thumbnail_position' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'thumbnail_position' ) ); ?>">
					<?php foreach ( $thumb_positions as $key => $label ) : ?>
						<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $instance['thumbnail_position'], $key ); ?>>
							<?php echo esc_html( $label ); ?>
						</option>
					<?php endforeach; ?>
				</select>
			</p>

			<p>
				<label><?php esc_html_e( 'Thumbnail Width / Height:', 'rx-theme' ); ?></label><br>
				<input class="small-text"
					name="<?php echo esc_attr( $this->get_field_name( 'thumbnail_width' ) ); ?>"
					type="number"
					min="20"
					value="<?php echo esc_attr( absint( $instance['thumbnail_width'] ) ); ?>">
				×
				<input class="small-text"
					name="<?php echo esc_attr( $this->get_field_name( 'thumbnail_height' ) ); ?>"
					type="number"
					min="20"
					value="<?php echo esc_attr( absint( $instance['thumbnail_height'] ) ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'fallback_image' ) ); ?>">
					<?php esc_html_e( 'Fallback Image URL:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'fallback_image' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'fallback_image' ) ); ?>"
					type="url"
					value="<?php echo esc_url( $instance['fallback_image'] ); ?>">
			</p>

			<hr>

			<p>
				<input class="checkbox"
					id="<?php echo esc_attr( $this->get_field_id( 'show_title' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'show_title' ) ); ?>"
					type="checkbox"
					value="1" <?php checked( $instance['show_title'], 1 ); ?>>
				<label for="<?php echo esc_attr( $this->get_field_id( 'show_title' ) ); ?>">
					<?php esc_html_e( 'Show Post Title', 'rx-theme' ); ?>
				</label>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'title_words' ) ); ?>">
					<?php esc_html_e( 'Title Words:', 'rx-theme' ); ?>
				</label>
				<input class="tiny-text"
					id="<?php echo esc_attr( $this->get_field_id( 'title_words' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'title_words' ) ); ?>"
					type="number"
					min="1"
					max="50"
					value="<?php echo esc_attr( absint( $instance['title_words'] ) ); ?>">
			</p>

			<p>
				<input class="checkbox"
					id="<?php echo esc_attr( $this->get_field_id( 'show_excerpt' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'show_excerpt' ) ); ?>"
					type="checkbox"
					value="1" <?php checked( $instance['show_excerpt'], 1 ); ?>>
				<label for="<?php echo esc_attr( $this->get_field_id( 'show_excerpt' ) ); ?>">
					<?php esc_html_e( 'Show Excerpt', 'rx-theme' ); ?>
				</label>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'excerpt_words' ) ); ?>">
					<?php esc_html_e( 'Excerpt Words:', 'rx-theme' ); ?>
				</label>
				<input class="tiny-text"
					id="<?php echo esc_attr( $this->get_field_id( 'excerpt_words' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'excerpt_words' ) ); ?>"
					type="number"
					min="1"
					max="100"
					value="<?php echo esc_attr( absint( $instance['excerpt_words'] ) ); ?>">
			</p>

			<hr>

			<p><strong><?php esc_html_e( 'Meta Display Options', 'rx-theme' ); ?></strong></p>

			<?php
			$this->checkbox_field( $instance, 'show_date', esc_html__( 'Show Published Date', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'show_modified_date', esc_html__( 'Show Modified Date', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'show_author', esc_html__( 'Show Author', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'show_comments', esc_html__( 'Show Comment Count', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'show_category', esc_html__( 'Show Categories', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'show_tags', esc_html__( 'Show Tags', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'show_reading_time', esc_html__( 'Show Reading Time', 'rx-theme' ) );
			?>

			<hr>

			<p><strong><?php esc_html_e( 'Advanced Options', 'rx-theme' ); ?></strong></p>

			<?php
			$this->checkbox_field( $instance, 'ignore_sticky', esc_html__( 'Ignore Sticky Posts', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'only_sticky', esc_html__( 'Show Only Sticky Posts', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'hide_current_post', esc_html__( 'Hide Current Post on Single Page', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'open_new_tab', esc_html__( 'Open Links in New Tab', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'nofollow_links', esc_html__( 'Add Nofollow to Links', 'rx-theme' ) );
			$this->checkbox_field( $instance, 'enable_cache', esc_html__( 'Enable Cache', 'rx-theme' ) );
			?>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'cache_time' ) ); ?>">
					<?php esc_html_e( 'Cache Time in Minutes:', 'rx-theme' ); ?>
				</label>
				<input class="tiny-text"
					id="<?php echo esc_attr( $this->get_field_id( 'cache_time' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'cache_time' ) ); ?>"
					type="number"
					min="1"
					max="1440"
					value="<?php echo esc_attr( absint( $instance['cache_time'] ) ); ?>">
			</p>

			<hr>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'layout_style' ) ); ?>">
					<?php esc_html_e( 'Layout Style:', 'rx-theme' ); ?>
				</label>
				<select class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'layout_style' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'layout_style' ) ); ?>">
					<?php foreach ( $layout_options as $key => $label ) : ?>
						<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $instance['layout_style'], $key ); ?>>
							<?php echo esc_html( $label ); ?>
						</option>
					<?php endforeach; ?>
				</select>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'wrapper_class' ) ); ?>">
					<?php esc_html_e( 'Extra Wrapper Class:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'wrapper_class' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'wrapper_class' ) ); ?>"
					type="text"
					value="<?php echo esc_attr( $instance['wrapper_class'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'item_class' ) ); ?>">
					<?php esc_html_e( 'Extra Item Class:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'item_class' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'item_class' ) ); ?>"
					type="text"
					value="<?php echo esc_attr( $instance['item_class'] ); ?>">
			</p>

			<hr>

			<p>
				<input class="checkbox"
					id="<?php echo esc_attr( $this->get_field_id( 'show_view_all' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'show_view_all' ) ); ?>"
					type="checkbox"
					value="1" <?php checked( $instance['show_view_all'], 1 ); ?>>
				<label for="<?php echo esc_attr( $this->get_field_id( 'show_view_all' ) ); ?>">
					<?php esc_html_e( 'Show View All Link', 'rx-theme' ); ?>
				</label>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'view_all_text' ) ); ?>">
					<?php esc_html_e( 'View All Text:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'view_all_text' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'view_all_text' ) ); ?>"
					type="text"
					value="<?php echo esc_attr( $instance['view_all_text'] ); ?>">
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'view_all_url' ) ); ?>">
					<?php esc_html_e( 'Custom View All URL:', 'rx-theme' ); ?>
				</label>
				<input class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'view_all_url' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'view_all_url' ) ); ?>"
					type="url"
					value="<?php echo esc_url( $instance['view_all_url'] ); ?>">
			</p>

			<hr>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'before_item' ) ); ?>">
					<?php esc_html_e( 'Before Each Item HTML:', 'rx-theme' ); ?>
				</label>
				<textarea class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'before_item' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'before_item' ) ); ?>"
					rows="2"><?php echo esc_textarea( $instance['before_item'] ); ?></textarea>
			</p>

			<p>
				<label for="<?php echo esc_attr( $this->get_field_id( 'after_item' ) ); ?>">
					<?php esc_html_e( 'After Each Item HTML:', 'rx-theme' ); ?>
				</label>
				<textarea class="widefat"
					id="<?php echo esc_attr( $this->get_field_id( 'after_item' ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( 'after_item' ) ); ?>"
					rows="2"><?php echo esc_textarea( $instance['after_item'] ); ?></textarea>
			</p>

		</div>
		<?php
	}

	/**
	 * Save widget settings.
	 */
	public function update( $new_instance, $old_instance ) {
		$instance = wp_parse_args( (array) $old_instance, $this->defaults );

		$text_fields = array(
			'title',
			'post_type',
			'orderby',
			'order',
			'category_ids',
			'tag_ids',
			'taxonomy',
			'taxonomy_terms',
			'author_ids',
			'include_posts',
			'exclude_posts',
			'date_after',
			'date_before',
			'thumbnail_size',
			'thumbnail_position',
			'layout_style',
			'wrapper_class',
			'item_class',
			'view_all_text',
		);

		foreach ( $text_fields as $field ) {
			$instance[ $field ] = isset( $new_instance[ $field ] ) ? sanitize_text_field( $new_instance[ $field ] ) : '';
		}

		$url_fields = array(
			'fallback_image',
			'view_all_url',
		);

		foreach ( $url_fields as $field ) {
			$instance[ $field ] = isset( $new_instance[ $field ] ) ? esc_url_raw( $new_instance[ $field ] ) : '';
		}

		$html_fields = array(
			'before_item',
			'after_item',
		);

		foreach ( $html_fields as $field ) {
			$instance[ $field ] = isset( $new_instance[ $field ] ) ? wp_kses_post( $new_instance[ $field ] ) : '';
		}

		$number_fields = array(
			'posts_per_page',
			'offset',
			'thumbnail_width',
			'thumbnail_height',
			'title_words',
			'excerpt_words',
			'cache_time',
		);

		foreach ( $number_fields as $field ) {
			$instance[ $field ] = isset( $new_instance[ $field ] ) ? absint( $new_instance[ $field ] ) : absint( $this->defaults[ $field ] );
		}

		$checkbox_fields = array(
			'show_thumbnail',
			'show_title',
			'show_excerpt',
			'show_date',
			'show_modified_date',
			'show_author',
			'show_comments',
			'show_category',
			'show_tags',
			'show_reading_time',
			'show_view_all',
			'open_new_tab',
			'nofollow_links',
			'ignore_sticky',
			'only_sticky',
			'hide_current_post',
			'enable_cache',
		);

		foreach ( $checkbox_fields as $field ) {
			$instance[ $field ] = ! empty( $new_instance[ $field ] ) ? 1 : 0;
		}

		$instance['posts_per_page']  = min( 50, max( 1, absint( $instance['posts_per_page'] ) ) );
		$instance['title_words']     = min( 50, max( 1, absint( $instance['title_words'] ) ) );
		$instance['excerpt_words']   = min( 100, max( 1, absint( $instance['excerpt_words'] ) ) );
		$instance['cache_time']      = min( 1440, max( 1, absint( $instance['cache_time'] ) ) );
		$instance['thumbnail_width'] = max( 20, absint( $instance['thumbnail_width'] ) );
		$instance['thumbnail_height'] = max( 20, absint( $instance['thumbnail_height'] ) );

		$this->flush_widget_cache();

		return $instance;
	}

	/**
	 * Build WP_Query args.
	 */
	protected function build_query_args( $instance ) {
		$post_type = ! empty( $instance['post_type'] ) ? sanitize_key( $instance['post_type'] ) : 'post';

		$query_args = array(
			'post_type'              => $post_type,
			'post_status'            => 'publish',
			'posts_per_page'         => absint( $instance['posts_per_page'] ),
			'offset'                 => absint( $instance['offset'] ),
			'orderby'                => sanitize_key( $instance['orderby'] ),
			'order'                  => 'ASC' === strtoupper( $instance['order'] ) ? 'ASC' : 'DESC',
			'ignore_sticky_posts'    => ! empty( $instance['ignore_sticky'] ),
			'no_found_rows'          => true,
			'update_post_meta_cache' => true,
			'update_post_term_cache' => true,
		);

		if ( 'rand' === $query_args['orderby'] ) {
			$query_args['orderby'] = 'rand';
		}

		if ( ! empty( $instance['include_posts'] ) ) {
			$query_args['post__in'] = $this->csv_to_absint_array( $instance['include_posts'] );
			$query_args['orderby']  = 'post__in';
		}

		if ( ! empty( $instance['exclude_posts'] ) ) {
			$query_args['post__not_in'] = $this->csv_to_absint_array( $instance['exclude_posts'] );
		}

		if ( ! empty( $instance['hide_current_post'] ) && is_singular() ) {
			$current_id = get_queried_object_id();

			if ( $current_id ) {
				if ( empty( $query_args['post__not_in'] ) ) {
					$query_args['post__not_in'] = array();
				}

				$query_args['post__not_in'][] = absint( $current_id );
			}
		}

		if ( ! empty( $instance['only_sticky'] ) ) {
			$sticky_posts = get_option( 'sticky_posts' );

			if ( ! empty( $sticky_posts ) && is_array( $sticky_posts ) ) {
				$query_args['post__in'] = array_map( 'absint', $sticky_posts );
			} else {
				$query_args['post__in'] = array( 0 );
			}
		}

		if ( ! empty( $instance['category_ids'] ) ) {
			$query_args['category__in'] = $this->csv_to_absint_array( $instance['category_ids'] );
		}

		if ( ! empty( $instance['tag_ids'] ) ) {
			$query_args['tag__in'] = $this->csv_to_absint_array( $instance['tag_ids'] );
		}

		if ( ! empty( $instance['author_ids'] ) ) {
			$query_args['author__in'] = $this->csv_to_absint_array( $instance['author_ids'] );
		}

		$tax_query = array();

		if ( ! empty( $instance['taxonomy'] ) && ! empty( $instance['taxonomy_terms'] ) && taxonomy_exists( $instance['taxonomy'] ) ) {
			$tax_query[] = array(
				'taxonomy' => sanitize_key( $instance['taxonomy'] ),
				'field'    => 'term_id',
				'terms'    => $this->csv_to_absint_array( $instance['taxonomy_terms'] ),
			);
		}

		if ( ! empty( $tax_query ) ) {
			$query_args['tax_query'] = $tax_query; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
		}

		$date_query = array();

		if ( ! empty( $instance['date_after'] ) ) {
			$date_query['after'] = sanitize_text_field( $instance['date_after'] );
		}

		if ( ! empty( $instance['date_before'] ) ) {
			$date_query['before'] = sanitize_text_field( $instance['date_before'] );
		}

		if ( ! empty( $date_query ) ) {
			$date_query['inclusive']    = true;
			$query_args['date_query'][] = $date_query;
		}

		/**
		 * Filter query args for RX recent posts widget.
		 */
		return apply_filters( 'rx_recent_posts_widget_query_args', $query_args, $instance, $this );
	}

	/**
	 * Thumbnail HTML.
	 */
	protected function get_thumbnail_html( $post_id, $instance, $link_attrs ) {
		if ( 'none' === $instance['thumbnail_position'] ) {
			return '';
		}

		$width  = absint( $instance['thumbnail_width'] );
		$height = absint( $instance['thumbnail_height'] );
		$size   = ! empty( $instance['thumbnail_size'] ) ? sanitize_key( $instance['thumbnail_size'] ) : 'thumbnail';

		$image_html = '';

		if ( has_post_thumbnail( $post_id ) ) {
			$image_html = get_the_post_thumbnail(
				$post_id,
				$size,
				array(
					'class'    => 'rx-recent-post-thumbnail-img',
					'alt'      => esc_attr( get_the_title( $post_id ) ),
					'loading'  => 'lazy',
					'decoding' => 'async',
					'style'    => 'width:' . $width . 'px;height:' . $height . 'px;object-fit:cover;',
					'itemprop' => 'image',
				)
			);
		} elseif ( ! empty( $instance['fallback_image'] ) ) {
			$image_html = sprintf(
				'<img class="rx-recent-post-thumbnail-img rx-fallback-img" src="%1$s" alt="%2$s" loading="lazy" decoding="async" style="width:%3$dpx;height:%4$dpx;object-fit:cover;" itemprop="image">',
				esc_url( $instance['fallback_image'] ),
				esc_attr( get_the_title( $post_id ) ),
				$width,
				$height
			);
		}

		if ( empty( $image_html ) ) {
			return '';
		}

		return sprintf(
			'<a class="rx-recent-post-thumbnail" href="%1$s" %2$s aria-label="%3$s">%4$s</a>',
			esc_url( get_permalink( $post_id ) ),
			$link_attrs,
			esc_attr( get_the_title( $post_id ) ),
			$image_html
		);
	}

	/**
	 * Meta HTML.
	 */
	protected function get_meta_html( $post_id, $instance ) {
		$meta = array();

		if ( ! empty( $instance['show_date'] ) ) {
			$meta[] = sprintf(
				'<span class="rx-post-meta-date"><time datetime="%1$s" itemprop="datePublished">%2$s</time></span>',
				esc_attr( get_the_date( DATE_W3C, $post_id ) ),
				esc_html( get_the_date( '', $post_id ) )
			);
		}

		if ( ! empty( $instance['show_modified_date'] ) ) {
			$meta[] = sprintf(
				'<span class="rx-post-meta-modified">%1$s <time datetime="%2$s" itemprop="dateModified">%3$s</time></span>',
				esc_html__( 'Updated:', 'rx-theme' ),
				esc_attr( get_the_modified_date( DATE_W3C, $post_id ) ),
				esc_html( get_the_modified_date( '', $post_id ) )
			);
		}

		if ( ! empty( $instance['show_author'] ) ) {
			$author_id = get_post_field( 'post_author', $post_id );

			$meta[] = sprintf(
				'<span class="rx-post-meta-author" itemprop="author" itemscope itemtype="https://schema.org/Person">%1$s <a href="%2$s" itemprop="url"><span itemprop="name">%3$s</span></a></span>',
				esc_html__( 'By', 'rx-theme' ),
				esc_url( get_author_posts_url( $author_id ) ),
				esc_html( get_the_author_meta( 'display_name', $author_id ) )
			);
		}

		if ( ! empty( $instance['show_comments'] ) && comments_open( $post_id ) ) {
			$comments_number = get_comments_number( $post_id );

			$meta[] = sprintf(
				'<span class="rx-post-meta-comments"><a href="%1$s">%2$s</a></span>',
				esc_url( get_comments_link( $post_id ) ),
				esc_html( sprintf( _n( '%s Comment', '%s Comments', $comments_number, 'rx-theme' ), number_format_i18n( $comments_number ) ) )
			);
		}

		if ( ! empty( $instance['show_category'] ) ) {
			$categories = get_the_category_list( ', ', '', $post_id );

			if ( ! empty( $categories ) ) {
				$meta[] = '<span class="rx-post-meta-categories">' . wp_kses_post( $categories ) . '</span>';
			}
		}

		if ( ! empty( $instance['show_tags'] ) ) {
			$tags = get_the_tag_list( '', ', ', '', $post_id );

			if ( ! empty( $tags ) ) {
				$meta[] = '<span class="rx-post-meta-tags">' . wp_kses_post( $tags ) . '</span>';
			}
		}

		if ( ! empty( $instance['show_reading_time'] ) ) {
			$meta[] = sprintf(
				'<span class="rx-post-meta-reading-time">%s</span>',
				esc_html( $this->get_reading_time( $post_id ) )
			);
		}

		if ( empty( $meta ) ) {
			return '';
		}

		return '<div class="rx-recent-post-meta">' . implode( '<span class="rx-meta-separator"> / </span>', $meta ) . '</div>';
	}

	/**
	 * Get excerpt.
	 */
	protected function get_excerpt( $post_id, $words = 18 ) {
		$excerpt = get_the_excerpt( $post_id );

		if ( empty( $excerpt ) ) {
			$excerpt = wp_strip_all_tags( get_post_field( 'post_content', $post_id ) );
		}

		return $this->trim_words( $excerpt, $words );
	}

	/**
	 * Reading time.
	 */
	protected function get_reading_time( $post_id ) {
		$content = wp_strip_all_tags( get_post_field( 'post_content', $post_id ) );
		$words   = str_word_count( $content );
		$minutes = max( 1, ceil( $words / 200 ) );

		return sprintf(
			_n( '%s min read', '%s mins read', $minutes, 'rx-theme' ),
			number_format_i18n( $minutes )
		);
	}

	/**
	 * Trim words.
	 */
	protected function trim_words( $text, $words = 12 ) {
		$words = max( 1, absint( $words ) );

		return wp_trim_words( wp_strip_all_tags( $text ), $words, '…' );
	}

	/**
	 * Link attributes.
	 */
	protected function get_link_attrs( $instance ) {
		$attrs = array();

		if ( ! empty( $instance['open_new_tab'] ) ) {
			$attrs[] = 'target="_blank"';
			$attrs[] = 'rel="noopener noreferrer' . ( ! empty( $instance['nofollow_links'] ) ? ' nofollow' : '' ) . '"';
		} elseif ( ! empty( $instance['nofollow_links'] ) ) {
			$attrs[] = 'rel="nofollow"';
		}

		return implode( ' ', $attrs );
	}

	/**
	 * CSV to absint array.
	 */
	protected function csv_to_absint_array( $csv ) {
		if ( empty( $csv ) ) {
			return array();
		}

		$items = explode( ',', $csv );
		$items = array_map( 'trim', $items );
		$items = array_map( 'absint', $items );
		$items = array_filter( $items );

		return array_values( array_unique( $items ) );
	}

	/**
	 * Checkbox field helper.
	 */
	protected function checkbox_field( $instance, $field, $label ) {
		?>
		<p>
			<input class="checkbox"
				id="<?php echo esc_attr( $this->get_field_id( $field ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( $field ) ); ?>"
				type="checkbox"
				value="1" <?php checked( ! empty( $instance[ $field ] ), 1 ); ?>>
			<label for="<?php echo esc_attr( $this->get_field_id( $field ) ); ?>">
				<?php echo esc_html( $label ); ?>
			</label>
		</p>
		<?php
	}

	/**
	 * Cache key.
	 */
	protected function get_cache_key( $args, $instance ) {
		$key_data = array(
			'id'       => isset( $args['widget_id'] ) ? $args['widget_id'] : $this->id,
			'instance' => $instance,
			'page'     => is_singular() ? get_queried_object_id() : 0,
			'lang'     => function_exists( 'pll_current_language' ) ? pll_current_language() : get_locale(),
		);

		return 'rx_recent_posts_widget_' . md5( wp_json_encode( $key_data ) );
	}

	/**
	 * Flush widget cache.
	 *
	 * Note: Transients with dynamic md5 keys cannot be deleted one by one easily
	 * without direct DB search. This method uses version bumping approach through option.
	 */
	public function flush_widget_cache() {
		update_option( 'rx_recent_posts_widget_cache_version', time(), false );
	}
}

endif;

/**
 * Register widget.
 */
if ( ! function_exists( 'rx_register_advanced_recent_posts_widget' ) ) {
	function rx_register_advanced_recent_posts_widget() {
		register_widget( 'RX_Advanced_Recent_Posts_Widget' );
	}
}
add_action( 'widgets_init', 'rx_register_advanced_recent_posts_widget' );

One important improvement: your current cache key needs to use the cache version so cache is truly flushed after post updates. Replace the get_cache_key() method above with this improved version:

protected function get_cache_key( $args, $instance ) {
	$cache_version = get_option( 'rx_recent_posts_widget_cache_version', '1' );

	$key_data = array(
		'id'            => isset( $args['widget_id'] ) ? $args['widget_id'] : $this->id,
		'instance'      => $instance,
		'page'          => is_singular() ? get_queried_object_id() : 0,
		'lang'          => function_exists( 'pll_current_language' ) ? pll_current_language() : get_locale(),
		'cache_version' => $cache_version,
	);

	return 'rx_recent_posts_widget_' . md5( wp_json_encode( $key_data ) );
}

Also add this small CSS in your theme, for example:

assets/css/widgets/recent-posts-widget.css

.rx-recent-posts-list {
	list-style: none;
	margin: 0;
	padding: 0;
}

.rx-recent-post-item {
	margin-bottom: 16px;
	padding-bottom: 16px;
	border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.rx-recent-post-article {
	display: flex;
	gap: 12px;
	align-items: flex-start;
}

.rx-thumb-right .rx-recent-post-article {
	flex-direction: row-reverse;
}

.rx-thumb-top .rx-recent-post-article {
	display: block;
}

.rx-recent-post-thumbnail {
	display: block;
	flex: 0 0 auto;
	overflow: hidden;
	border-radius: 8px;
}

.rx-recent-post-thumbnail-img {
	display: block;
	max-width: 100%;
	height: auto;
}

.rx-recent-post-title {
	margin: 0 0 6px;
	font-size: 16px;
	line-height: 1.35;
}

.rx-recent-post-title a {
	text-decoration: none;
}

.rx-recent-post-meta {
	font-size: 13px;
	line-height: 1.5;
	opacity: 0.75;
	margin-bottom: 6px;
}

.rx-recent-post-excerpt {
	font-size: 14px;
	line-height: 1.6;
}

.rx-recent-posts-layout-grid .rx-recent-posts-list {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 16px;
}

.rx-recent-posts-layout-grid .rx-recent-post-item {
	margin: 0;
	padding: 0;
	border: 0;
}

.rx-recent-posts-layout-card .rx-recent-post-item {
	padding: 14px;
	border: 1px solid rgba(0, 0, 0, 0.08);
	border-radius: 12px;
}

.rx-recent-posts-layout-compact .rx-recent-post-item {
	margin-bottom: 10px;
	padding-bottom: 10px;
}

.rx-recent-posts-layout-minimal .rx-recent-post-thumbnail,
.rx-recent-posts-layout-minimal .rx-recent-post-meta,
.rx-recent-posts-layout-minimal .rx-recent-post-excerpt {
	display: none;
}

.rx-recent-posts-view-all {
	margin-top: 15px;
}

.rx-recent-posts-view-all-link {
	display: inline-flex;
	align-items: center;
	text-decoration: none;
	font-weight: 600;
}

@media (max-width: 600px) {
	.rx-recent-posts-layout-grid .rx-recent-posts-list {
		grid-template-columns: 1fr;
	}

	.rx-recent-post-article {
		gap: 10px;
	}
}

Then enqueue the CSS from your theme functions.php:

function rx_enqueue_recent_posts_widget_css() {
	wp_enqueue_style(
		'rx-recent-posts-widget',
		get_template_directory_uri() . '/assets/css/widgets/recent-posts-widget.css',
		array(),
		wp_get_theme()->get( 'Version' )
	);
}
add_action( 'wp_enqueue_scripts', 'rx_enqueue_recent_posts_widget_css' );

And make sure your theme loads the widget file in functions.php:

require_once get_template_directory() . '/inc/widgets/recent-posts-widget.php';

Leave a Reply

Your email address will not be published. Required fields are marked *