Shortcode with a foreach and arguments

Here is your problem:

foreach ($files[0] as $file) {

There is no $files variable, so there is nothing to loop through.

You can’t pull variables out of thin air and expect PHP to understand your intent. Computers are very literal, in the most extreme sense. They can’t intuit what you meant, or want. This code should be generating lots of PHP notices and warnings in your error log file when it runs.

Fundamentally, you can’t pass arrays/lists like this to shortcodes. Shortcode attributes are strings, text. Because of this, you cannot use an array as a default value, it doesn’t make sense:

      // this is not possible:
      'files_title' => ['File 1',' File 2'],
      'files_name' => ['file1.pdf', 'file2.pdf']

You can pass a comma separated list however, as your shortcode example demonstrates:

      'files_title' => 'File 1, File 2',
      'files_name' => 'file1.pdf, file2.pdf'

Then use explode to get an array, e.g. $file_titles = explode( ',', $att['files_title'];, then do it again for files_name. At this point, you have a generic PHP problem rather than a WordPress problem. You would have to create a new array from those 2 arrays where each item is an array with a title and name key, e.g. [ [ 'title'=>'File 1', 'name' => 'file1.pdf' ], [ 'title' => 'File 2' ... etc.

This is the kind of thing you would do in the first year of a computer science degree, or in a general programming book, but anybody can do it if they understand the fundamental basics of the programming language, specifically loops and arrays,

Alternatively, use composition. Don’t define your files in attributes, give them their own shortcodes:

[files title="title"]
[file url="..."]First file[/file]
[file url="..."]second file[/file]
[/files]

That way the file shortcode can be for just the <option> tag.

Fundamentally though, shortcodes are an old feature, you should consider replacing them with a block instead